link-header-parser 6.1.1 → 7.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 +4 -4
- data/README.md +7 -108
- data/USAGE.md +143 -0
- data/lib/link-header-parser.rb +14 -11
- data/lib/link_header_parser/link_header.rb +67 -76
- data/lib/link_header_parser/link_header_set.rb +96 -0
- data/lib/link_header_parser/target_attribute.rb +60 -0
- data/lib/link_header_parser/target_attribute_set.rb +72 -0
- data/link-header-parser.gemspec +4 -2
- metadata +23 -7
- data/lib/link_header_parser/link_header_parameter.rb +0 -58
- data/lib/link_header_parser/link_headers_collection.rb +0 -86
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3d7f18899021fd6fb79e5915e8d1945fc67a1de6093f8b8043e05b3de01cbffc
|
4
|
+
data.tar.gz: 30652a856807b2ab19dc1a2b0d7a419e7ff399f182a31eff3734d403eba1b8bb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a509d74c25c362a61eba888acf9ea4b5e733e66db19003e2c82ac9b9d72de70d491993ed9b1c5db4265905332fffbf2cdc4cb1f331bb8f7f19a5914d4b8cfbf5
|
7
|
+
data.tar.gz: 73a5ebd6172a225bf6ffff23250c241dcfe78855cb2aa97b65811deece22ae3a10b292fc444f973ec141396edc33736a2210183c2a3b85bac2f1b8160a0e5e21
|
data/README.md
CHANGED
@@ -6,6 +6,12 @@
|
|
6
6
|
[](https://rubygems.org/gems/link-header-parser)
|
7
7
|
[](https://codeberg.org/jgarber/link-header-parser-ruby)
|
8
8
|
|
9
|
+
## Key Features
|
10
|
+
|
11
|
+
- Implements [RFC-8288](https://tools.ietf.org/html/rfc8288) HTTP Link Header parsing algorithm (see [Appendix B](https://tools.ietf.org/html/rfc8288#appendix-B)).
|
12
|
+
- Supports [IRI](https://en.wikipedia.org/wiki/Internationalized_Resource_Identifier)s by using the [Addressable](https://rubygems.org/gems/addressable) gem.
|
13
|
+
- Supports Ruby 2.6 and newer.
|
14
|
+
|
9
15
|
## Getting Started
|
10
16
|
|
11
17
|
Before installing and using link-header-parser-ruby, you'll want to have [Ruby](https://www.ruby-lang.org) 2.6 (or newer) installed. Using a Ruby version managment tool like [rbenv](https://github.com/rbenv/rbenv), [chruby](https://github.com/postmodern/chruby), or [rvm](https://github.com/rvm/rvm) is recommended.
|
@@ -24,114 +30,7 @@ gem "link-header-parser"
|
|
24
30
|
|
25
31
|
## Usage
|
26
32
|
|
27
|
-
|
28
|
-
|
29
|
-
```ruby
|
30
|
-
require "net/http"
|
31
|
-
require "link-header-parser"
|
32
|
-
|
33
|
-
url = "https://sixtwothree.org"
|
34
|
-
link_headers = Net::HTTP.get_response(URI.parse(url)).get_fields("Link")
|
35
|
-
|
36
|
-
collection = LinkHeaderParser.parse(link_headers, base: url)
|
37
|
-
```
|
38
|
-
|
39
|
-
The `parse` method accepts two arguments:
|
40
|
-
|
41
|
-
1. an `Array` of strings representing HTTP Link headers (e.g. `['</>; rel="home"', '</chapters/1>; anchor="#copyright"; rel="license"']`)
|
42
|
-
1. a `String` (or any `String`-like object) representing the absolute URL of the resource providing the HTTP Link headers
|
43
|
-
|
44
|
-
In the example above, `collection` is an instance of `LinkHeadersCollection` which includes Ruby's [`Enumerable`](https://ruby-doc.org/core/Enumerable.html) mixin. This mixin allows for use of common methods like `each`, `first`/`last`, and `map`.
|
45
|
-
|
46
|
-
For example, you could retrieve an array of `target_uri`s:
|
47
|
-
|
48
|
-
```ruby
|
49
|
-
puts collection.map(&:target_uri)
|
50
|
-
#=> ["https://assets.sixtwothree.org/", "https://fonts.googleapis.com/", "https://fonts.gstatic.com/", "https://sixtwothree.org/webmentions"]
|
51
|
-
```
|
52
|
-
|
53
|
-
### Working with a `LinkHeadersCollection`
|
54
|
-
|
55
|
-
In addition to the included `Enumerable` methods, the following methods may be used to interact with a `LinkHeadersCollection`:
|
56
|
-
|
57
|
-
#### The `relation_types` Method
|
58
|
-
|
59
|
-
```ruby
|
60
|
-
puts collection.relation_types
|
61
|
-
#=> ["preconnect", "webmention"]
|
62
|
-
```
|
63
|
-
|
64
|
-
#### The `group_by_relation_type` Method
|
65
|
-
|
66
|
-
Using the `collection` from above, the `group_by_relation_type` method returns a `Hash`:
|
67
|
-
|
68
|
-
```ruby
|
69
|
-
{
|
70
|
-
preconnect: [
|
71
|
-
#<LinkHeaderParser::LinkHeader target_uri: "https://assets.sixtwothree.org/", relation_types: ["preconnect"]>,
|
72
|
-
#<LinkHeaderParser::LinkHeader target_uri: "https://fonts.googleapis.com/", relation_types: ["preconnect"]>,
|
73
|
-
#<LinkHeaderParser::LinkHeader target_uri: "https://fonts.gstatic.com/", relation_types: ["preconnect"]>
|
74
|
-
],
|
75
|
-
webmention: [
|
76
|
-
#<LinkHeaderParser::LinkHeader target_uri: "https://sixtwothree.org/webmentions", relation_types: ["webmention"]>
|
77
|
-
]
|
78
|
-
}
|
79
|
-
```
|
80
|
-
|
81
|
-
### Working with a `LinkHeader`
|
82
|
-
|
83
|
-
You may interact with one or more `LinkHeader`s in a `LinkHeadersCollection` using the methods outlined below. The naming conventions for these methods draws heavily on the terminology established in [RFC-5988](https://tools.ietf.org/html/rfc5988) and [RFC-8288](https://tools.ietf.org/html/rfc8288).
|
84
|
-
|
85
|
-
#### Link Target ([§ 3.1](https://tools.ietf.org/html/rfc8288#section-3.1))
|
86
|
-
|
87
|
-
```ruby
|
88
|
-
link_header = LinkHeaderParser.parse(%(</index.html>; rel="home"), base: "https://example.com/").first
|
89
|
-
|
90
|
-
link_header.target_string
|
91
|
-
#=> "/index.html"
|
92
|
-
|
93
|
-
link_header.target_uri
|
94
|
-
#=> "https://example.com/index.html"
|
95
|
-
```
|
96
|
-
|
97
|
-
The `target_string` method returns a string of the value between the opening and closing angle brackets at the beginning of the Link header. The `target_uri` method returns a string representing the resolved URL.
|
98
|
-
|
99
|
-
#### Link Context ([§ 3.2](https://tools.ietf.org/html/rfc8288#section-3.2))
|
100
|
-
|
101
|
-
```ruby
|
102
|
-
link_header = LinkHeaderParser.parse(%(</chapters/1>; anchor="#copyright"; rel="license"), base: "https://example.com/").first
|
103
|
-
|
104
|
-
link_header.context_string
|
105
|
-
#=> "#copyright"
|
106
|
-
|
107
|
-
link_header.context_uri
|
108
|
-
#=> "https://example.com/chapters/1#copyright"
|
109
|
-
```
|
110
|
-
|
111
|
-
The `anchor` parameter's value may be a fragment identifier (e.g. `#foo`), a relative URL (e.g. `/foo`), or an absolute URL (e.g. `https://context.example.com`). The `context_string` method returns the `anchor` parameter's value (when present) and defaults to the `target_string` value. The `context_uri` method returns a string representing the resolved URL.
|
112
|
-
|
113
|
-
#### Relation Type ([§ 3.3](https://tools.ietf.org/html/rfc8288#section-3.3))
|
114
|
-
|
115
|
-
```ruby
|
116
|
-
link_header = LinkHeaderParser.parse(%(</chapters/1>; rel="prev start"), base: "https://example.com/").first
|
117
|
-
|
118
|
-
link_header.relations_string
|
119
|
-
#=> "prev start"
|
120
|
-
|
121
|
-
link_header.relation_types
|
122
|
-
#=> ["prev", "start"]
|
123
|
-
```
|
124
|
-
|
125
|
-
#### Link Parameters ([Appendix B.3](https://tools.ietf.org/html/rfc8288#appendix-B.3))
|
126
|
-
|
127
|
-
```ruby
|
128
|
-
link_header = LinkHeaderParser.parse(%(</posts.rss>; rel="alternate"; hreflang="en-US"; title="sixtwothree.org: Posts"; type="application/rss+xml"), base: "https://sixtwothree.org").first
|
129
|
-
|
130
|
-
link_header.link_parameters
|
131
|
-
#=> [#<LinkHeaderParser::LinkHeaderParameter name: "rel", value: "alternate">, #<LinkHeaderParser::LinkHeaderParameter name: "hreflang", value: "en-US">, #<LinkHeaderParser::LinkHeaderParameter name: "title", value: "sixtwothree.org: Posts">, #<LinkHeaderParser::LinkHeaderParameter name: "type", value: "application/rss+xml">]
|
132
|
-
```
|
133
|
-
|
134
|
-
Note that the `Array` returned by the `link_parameters` method may include multiple `LinkHeaderParameter`s with the same name depending on the provided Link header. Certain methods on `LinkHeader` will return values from the first occurrence of a parameter name (e.g. `link_header.relations_string`) in accordance with [RFC-8288](https://tools.ietf.org/html/rfc8288).
|
33
|
+
See [USAGE.md](USAGE.md) for documentation of link-header-parser-ruby's features.
|
135
34
|
|
136
35
|
## Acknowledgments
|
137
36
|
|
data/USAGE.md
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
# Using link-header-parser-ruby
|
2
|
+
|
3
|
+
Before using link-header-parser-ruby, please read the [Getting Started](README.md#getting-started) and [Installation](README.md#installation) sections of the project's [README.md](README.md).
|
4
|
+
|
5
|
+
## Parsing HTTP Link headers
|
6
|
+
|
7
|
+
With link-header-parser-ruby added to your project's `Gemfile` and installed, you may parse a HTTP Link headers from an HTTP response by doing something like:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
require "net/http"
|
11
|
+
require "link-header-parser"
|
12
|
+
|
13
|
+
url = "https://sixtwothree.org"
|
14
|
+
link_headers = Net::HTTP.get_response(URI.parse(url)).get_fields("Link")
|
15
|
+
|
16
|
+
collection = LinkHeaderParser.parse(link_headers, base_uri: url)
|
17
|
+
```
|
18
|
+
|
19
|
+
The `LinkHeaderParser.parse` method accepts two arguments:
|
20
|
+
|
21
|
+
1. an `Array` of strings representing HTTP Link headers (e.g. `['</>; rel="home"', '</chapters/1>; anchor="#copyright"; rel="license"']`), and
|
22
|
+
2. a `String` (or any object that responds to `#to_s`) representing the absolute URL of the resource providing the HTTP Link headers.
|
23
|
+
|
24
|
+
## Working with a `LinkHeaderSet`
|
25
|
+
|
26
|
+
In the example above, `collection` is a `LinkHeaderSet`, an [`Array`](https://docs.ruby-lang.org/en/master/Array.html)-like object whose elements are one or more `LinkHeader` objects. The `LinkHeaderSet` is enhanced with a few custom methods noted below.
|
27
|
+
|
28
|
+
Building on the example `collection` from above:
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
collection.relation_types
|
32
|
+
#=> ["preconnect", "webmention"]
|
33
|
+
|
34
|
+
collection.group_by_relation_type
|
35
|
+
#=>
|
36
|
+
{
|
37
|
+
"preconnect" => [
|
38
|
+
#<LinkHeaderParser::LinkHeader @base_uri=#<Addressable::URI URI:https://sixtwothree.org> @target_string="https://assets.sixtwothree.org/">,
|
39
|
+
#<LinkHeaderParser::LinkHeader @base_uri=#<Addressable::URI URI:https://sixtwothree.org> @target_string="https://fonts.googleapis.com/">,
|
40
|
+
#<LinkHeaderParser::LinkHeader @base_uri=#<Addressable::URI URI:https://sixtwothree.org> @target_string="https://fonts.gstatic.com/">
|
41
|
+
],
|
42
|
+
"webmention" => [
|
43
|
+
#<LinkHeaderParser::LinkHeader @base_uri=#<Addressable::URI URI:https://sixtwothree.org> @target_string="https://sixtwothree.org/webmentions">
|
44
|
+
]
|
45
|
+
}
|
46
|
+
```
|
47
|
+
|
48
|
+
## Working with a `LinkHeader`
|
49
|
+
|
50
|
+
You may also directly parse a `LinkHeader` using `LinkHeaderParser::LinkHeader.parse` as shown in the examples below. The naming conventions for `LinkHeader`'s instance methods draws heavily on the terminology established in [RFC-5988](https://tools.ietf.org/html/rfc5988) and [RFC-8288](https://tools.ietf.org/html/rfc8288).
|
51
|
+
|
52
|
+
### Link Target ([§ 3.1](https://tools.ietf.org/html/rfc8288#section-3.1))
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
link_header = LinkHeaderParser::LinkHeader.parse(%(</index.html>; rel="home"), base_uri: "https://website.example")
|
56
|
+
|
57
|
+
link_header.target_string
|
58
|
+
#=> "/index.html"
|
59
|
+
|
60
|
+
link_header.target_uri
|
61
|
+
#=> "https://website.example/index.html"
|
62
|
+
```
|
63
|
+
|
64
|
+
The `target_string` method returns a string of the value between the opening and closing angle brackets at the beginning of the Link header. The `target_uri` method returns a string representing the resolved URL.
|
65
|
+
|
66
|
+
### Link Context ([§ 3.2](https://tools.ietf.org/html/rfc8288#section-3.2))
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
link_header = LinkHeaderParser::LinkHeader.parse(%(</chapters/1>; anchor="#copyright"; rel="license"), base_uri: "https://website.example")
|
70
|
+
|
71
|
+
link_header.context_string
|
72
|
+
#=> "#copyright"
|
73
|
+
|
74
|
+
link_header.context_uri
|
75
|
+
#=> "https://website.example/chapters/1#copyright"
|
76
|
+
```
|
77
|
+
|
78
|
+
The `anchor` parameter's value may be a fragment identifier (e.g. `#copyright`), a relative URL (e.g. `/copyright`), or an absolute URL (e.g. `https://website.example/copyright`). The `context_string` method returns the `anchor` parameter's value (when present) and defaults to the `target_string` value. The `context_uri` method returns a string representing the resolved URL.
|
79
|
+
|
80
|
+
### Relation Type ([§ 3.3](https://tools.ietf.org/html/rfc8288#section-3.3))
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
link_header = LinkHeaderParser::LinkHeader.parse(%(</chapters/1>; rel="prev start"), base_uri: "https://website.example")
|
84
|
+
|
85
|
+
link_header.relations_string
|
86
|
+
#=> "prev start"
|
87
|
+
|
88
|
+
link_header.relation_types
|
89
|
+
#=> ["prev", "start"]
|
90
|
+
```
|
91
|
+
|
92
|
+
### Target Attributes ([Appendix B.3](https://tools.ietf.org/html/rfc8288#appendix-B.2))
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
link_header = LinkHeaderParser::LinkHeader.parse(%(</posts.rss>; rel="alternate"; hreflang="en-US"; title="Posts"; type="application/rss+xml"), base_uri: "https://website.example")
|
96
|
+
|
97
|
+
link_header.target_attributes
|
98
|
+
#=>
|
99
|
+
[
|
100
|
+
#<LinkHeaderParser::TargetAttribute @name="rel" @value="alternate">,
|
101
|
+
#<LinkHeaderParser::TargetAttribute @name="hreflang" @value="en-US">,
|
102
|
+
#<LinkHeaderParser::TargetAttribute @name="title" @value="Posts">,
|
103
|
+
#<LinkHeaderParser::TargetAttribute @name="type" @value="application/rss+xml">
|
104
|
+
]
|
105
|
+
```
|
106
|
+
|
107
|
+
> [!NOTE]
|
108
|
+
> The `target_attributes` method returns a `TargetAttributeSet`, an `Array`-like object. Its elements, instances of `TargetAttribute`, may include multiple `TargetAttribute`s with the same `name` depending on the provided HTTP Link header. Some methods on `LinkHeader` return values from _only the first occurrence_ of a target attribute's name (e.g. `link_header.relations_string`) in accordance with [RFC-8288](https://tools.ietf.org/html/rfc8288).
|
109
|
+
|
110
|
+
## Working with a `TargetAttributeSet`
|
111
|
+
|
112
|
+
Beyond standard `Array` methods, the `TargetAttributeSet` adds a `find_by_name` method that may be used to locate the first occurrence of a particular target attribute. Building on the example above:
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
link_header.target_attributes.find_by_name("type")
|
116
|
+
#=> #<LinkHeaderParser::TargetAttribute @name="type" @value="application/rss+xml">
|
117
|
+
```
|
118
|
+
|
119
|
+
## Creating HTTP Link headers
|
120
|
+
|
121
|
+
From v7.0.0, link-header-parser-ruby may be used to created HTTP Link headers. It's maybe not the most efficient way to do this, but it's possible:
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
# Create a new `LinkHeader` for the `/feed.rss` target string.
|
125
|
+
link_header = LinkHeaderParser::LinkHeader.new("/feed.rss", base_uri: "https://website.example")
|
126
|
+
|
127
|
+
# Append a target attribute using `Array#<<`.
|
128
|
+
link_header.target_attributes << "rel=alternate"
|
129
|
+
|
130
|
+
# Append multiple target attributes using `Array#append`.
|
131
|
+
link_header.target_attributes.append(%(hreflang="en-US"), LinkHeaderParser::TargetAttribute.new(name: "type", value: "application/rss+xml"))
|
132
|
+
|
133
|
+
# Coerce the `LinkHeader` and its target attributes to a string.
|
134
|
+
link_header.to_s
|
135
|
+
#=> "</feed.rss>; rel=\"alternate\"; hreflang=\"en-US\"; type=\"application/rss+xml\""
|
136
|
+
```
|
137
|
+
|
138
|
+
> [!TIP]
|
139
|
+
> When adding elements to either a `LinkHeaderSet` or `TargetAttributeSet`, you may pass a string or an instance of the related object (`LinkHeader` and `TargetAttribute` respectively). As necessary, link-header-parser will automatically create a new instance of the related object using the provided input before adding the new element.
|
140
|
+
|
141
|
+
## Exception Handling
|
142
|
+
|
143
|
+
link-header-parser-ruby may raise an `Addressable::URI::InvalidURIError` exception if the value passed to the `base_uri` option is an invalid URI (e.g. `https:`).
|
data/lib/link-header-parser.rb
CHANGED
@@ -1,28 +1,31 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
4
|
-
|
3
|
+
require "delegate"
|
4
|
+
|
5
|
+
require "addressable/uri"
|
5
6
|
|
6
7
|
require_relative "link_header_parser/link_header"
|
7
|
-
require_relative "link_header_parser/
|
8
|
-
require_relative "link_header_parser/
|
8
|
+
require_relative "link_header_parser/link_header_set"
|
9
|
+
require_relative "link_header_parser/target_attribute"
|
10
|
+
require_relative "link_header_parser/target_attribute_set"
|
9
11
|
|
10
12
|
module LinkHeaderParser
|
11
13
|
# Parse an array of HTTP Link headers.
|
12
14
|
#
|
13
|
-
# Convenience method for {
|
15
|
+
# Convenience method for {LinkHeaderSet#initialize}.
|
14
16
|
#
|
15
17
|
# @example
|
16
18
|
# require "net/http"
|
17
19
|
#
|
18
|
-
# url = "https://
|
20
|
+
# url = "https://website.example"
|
19
21
|
# link_headers = Net::HTTP.get_response(URI.parse(url)).get_fields("Link")
|
20
22
|
#
|
21
|
-
# LinkHeaderParser.parse(link_headers,
|
23
|
+
# LinkHeaderParser.parse(link_headers, base_uri: url)
|
24
|
+
#
|
25
|
+
# @param (see LinkHeaderSet#initialize)
|
22
26
|
#
|
23
|
-
# @
|
24
|
-
|
25
|
-
|
26
|
-
LinkHeadersCollection.new(*headers, base: base)
|
27
|
+
# @return [LinkHeaderSet]
|
28
|
+
def self.parse(*field_values, base_uri:)
|
29
|
+
LinkHeaderSet.new(*field_values, base_uri: base_uri)
|
27
30
|
end
|
28
31
|
end
|
@@ -8,21 +8,58 @@ module LinkHeaderParser
|
|
8
8
|
PARAMETERS_REGEXP_PATTERN = /(?<!;)\s*[^;]+/.freeze
|
9
9
|
private_constant :PARAMETERS_REGEXP_PATTERN
|
10
10
|
|
11
|
-
#
|
11
|
+
# Parse a +field_value+ string into a {LinkHeader}.
|
12
12
|
#
|
13
|
-
# @
|
14
|
-
|
13
|
+
# @example
|
14
|
+
# url = "https://website.example"
|
15
|
+
# link_header = %(</webmentions>; rel="webmention")
|
16
|
+
#
|
17
|
+
# LinkHeaderParser::LinkHeader.parse(link_header, base_uri: url)
|
18
|
+
#
|
19
|
+
# @param field_value [String, #to_s]
|
20
|
+
# @param base_uri (see #initialize)
|
21
|
+
#
|
22
|
+
# @return [self]
|
23
|
+
def self.parse(field_value, base_uri:)
|
24
|
+
return field_value if field_value.is_a?(self)
|
25
|
+
|
26
|
+
match_data = field_value.to_s.match(FIELD_VALUE_REGEXP_PATTERN)
|
27
|
+
|
28
|
+
new(match_data[:target_string], *match_data[:parameters].scan(PARAMETERS_REGEXP_PATTERN), base_uri: base_uri)
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [Addressable::URI]
|
32
|
+
attr_reader :base_uri
|
33
|
+
|
34
|
+
# The parsed parameters for this Link header extracted from +field_value+.
|
35
|
+
#
|
36
|
+
# @see https://tools.ietf.org/html/rfc8288#appendix-B.2
|
37
|
+
# IETF RFC 8288 Web Linking Appendix B.2.2.4 Parsing a Link Field Value
|
38
|
+
#
|
39
|
+
# @return [TargetAttributeSet]
|
40
|
+
attr_reader :target_attributes
|
15
41
|
|
16
|
-
#
|
42
|
+
# The target URL for this Link header extracted from +field_value+.
|
17
43
|
#
|
18
44
|
# @see https://tools.ietf.org/html/rfc8288#appendix-B.2
|
19
|
-
# IETF RFC 8288 Web Linking Appendix B.2 Parsing a Link Field Value
|
45
|
+
# IETF RFC 8288 Web Linking Appendix B.2.2.4 Parsing a Link Field Value
|
46
|
+
#
|
47
|
+
# @return [String]
|
48
|
+
attr_reader :target_string
|
49
|
+
|
50
|
+
# Create a new {LinkHeader}.
|
20
51
|
#
|
21
|
-
# @
|
22
|
-
#
|
23
|
-
|
24
|
-
|
25
|
-
|
52
|
+
# @see LinkParameter.parse
|
53
|
+
#
|
54
|
+
# @param target_string [String, #to_s]
|
55
|
+
# @param link_parameters [Array<String, #to_s>]
|
56
|
+
# @param base_uri [String, #to_s]
|
57
|
+
#
|
58
|
+
# @raise [Addressable::URI::InvalidURIError]
|
59
|
+
def initialize(target_string, *link_parameters, base_uri:)
|
60
|
+
@target_string = target_string.to_s.strip
|
61
|
+
@target_attributes = TargetAttributeSet.new(*link_parameters)
|
62
|
+
@base_uri = Addressable::URI.parse(base_uri)
|
26
63
|
end
|
27
64
|
|
28
65
|
# The context URL for this Link header extracted from +field_value+ (or
|
@@ -33,7 +70,7 @@ module LinkHeaderParser
|
|
33
70
|
#
|
34
71
|
# @return [String]
|
35
72
|
def context_string
|
36
|
-
|
73
|
+
target_attributes.find_by_name("anchor")&.value || target_string
|
37
74
|
end
|
38
75
|
|
39
76
|
# The resolved context URL for this Link header.
|
@@ -43,38 +80,16 @@ module LinkHeaderParser
|
|
43
80
|
#
|
44
81
|
# @return [String]
|
45
82
|
def context_uri
|
46
|
-
|
83
|
+
Addressable::URI.join(target_uri, context_string).to_s
|
47
84
|
end
|
48
85
|
|
49
86
|
# @return [String]
|
50
87
|
def inspect
|
51
|
-
"
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
# The parsed parameters for this Link header extracted from +field_value+.
|
57
|
-
#
|
58
|
-
# @see https://tools.ietf.org/html/rfc8288#appendix-B.3
|
59
|
-
# IETF RFC 8288 Web Linking Appendix B.3 Parsing Parameters
|
60
|
-
#
|
61
|
-
# @return [Array<LinkHeaderParser::LinkHeaderParameter>]
|
62
|
-
def link_parameters
|
63
|
-
@link_parameters ||= field_value_match_data[:parameters]
|
64
|
-
.scan(PARAMETERS_REGEXP_PATTERN)
|
65
|
-
.map { |parameter| LinkHeaderParameter.new(parameter.strip) }
|
66
|
-
end
|
67
|
-
|
68
|
-
# The +relations_string+ value returned as an +Array+.
|
69
|
-
#
|
70
|
-
# @see LinkHeader#relations_string
|
71
|
-
#
|
72
|
-
# @see https://tools.ietf.org/html/rfc8288#appendix-B.2
|
73
|
-
# IETF RFC 8288 Web Linking Appendix B.2.2.10 and Appendix B.2.2.17.1 Parsing a Link Field Value
|
74
|
-
#
|
75
|
-
# @return [Array<String>]
|
76
|
-
def relation_types
|
77
|
-
@relation_types ||= relations_string.split.map(&:downcase)
|
88
|
+
format "#<%<class>s:%<id>#0x @base_uri=%<base_uri>s @target_string=%<target_string>s>",
|
89
|
+
class: self.class,
|
90
|
+
id: object_id << 1,
|
91
|
+
base_uri: base_uri.inspect,
|
92
|
+
target_string: target_string.inspect
|
78
93
|
end
|
79
94
|
|
80
95
|
# The relation types for this Link header extracted from +field_value+.
|
@@ -84,17 +99,19 @@ module LinkHeaderParser
|
|
84
99
|
#
|
85
100
|
# @return [String]
|
86
101
|
def relations_string
|
87
|
-
|
102
|
+
target_attributes.find_by_name("rel")&.value.to_s
|
88
103
|
end
|
89
104
|
|
90
|
-
# The
|
105
|
+
# The +relations_string+ value returned as an array.
|
106
|
+
#
|
107
|
+
# @see #relations_string
|
91
108
|
#
|
92
109
|
# @see https://tools.ietf.org/html/rfc8288#appendix-B.2
|
93
|
-
# IETF RFC 8288 Web Linking Appendix B.2.2.
|
110
|
+
# IETF RFC 8288 Web Linking Appendix B.2.2.10 and Appendix B.2.2.17.1 Parsing a Link Field Value
|
94
111
|
#
|
95
|
-
# @return [String]
|
96
|
-
def
|
97
|
-
|
112
|
+
# @return [Array<String>]
|
113
|
+
def relation_types
|
114
|
+
relations_string.split.map(&:downcase)
|
98
115
|
end
|
99
116
|
|
100
117
|
# The resolved target URL for this Link header.
|
@@ -104,40 +121,14 @@ module LinkHeaderParser
|
|
104
121
|
#
|
105
122
|
# @return [String]
|
106
123
|
def target_uri
|
107
|
-
@target_uri ||=
|
108
|
-
end
|
109
|
-
|
110
|
-
# Return a +Hash+ representation of this {LinkHeader}.
|
111
|
-
#
|
112
|
-
# @return [Hash{Symbol => String, Array, Hash{Symbol => Array}}]
|
113
|
-
def to_hash
|
114
|
-
{
|
115
|
-
target_string: target_string,
|
116
|
-
target_uri: target_uri,
|
117
|
-
context_string: context_string,
|
118
|
-
context_uri: context_uri,
|
119
|
-
relations_string: relations_string,
|
120
|
-
relation_types: relation_types,
|
121
|
-
link_parameters: grouped_link_parameters,
|
122
|
-
}
|
124
|
+
@target_uri ||= base_uri.join(target_string).to_s
|
123
125
|
end
|
124
126
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
attr_reader :base
|
130
|
-
|
131
|
-
def field_value_match_data
|
132
|
-
@field_value_match_data ||= field_value.match(FIELD_VALUE_REGEXP_PATTERN)
|
127
|
+
# @return [String]
|
128
|
+
def to_str
|
129
|
+
"<#{target_string}>; #{target_attributes}"
|
133
130
|
end
|
134
131
|
|
135
|
-
|
136
|
-
@grouped_link_parameters ||= link_parameters
|
137
|
-
.map(&:to_a)
|
138
|
-
.group_by(&:shift)
|
139
|
-
.transform_keys(&:to_sym)
|
140
|
-
.transform_values(&:flatten)
|
141
|
-
end
|
132
|
+
alias to_s to_str
|
142
133
|
end
|
143
134
|
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LinkHeaderParser
|
4
|
+
class LinkHeaderSet < SimpleDelegator
|
5
|
+
# Create a new {LinkHeaderSet} from an array of HTTP Link header
|
6
|
+
# +field_values+.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# require "net/http"
|
10
|
+
#
|
11
|
+
# url = "https://website.example"
|
12
|
+
# link_headers = Net::HTTP.get_response(URI.parse(url)).get_fields("Link")
|
13
|
+
#
|
14
|
+
# LinkHeaderParser::LinkHeaderSet.new(link_headers, base_uri: url)
|
15
|
+
#
|
16
|
+
# @param field_values [Array<String>]
|
17
|
+
# @param base_uri (see LinkHeader.parse)
|
18
|
+
def initialize(*field_values, base_uri:)
|
19
|
+
@base_uri = base_uri
|
20
|
+
|
21
|
+
super(parsed_link_headers_from(field_values))
|
22
|
+
end
|
23
|
+
|
24
|
+
# Append an HTTP Link header to this {LinkHeaderSet}.
|
25
|
+
#
|
26
|
+
# @param field_value [String]
|
27
|
+
#
|
28
|
+
# @return [self]
|
29
|
+
def <<(field_value)
|
30
|
+
super(*parsed_link_headers_from(field_value))
|
31
|
+
end
|
32
|
+
|
33
|
+
# Append one or more HTTP Link headers to this {LinkHeaderSet}.
|
34
|
+
#
|
35
|
+
# @param field_values [Array<String>]
|
36
|
+
#
|
37
|
+
# @return [self]
|
38
|
+
def append(*field_values)
|
39
|
+
super(*parsed_link_headers_from(field_values))
|
40
|
+
end
|
41
|
+
|
42
|
+
# Obtain a hash of this {LinkHeaderSet}'s {LinkHeader}s grouped by their
|
43
|
+
# relation types.
|
44
|
+
#
|
45
|
+
# @return [Hash{String => LinkHeaderSet<LinkHeader>}]
|
46
|
+
def group_by_relation_type
|
47
|
+
obj = relation_types.to_h { |relation_type| [relation_type, LinkHeaderSet.new(base_uri: base_uri)] }
|
48
|
+
|
49
|
+
each_with_object(obj) do |link_header, hash|
|
50
|
+
link_header.relation_types.each { |relation_type| hash[relation_type] << link_header }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# @return [String]
|
55
|
+
def inspect
|
56
|
+
format "#<%<class>s:%<id>#0x @base_uri=%<base_uri>s>",
|
57
|
+
class: self.class,
|
58
|
+
id: object_id << 1,
|
59
|
+
base_uri: base_uri.inspect
|
60
|
+
end
|
61
|
+
|
62
|
+
# Retrieve a unique sorted array of this collection's {LinkHeader}
|
63
|
+
# relation types.
|
64
|
+
#
|
65
|
+
# @return [Array<String>]
|
66
|
+
def relation_types
|
67
|
+
Set.new(flat_map(&:relation_types)).to_a.sort
|
68
|
+
end
|
69
|
+
|
70
|
+
# @return [String]
|
71
|
+
def to_str
|
72
|
+
join(", ")
|
73
|
+
end
|
74
|
+
|
75
|
+
alias to_s to_str
|
76
|
+
|
77
|
+
# Prepend one or more HTTP Link headers to this {LinkHeaderSet}.
|
78
|
+
#
|
79
|
+
# @param field_values [Array<String>]
|
80
|
+
#
|
81
|
+
# @return [self]
|
82
|
+
def unshift(*field_values)
|
83
|
+
super(*parsed_link_headers_from(field_values))
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
attr_reader :base_uri
|
89
|
+
|
90
|
+
# @return [self]
|
91
|
+
def parsed_link_headers_from(*field_values)
|
92
|
+
field_values.flatten!
|
93
|
+
field_values.map { |field_value| LinkHeader.parse(field_value, base_uri: base_uri) }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LinkHeaderParser
|
4
|
+
class TargetAttribute
|
5
|
+
# @see https://tools.ietf.org/html/rfc8288#appendix-B.3
|
6
|
+
# IETF RFC 8288 Web Linking Appendix B.3 Parsing Parameters
|
7
|
+
# @see https://tools.ietf.org/html/rfc8288#appendix-B.4
|
8
|
+
# IETF RFC 8288 Web Linking Appendix B.4 Parsing a Quoted String
|
9
|
+
PARAMETER_REGEXP_PATTERN = /^(?<name>.+?)(?:=\s*"?(?<value>.*?)"?)?$/.freeze
|
10
|
+
private_constant :PARAMETER_REGEXP_PATTERN
|
11
|
+
|
12
|
+
# Parse a +link_parameter+ string into a {TargetAttribute}.
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# LinkHeaderParser::TargetAttribute.parse(%(rel="webmention"))
|
16
|
+
#
|
17
|
+
# @param link_parameter [String, #to_s]
|
18
|
+
#
|
19
|
+
# @return [self]
|
20
|
+
def self.parse(link_parameter)
|
21
|
+
return link_parameter if link_parameter.is_a?(self)
|
22
|
+
|
23
|
+
new(**link_parameter.to_s.match(PARAMETER_REGEXP_PATTERN).named_captures.transform_keys(&:to_sym))
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [String]
|
27
|
+
attr_reader :name
|
28
|
+
|
29
|
+
# @return [String]
|
30
|
+
attr_reader :value
|
31
|
+
|
32
|
+
# Create a new {TargetAttribute}.
|
33
|
+
#
|
34
|
+
# @see https://tools.ietf.org/html/rfc8288#appendix-B.2
|
35
|
+
# IETF RFC 8288 Web Linking Appendix B.2.2.4 Parsing a Link Field Value
|
36
|
+
#
|
37
|
+
# @param name [String, #to_s]
|
38
|
+
# @param value [String, #to_s]
|
39
|
+
def initialize(name:, value:)
|
40
|
+
@name = name.to_s.strip.downcase
|
41
|
+
@value = value.to_s.strip
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return [String]
|
45
|
+
def inspect
|
46
|
+
format "#<%<class>s:%<id>#0x @name=%<name>s @value=%<value>s>",
|
47
|
+
class: self.class,
|
48
|
+
id: object_id << 1,
|
49
|
+
name: name.inspect,
|
50
|
+
value: value.inspect
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [String]
|
54
|
+
def to_str
|
55
|
+
%(#{name}="#{value}")
|
56
|
+
end
|
57
|
+
|
58
|
+
alias to_s to_str
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LinkHeaderParser
|
4
|
+
class TargetAttributeSet < SimpleDelegator
|
5
|
+
# Create a new {TargetAttributeSet} from an array of HTTP Link header
|
6
|
+
# +link_parameters+.
|
7
|
+
#
|
8
|
+
# @param link_parameters [Array<String>]
|
9
|
+
def initialize(*link_parameters)
|
10
|
+
super(parsed_target_attributes_from(link_parameters))
|
11
|
+
end
|
12
|
+
|
13
|
+
# Append an HTTP Link header parameter to this {TargetAttributeSet}.
|
14
|
+
#
|
15
|
+
# @param parameter_value [String]
|
16
|
+
#
|
17
|
+
# @return [self]
|
18
|
+
def <<(parameter_value)
|
19
|
+
super(*parsed_target_attributes_from(parameter_value))
|
20
|
+
end
|
21
|
+
|
22
|
+
# Append one or more HTTP Link header parameters to this
|
23
|
+
# {TargetAttributeSet}.
|
24
|
+
#
|
25
|
+
# @param link_parameters [Array<String>]
|
26
|
+
#
|
27
|
+
# @return [self]
|
28
|
+
def append(*link_parameters)
|
29
|
+
super(*parsed_target_attributes_from(link_parameters))
|
30
|
+
end
|
31
|
+
|
32
|
+
# Find and retrieve a {TargetAttribute} from this {TargetAttributeSet} by
|
33
|
+
# its +#name+.
|
34
|
+
#
|
35
|
+
# @return [TargetAttribute, nil]
|
36
|
+
def find_by_name(name)
|
37
|
+
find { |target_attribute| target_attribute.name == name }
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [String]
|
41
|
+
def inspect
|
42
|
+
format "#<%<class>s:%<id>#0x>",
|
43
|
+
class: self.class,
|
44
|
+
id: object_id << 1
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [String]
|
48
|
+
def to_str
|
49
|
+
join("; ")
|
50
|
+
end
|
51
|
+
|
52
|
+
alias to_s to_str
|
53
|
+
|
54
|
+
# Prepend one or more HTTP Link header parameter to this
|
55
|
+
# {TargetAttributeSet}.
|
56
|
+
#
|
57
|
+
# @param link_parameters [Array<String>]
|
58
|
+
#
|
59
|
+
# @return [self]
|
60
|
+
def unshift(*link_parameters)
|
61
|
+
super(*parsed_target_attributes_from(link_parameters))
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
# @return [self]
|
67
|
+
def parsed_target_attributes_from(*link_parameters)
|
68
|
+
link_parameters.flatten!
|
69
|
+
link_parameters.map { |parameter_value| TargetAttribute.parse(parameter_value) }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/link-header-parser.gemspec
CHANGED
@@ -4,7 +4,7 @@ Gem::Specification.new do |spec|
|
|
4
4
|
spec.required_ruby_version = ">= 2.6"
|
5
5
|
|
6
6
|
spec.name = "link-header-parser"
|
7
|
-
spec.version = "
|
7
|
+
spec.version = "7.0.0"
|
8
8
|
spec.authors = ["Jason Garber"]
|
9
9
|
spec.email = ["jason@sixtwothree.org"]
|
10
10
|
|
@@ -14,7 +14,7 @@ Gem::Specification.new do |spec|
|
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
16
|
spec.files = Dir["lib/**/*"].reject { |f| File.directory?(f) }
|
17
|
-
spec.files += ["LICENSE", "CHANGELOG.md", "README.md"]
|
17
|
+
spec.files += ["LICENSE", "CHANGELOG.md", "README.md", "USAGE.md"]
|
18
18
|
spec.files += ["link-header-parser.gemspec"]
|
19
19
|
|
20
20
|
spec.require_paths = ["lib"]
|
@@ -27,4 +27,6 @@ Gem::Specification.new do |spec|
|
|
27
27
|
"rubygems_mfa_required" => "true",
|
28
28
|
"source_code_uri" => "#{spec.homepage}/src/tag/v#{spec.version}",
|
29
29
|
}
|
30
|
+
|
31
|
+
spec.add_dependency "addressable", "~> 2.8"
|
30
32
|
end
|
metadata
CHANGED
@@ -1,14 +1,28 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: link-header-parser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 7.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jason Garber
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
11
|
-
dependencies:
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: addressable
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '2.8'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - "~>"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '2.8'
|
12
26
|
description: Parse HTTP Link headers.
|
13
27
|
email:
|
14
28
|
- jason@sixtwothree.org
|
@@ -19,21 +33,23 @@ files:
|
|
19
33
|
- CHANGELOG.md
|
20
34
|
- LICENSE
|
21
35
|
- README.md
|
36
|
+
- USAGE.md
|
22
37
|
- lib/link-header-parser.rb
|
23
38
|
- lib/link_header_parser/link_header.rb
|
24
|
-
- lib/link_header_parser/
|
25
|
-
- lib/link_header_parser/
|
39
|
+
- lib/link_header_parser/link_header_set.rb
|
40
|
+
- lib/link_header_parser/target_attribute.rb
|
41
|
+
- lib/link_header_parser/target_attribute_set.rb
|
26
42
|
- link-header-parser.gemspec
|
27
43
|
homepage: https://codeberg.org/jgarber/link-header-parser-ruby
|
28
44
|
licenses:
|
29
45
|
- MIT
|
30
46
|
metadata:
|
31
47
|
bug_tracker_uri: https://codeberg.org/jgarber/link-header-parser-ruby/issues
|
32
|
-
changelog_uri: https://codeberg.org/jgarber/link-header-parser-ruby/releases/tag/
|
33
|
-
documentation_uri: https://rubydoc.info/gems/link-header-parser/
|
48
|
+
changelog_uri: https://codeberg.org/jgarber/link-header-parser-ruby/releases/tag/v7.0.0
|
49
|
+
documentation_uri: https://rubydoc.info/gems/link-header-parser/7.0.0
|
34
50
|
homepage_uri: https://codeberg.org/jgarber/link-header-parser-ruby
|
35
51
|
rubygems_mfa_required: 'true'
|
36
|
-
source_code_uri: https://codeberg.org/jgarber/link-header-parser-ruby/src/tag/
|
52
|
+
source_code_uri: https://codeberg.org/jgarber/link-header-parser-ruby/src/tag/v7.0.0
|
37
53
|
rdoc_options: []
|
38
54
|
require_paths:
|
39
55
|
- lib
|
@@ -1,58 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module LinkHeaderParser
|
4
|
-
class LinkHeaderParameter
|
5
|
-
PARAMETER_REGEXP_PATTERN = /^(?<name>.+?)(?:="?(?<value>.*?)"?)?$/.freeze
|
6
|
-
private_constant :PARAMETER_REGEXP_PATTERN
|
7
|
-
|
8
|
-
# The +String+ value used to create this {LinkHeaderParameter}.
|
9
|
-
#
|
10
|
-
# @return [String]
|
11
|
-
attr_reader :parameter
|
12
|
-
|
13
|
-
# Create a new parsed Link header parameter.
|
14
|
-
#
|
15
|
-
# @param parameter [String, #to_str]
|
16
|
-
def initialize(parameter)
|
17
|
-
@parameter = parameter.to_str
|
18
|
-
end
|
19
|
-
|
20
|
-
# @return [String]
|
21
|
-
def inspect
|
22
|
-
"#<#{self.class.name}:#{format("%#0x", object_id)} " \
|
23
|
-
"name: #{name.inspect}, " \
|
24
|
-
"value: #{value.inspect}>"
|
25
|
-
end
|
26
|
-
|
27
|
-
# @see https://tools.ietf.org/html/rfc8288#appendix-B.3
|
28
|
-
# IETF RFC 8288 Web Linking Appendix B.3.2.9 Parsing Parameters
|
29
|
-
#
|
30
|
-
# @return [String]
|
31
|
-
def name
|
32
|
-
@name ||= parameter_match_data[:name].downcase
|
33
|
-
end
|
34
|
-
|
35
|
-
# @see https://tools.ietf.org/html/rfc8288#appendix-B.3
|
36
|
-
# IETF RFC 8288 Web Linking Appendix B.3.2.8 Parsing Parameters
|
37
|
-
#
|
38
|
-
# @return [String]
|
39
|
-
def value
|
40
|
-
@value ||= parameter_match_data[:value].to_s
|
41
|
-
end
|
42
|
-
|
43
|
-
# Return an +Array+ representation of this {LinkHeaderParameter}.
|
44
|
-
#
|
45
|
-
# @return [Array<String>]
|
46
|
-
def to_ary
|
47
|
-
[name, value]
|
48
|
-
end
|
49
|
-
|
50
|
-
alias to_a to_ary
|
51
|
-
|
52
|
-
private
|
53
|
-
|
54
|
-
def parameter_match_data
|
55
|
-
@parameter_match_data ||= parameter.match(PARAMETER_REGEXP_PATTERN)
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
@@ -1,86 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module LinkHeaderParser
|
4
|
-
class LinkHeadersCollection
|
5
|
-
extend Forwardable
|
6
|
-
|
7
|
-
include Enumerable
|
8
|
-
|
9
|
-
def_delegators :members, :[], :<<, :each, :last, :length, :push
|
10
|
-
|
11
|
-
# The +Array+ of HTTP Link headers used to create this
|
12
|
-
# {LinkHeadersCollection}.
|
13
|
-
#
|
14
|
-
# @return [Array<String>]
|
15
|
-
attr_reader :headers
|
16
|
-
|
17
|
-
# Parse an array of HTTP Link headers.
|
18
|
-
#
|
19
|
-
# @param headers [Array<String, #to_str>]
|
20
|
-
# @param base [String, #to_str]
|
21
|
-
def initialize(*headers, base:)
|
22
|
-
headers = headers.to_ary
|
23
|
-
|
24
|
-
headers.flatten!
|
25
|
-
|
26
|
-
@headers = headers.map!(&:to_str)
|
27
|
-
@base = base.to_str
|
28
|
-
|
29
|
-
push(*distinct_link_headers)
|
30
|
-
end
|
31
|
-
|
32
|
-
# Retrieve a +Hash+ of this collection's {LinkHeader}s grouped by their
|
33
|
-
# relation type(s).
|
34
|
-
#
|
35
|
-
# @return [Hash{Symbol => Array<LinkHeaderParser::LinkHeader>}]
|
36
|
-
def group_by_relation_type
|
37
|
-
relation_types.to_h do |relation_type|
|
38
|
-
[relation_type.to_sym, select_by_relation_type(relation_type)]
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
# @return [String]
|
43
|
-
def inspect
|
44
|
-
"#<#{self.class.name}:#{format("%#0x", object_id)} " \
|
45
|
-
"headers: #{headers.inspect}, " \
|
46
|
-
"relation_types: #{relation_types.inspect}>"
|
47
|
-
end
|
48
|
-
|
49
|
-
# Retrieve a unique sorted +Array+ of this collection's {LinkHeader}
|
50
|
-
# relation types.
|
51
|
-
#
|
52
|
-
# @return [Array<String>]
|
53
|
-
def relation_types
|
54
|
-
@relation_types ||= Set.new(flat_map(&:relation_types)).to_a.sort
|
55
|
-
end
|
56
|
-
|
57
|
-
# Return an +Array+ representation of this {LinkHeadersCollection}.
|
58
|
-
#
|
59
|
-
# @see LinkHeader#to_hash
|
60
|
-
#
|
61
|
-
# @return [Array<Hash>}>]
|
62
|
-
def to_ary
|
63
|
-
map(&:to_hash)
|
64
|
-
end
|
65
|
-
|
66
|
-
alias to_a to_ary
|
67
|
-
|
68
|
-
private
|
69
|
-
|
70
|
-
attr_reader :base
|
71
|
-
|
72
|
-
def distinct_link_headers
|
73
|
-
headers
|
74
|
-
.flat_map { |header| header.split(/,(?=[\s|<])/) }
|
75
|
-
.map { |header| LinkHeader.new(header.strip, base: base) }
|
76
|
-
end
|
77
|
-
|
78
|
-
def members
|
79
|
-
@members ||= []
|
80
|
-
end
|
81
|
-
|
82
|
-
def select_by_relation_type(relation_type)
|
83
|
-
select { |member| member.relation_types.include?(relation_type) }
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|