link-header-parser 0.3.0 → 3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 187d1cdf4fa37bf3e55485c6a11d47d9404365f2feee404b620fad075705797e
4
- data.tar.gz: 2ddf7984a6a38a812db8b1919fb584cba18a7a05a6838158e3ddc7720af898d2
3
+ metadata.gz: 6b2127eef719541b0ed02c6cb37c7271ed1c1bac8358f5808fb50c08d5ccada3
4
+ data.tar.gz: 999fdc8ea6ad395fe72e88094ed4e924063844600b25df4df5822b2e85b6c2aa
5
5
  SHA512:
6
- metadata.gz: fc4f9ba8a55df2e8bc85e442fcb68f6867bec872bef9db167ee4bb9c319a98de5bc5070d5f2fb1c3a2e7ea70d16c399fb70302655a62bc1e7d121bd812c5ce66
7
- data.tar.gz: 154ec370bd83b135bd42c3c0925863300057308b4338b219697035d784b025f07f1654a43f89452491efc6c840dcc7165dc457a3882381c4e4159875801640be
6
+ metadata.gz: 90a5edd9b4ea7802818d84d858a365f365c6aee0cdcb9ec453feb5620ef7c6b2957da0063a8eaa2847318b69be5e668874a1bf4dc5b01cb36ff75cfd6190a33d
7
+ data.tar.gz: 676c81a374430b522d62d2cc569e149e4c18d5eead47db851cadc1fe509ff787d02b3ecdb823a1e70f8244a6f4d3fb8b122a18ca2dd4ea697eff6faa8c9e22a5
data/CHANGELOG.md CHANGED
@@ -1,12 +1,43 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.0.0 / 2021-05-22
4
+
5
+ - Add implicit type conversion methods, alias explicit methods, and update specs (d132535)
6
+ - Remove exceptions module (249d77c)
7
+ - Use implicit conversions instead of type checking in methods (628c01b)
8
+ - Replace Absolutely dependency with Addressable (beb5188)
9
+ - Update project Ruby version to 2.5.9 (4a3941f)
10
+
11
+ ## 2.2.0 / 2021-04-02
12
+
13
+ - Expand supported Ruby versions to include 3.0 (7187878)
14
+
15
+ ## 2.1.0 / 2020-10-07
16
+
17
+ - Update Absolutely dependency to 5.0 (62988c0)
18
+
19
+ ## 2.0.0 / 2020-05-21
20
+
21
+ - **Breaking changes:** Rewrite gem code (5351010)
22
+ - `LinkHeaderParser.parse` returns `LinkHeadersCollection`
23
+ - New classes: `LinkHeadersCollection`, `LinkHeader`, and `LinkHeaderParameter`
24
+ - Renamed collection's `by_relation_type` method to `group_by_relation_type`
25
+ - **Breaking change:** Update project Ruby version to 2.5.8 and minimum Ruby version to 2.5 (05b2e82)
26
+ - Update inline documentation and refactor `ParsedHeader` and `ParsedHeaderCollection` classes (31ec43e)
27
+
28
+ ## 1.0.0 / 2020-05-14
29
+
30
+ - Update Absolutely dependency to 4.0 (4b78347)
31
+ - Update development Ruby version to 2.4.10 (8d26096)
32
+ - Move development dependencies to `Gemfile` (3f6b5dd)
33
+
3
34
  ## 0.3.0 / 2020-01-20
4
35
 
5
- - Expand supported Ruby versions to include 2.7 ([c55624a](https://github.com/jgarber623/link-header-parser-ruby/commit/c55624a)).
36
+ - Expand supported Ruby versions to include 2.7 (c55624a)
6
37
 
7
38
  ## 0.2.0 / 2019-07-02
8
39
 
9
- - Add support for the `anchor` parameter to `ParsedHeader` exposed via `context` and `context_uri` methods ([d2dff52](https://github.com/jgarber623/link-header-parser-ruby/commit/d2dff52)).
40
+ - Add support for the `anchor` parameter to `ParsedHeader` exposed via `context` and `context_uri` methods (d2dff52)
10
41
 
11
42
  ## 0.1.0 / 2019-06-06
12
43
 
data/CONTRIBUTING.md CHANGED
@@ -8,9 +8,9 @@ There are a couple ways you can help improve link-header-parser-ruby:
8
8
 
9
9
  ## Getting Started
10
10
 
11
- link-header-parser-ruby is developed using Ruby 2.4.9 and is additionally tested against Ruby 2.5, 2.6, and 2.7 using [Travis CI](https://travis-ci.com/jgarber623/link-header-parser-ruby).
11
+ link-header-parser-ruby is developed using Ruby 2.5.9 and is additionally tested against Ruby 2.6, 2.7, and 3.0 using [CircleCI](https://app.circleci.com/pipelines/github/jgarber623/link-header-parser-ruby).
12
12
 
13
- Before making changes to link-header-parser-ruby, you'll want to install Ruby 2.4.9. It's recommended that you use 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). Once you've installed Ruby 2.4.9 using your method of choice, install the project's gems by running:
13
+ Before making changes to link-header-parser-ruby, you'll want to install Ruby 2.5.9. It's recommended that you use 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). Once you've installed Ruby 2.5.9 using your method of choice, install the project's gems by running:
14
14
 
15
15
  ```sh
16
16
  bundle install
@@ -22,7 +22,7 @@ bundle install
22
22
  1. Install development dependencies as outlined above.
23
23
  1. Create a feature branch for the code changes you're looking to make: `git checkout -b my-new-feature`.
24
24
  1. _Write some code!_
25
- 1. If your changes would benefit from testing, add the necessary tests and verify everything passes by running `bundle exec rspec`.
25
+ 1. If your changes would benefit from testing, add the necessary tests and verify everything passes by running `bin/ci`.
26
26
  1. Commit your changes: `git commit -am 'Add some new feature or fix some issue'`. _(See [this excellent article](https://chris.beams.io/posts/git-commit/) for tips on writing useful Git commit messages.)_
27
27
  1. Push the branch to your fork: `git push -u origin my-new-feature`.
28
28
  1. Create a new [pull request][pulls] and we'll review your changes.
data/README.md CHANGED
@@ -2,18 +2,17 @@
2
2
 
3
3
  **A Ruby gem for parsing HTTP Link headers.**
4
4
 
5
- [![Gem](https://img.shields.io/gem/v/link-header-parser.svg?style=for-the-badge)](https://rubygems.org/gems/link-header-parser)
6
- [![Downloads](https://img.shields.io/gem/dt/link-header-parser.svg?style=for-the-badge)](https://rubygems.org/gems/link-header-parser)
7
- [![Build](https://img.shields.io/travis/com/jgarber623/link-header-parser-ruby/master.svg?style=for-the-badge)](https://travis-ci.com/jgarber623/link-header-parser-ruby)
8
- [![Dependencies](https://img.shields.io/depfu/jgarber623/link-header-parser-ruby.svg?style=for-the-badge)](https://depfu.com/github/jgarber623/link-header-parser-ruby)
9
- [![Maintainability](https://img.shields.io/codeclimate/maintainability/jgarber623/link-header-parser-ruby.svg?style=for-the-badge)](https://codeclimate.com/github/jgarber623/link-header-parser-ruby)
10
- [![Coverage](https://img.shields.io/codeclimate/c/jgarber623/link-header-parser-ruby.svg?style=for-the-badge)](https://codeclimate.com/github/jgarber623/link-header-parser-ruby/code)
5
+ [![Gem](https://img.shields.io/gem/v/link-header-parser.svg?logo=rubygems&style=for-the-badge)](https://rubygems.org/gems/link-header-parser)
6
+ [![Downloads](https://img.shields.io/gem/dt/link-header-parser.svg?logo=rubygems&style=for-the-badge)](https://rubygems.org/gems/link-header-parser)
7
+ [![Build](https://img.shields.io/circleci/build/github/jgarber623/link-header-parser-ruby?logo=circleci&style=for-the-badge)](https://app.circleci.com/pipelines/github/jgarber623/link-header-parser-ruby)
8
+ [![Maintainability](https://img.shields.io/codeclimate/maintainability/jgarber623/link-header-parser-ruby.svg?logo=code-climate&style=for-the-badge)](https://codeclimate.com/github/jgarber623/link-header-parser-ruby)
9
+ [![Coverage](https://img.shields.io/codeclimate/c/jgarber623/link-header-parser-ruby.svg?logo=code-climate&style=for-the-badge)](https://codeclimate.com/github/jgarber623/link-header-parser-ruby/code)
11
10
 
12
11
  ## Getting Started
13
12
 
14
- Before installing and using link-header-parser-ruby, you'll want to have [Ruby](https://www.ruby-lang.org) 2.4 (or newer) installed. It's recommended that you use 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).
13
+ Before installing and using link-header-parser-ruby, you'll want to have [Ruby](https://www.ruby-lang.org) 2.5 (or newer) installed. It's recommended that you use 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).
15
14
 
16
- link-header-parser-ruby is developed using Ruby 2.4.9 and is additionally tested against Ruby 2.5, 2.6, and 2.7 using [Travis CI](https://travis-ci.com/jgarber623/link-header-parser-ruby).
15
+ link-header-parser-ruby is developed using Ruby 2.5.9 and is additionally tested against Ruby 2.6, 2.7, and 3.0 using [CircleCI](https://app.circleci.com/pipelines/github/jgarber623/link-header-parser-ruby).
17
16
 
18
17
  ## Installation
19
18
 
@@ -43,90 +42,109 @@ response = HTTP.get('https://sixtwothree.org')
43
42
 
44
43
  link_headers = response.headers.get('link')
45
44
 
46
- collection = LinkHeaderParser.parse(link_headers, base: response.uri.to_s)
45
+ collection = LinkHeaderParser.parse(link_headers, base: response.uri)
47
46
  ```
48
47
 
49
48
  The `parse` method accepts two arguments:
50
49
 
51
50
  1. an `Array` of strings representing HTTP Link headers (e.g. `['</>; rel="home"', '</chapters/1>; anchor="#copyright"; rel="license"']`)
52
- 1. a `String` representing the absolute URL of the resource providing the HTTP Link headers
51
+ 1. a `String` (or any `String`-like object) representing the absolute URL of the resource providing the HTTP Link headers
53
52
 
54
- In the example above, `collection` is an instance of `ParsedHeaderCollection` 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`.
53
+ 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`.
55
54
 
56
55
  For example, you could retrieve an array of `target_uri`s:
57
56
 
58
57
  ```ruby
59
- puts collection.map(&:target_uri) # => ["https://assets.sixtwothree.org/", "https://fonts.googleapis.com/", "https://fonts.gstatic.com/", "https://sixtwothree.org/webmentions"]
58
+ puts collection.map(&:target_uri)
59
+ #=> ["https://assets.sixtwothree.org/", "https://fonts.googleapis.com/", "https://fonts.gstatic.com/", "https://sixtwothree.org/webmentions"]
60
60
  ```
61
61
 
62
- ### Working with a `ParsedHeaderCollection`
62
+ ### Working with a `LinkHeadersCollection`
63
63
 
64
- In addition to the included `Enumerable` methods, the following methods may be used to interact with a `ParsedHeaderCollection`:
64
+ In addition to the included `Enumerable` methods, the following methods may be used to interact with a `LinkHeadersCollection`:
65
65
 
66
66
  #### The `relation_types` Method
67
67
 
68
68
  ```ruby
69
- puts collection.relation_types # => ["preconnect", "webmention"]
69
+ puts collection.relation_types
70
+ #=> ["preconnect", "webmention"]
70
71
  ```
71
72
 
72
- #### The `by_relation_type` Method
73
+ #### The `group_by_relation_type` Method
73
74
 
74
- Using the `collection` from above, the `by_relation_type` method returns an `OpenStruct` with the following attributes:
75
+ Using the `collection` from above, the `group_by_relation_type` method returns a `Hash`:
75
76
 
76
77
  ```ruby
77
78
  {
78
79
  preconnect: [
79
- #<LinkHeaderParser::ParsedHeader @header="<https://assets.sixtwothree.org/>; rel=\"preconnect\"">,
80
- #<LinkHeaderParser::ParsedHeader @header="<https://fonts.googleapis.com/>; rel=\"preconnect\"">,
81
- #<LinkHeaderParser::ParsedHeader @header="<https://fonts.gstatic.com/>; rel=\"preconnect\"">
80
+ #<LinkHeaderParser::LinkHeader target_uri: "https://assets.sixtwothree.org/", relation_types: ["preconnect"]>,
81
+ #<LinkHeaderParser::LinkHeader target_uri: "https://fonts.googleapis.com/", relation_types: ["preconnect"]>,
82
+ #<LinkHeaderParser::LinkHeader target_uri: "https://fonts.gstatic.com/", relation_types: ["preconnect"]>
82
83
  ],
83
84
  webmention: [
84
- #<LinkHeaderParser::ParsedHeader @header="<https://sixtwothree.org/webmentions>; rel=\"webmention\"">
85
+ #<LinkHeaderParser::LinkHeader target_uri: "https://sixtwothree.org/webmentions", relation_types: ["webmention"]>
85
86
  ]
86
87
  }
87
88
  ```
88
89
 
89
- ### Working with a `ParsedHeader`
90
+ ### Working with a `LinkHeader`
90
91
 
91
- You may interact with one or more `ParsedHeader`s in a `ParsedHeaderCollection` 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).
92
+ 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).
92
93
 
93
94
  #### Link Target ([§ 3.1](https://tools.ietf.org/html/rfc8288#section-3.1))
94
95
 
95
96
  ```ruby
96
- link_headers = ['</index.html>; rel="home"']
97
- parsed_header = LinkHeaderParser.parse(link_headers, base: 'https://example.com/').first
97
+ link_header = LinkHeaderParser.parse('</index.html>; rel="home"', base: 'https://example.com/').first
98
98
 
99
- parsed_header.target # => '/index.html'
100
- parsed_header.target_uri # => 'https://example.com/index.html'
99
+ link_header.target_string
100
+ #=> "/index.html"
101
+
102
+ link_header.target_uri
103
+ #=> "https://example.com/index.html"
101
104
  ```
102
105
 
103
- The `target` 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.
106
+ 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.
104
107
 
105
108
  #### Link Context ([§ 3.2](https://tools.ietf.org/html/rfc8288#section-3.2))
106
109
 
107
110
  ```ruby
108
- link_headers = ['<https://example.com/chapters/1>; anchor="#copyright"; rel="license"']
109
- parsed_header = LinkHeaderParser.parse(link_headers, base: 'https://example.com/').first
111
+ link_header = LinkHeaderParser.parse('</chapters/1>; anchor="#copyright"; rel="license"', base: 'https://example.com/').first
112
+
113
+ link_header.context_string
114
+ #=> "#copyright"
110
115
 
111
- parsed_header.context # => '#copyright'
112
- parsed_header.context_uri # => 'https://example.com/chapters/1#copyright'
116
+ link_header.context_uri
117
+ #=> "https://example.com/chapters/1#copyright"
113
118
  ```
114
119
 
115
- 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` method returns the `anchor` parameter's value (when present) and defaults to the `target` value.
120
+ 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.
116
121
 
117
122
  #### Relation Type ([§ 3.3](https://tools.ietf.org/html/rfc8288#section-3.3))
118
123
 
119
124
  ```ruby
120
- link_headers = ['<https://example.com/chapters/1>; rel="prev start"']
121
- parsed_header = LinkHeaderParser.parse(link_headers, base: 'https://example.com/').first
125
+ link_header = LinkHeaderParser.parse('</chapters/1>; rel="prev start"', base: 'https://example.com/').first
126
+
127
+ link_header.relations_string
128
+ #=> "prev start"
122
129
 
123
- parsed_header.relations # => 'prev start'
124
- parsed_header.relation_types # => ['prev', 'start']
130
+ link_header.relation_types
131
+ #=> ["prev", "start"]
125
132
  ```
126
133
 
134
+ #### Link Parameters ([Appendix B.3](https://tools.ietf.org/html/rfc8288#appendix-B.3))
135
+
136
+ ```ruby
137
+ link_header = LinkHeaderParser.parse('</posts.rss>; rel="alternate"; hreflang="en-US"; title="sixtwothree.org: Posts"; type="application/rss+xml"', base: 'https://sixtwothree.org').first
138
+
139
+ link_header.link_parameters
140
+ #=> [#<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">]
141
+ ```
142
+
143
+ 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).
144
+
127
145
  ## Contributing
128
146
 
129
- Interested in helping improve link-header-parser-ruby? Awesome! Your help is greatly appreciated. See [CONTRIBUTING.md](https://github.com/jgarber623/link-header-parser-ruby/blob/master/CONTRIBUTING.md) for details.
147
+ Interested in helping improve link-header-parser-ruby? Awesome! Your help is greatly appreciated. See [CONTRIBUTING.md](https://github.com/jgarber623/link-header-parser-ruby/blob/main/CONTRIBUTING.md) for details.
130
148
 
131
149
  ## Acknowledgments
132
150
 
@@ -1,17 +1,20 @@
1
- require 'ostruct'
1
+ require 'forwardable'
2
2
 
3
- require 'absolutely'
3
+ require 'addressable/uri'
4
4
 
5
5
  require 'link_header_parser/version'
6
- require 'link_header_parser/exceptions'
7
6
 
8
- require 'link_header_parser/parsed_header'
9
- require 'link_header_parser/parsed_header_collection'
7
+ require 'link_header_parser/link_header'
8
+ require 'link_header_parser/link_header_parameter'
9
+ require 'link_header_parser/link_headers_collection'
10
10
 
11
11
  module LinkHeaderParser
12
- class << self
13
- def parse(*headers, base:)
14
- ParsedHeaderCollection.new(headers, base: base)
15
- end
12
+ # Parse an array of HTTP Link headers
13
+ #
14
+ # @param headers [Array<String>]
15
+ # @param base [String]
16
+ # @return [LinkHeaderParser::LinkHeadersCollection]
17
+ def self.parse(*headers, base:)
18
+ LinkHeadersCollection.new(*headers, base: base)
16
19
  end
17
20
  end
@@ -0,0 +1,105 @@
1
+ module LinkHeaderParser
2
+ class LinkHeader
3
+ FIELD_VALUE_REGEXP_PATTERN = /^\s*<\s*(?<target_string>[^>]+)\s*>\s*(?<parameters>;\s*.*)$/.freeze
4
+ PARAMETERS_REGEXP_PATTERN = /(?<!;)\s*([^;]+)/.freeze
5
+
6
+ attr_reader :field_value
7
+
8
+ # Create a new parsed Link header
9
+ # @see https://tools.ietf.org/html/rfc8288#appendix-B.2
10
+ #
11
+ # @param field_value [String]
12
+ # @param base [String]
13
+ def initialize(field_value, base:)
14
+ @field_value = field_value.to_str
15
+ @base = base.to_str
16
+ end
17
+
18
+ # The context URL for this Link header extracted from field_value (or target URL if no context URL is present)
19
+ # @see https://tools.ietf.org/html/rfc8288#appendix-B.2 (Appendix B.2.2.11)
20
+ #
21
+ # @return [String]
22
+ def context_string
23
+ @context_string ||= grouped_link_parameters[:anchor]&.first || target_string
24
+ end
25
+
26
+ # The resolved context URL for this Link header
27
+ # @see https://tools.ietf.org/html/rfc8288#appendix-B.2 (Appendix B.2.2.12)
28
+ #
29
+ # @return [String]
30
+ def context_uri
31
+ @context_uri ||= Addressable::URI.join(target_uri, context_string).normalize.to_s
32
+ end
33
+
34
+ def inspect
35
+ format(%(#<#{self.class.name}:%#0x target_uri: #{target_uri.inspect}, relation_types: #{relation_types.inspect}>), object_id)
36
+ end
37
+
38
+ # The parsed parameters for this Link header extracted from field_value
39
+ # @see https://tools.ietf.org/html/rfc8288#appendix-B.3
40
+ #
41
+ # @return [Array<LinkHeaderParser::LinkHeaderParameter>]
42
+ def link_parameters
43
+ @link_parameters ||= field_value_match_data[:parameters].scan(PARAMETERS_REGEXP_PATTERN).flatten.map { |parameter| LinkHeaderParameter.new(parameter) }
44
+ end
45
+
46
+ # The relations_string value returned as an Array
47
+ # @see https://tools.ietf.org/html/rfc8288#appendix-B.2 (Appendix B.2.2.10 and Appendix B.2.2.17.1)
48
+ #
49
+ # @return [Array<String>]
50
+ def relation_types
51
+ @relation_types ||= relations_string.split.map(&:downcase)
52
+ end
53
+
54
+ # The relation types for this Link header extracted from field_value
55
+ # @see https://tools.ietf.org/html/rfc8288#appendix-B.2 (Appendix B.2.2.9)
56
+ #
57
+ # @return [String]
58
+ def relations_string
59
+ @relations_string ||= grouped_link_parameters[:rel]&.first.to_s
60
+ end
61
+
62
+ # The target URL for this Link header extracted from field_value
63
+ # @see https://tools.ietf.org/html/rfc8288#appendix-B.2 (Appendix B.2.2.4)
64
+ #
65
+ # @return [String]
66
+ def target_string
67
+ @target_string ||= field_value_match_data[:target_string]
68
+ end
69
+
70
+ # The resolved target URL for this Link header
71
+ # @see https://tools.ietf.org/html/rfc8288#appendix-B.2 (Appendix B.2.2.8)
72
+ #
73
+ # @return [String]
74
+ def target_uri
75
+ @target_uri ||= Addressable::URI.join(base, target_string).normalize.to_s
76
+ end
77
+
78
+ # @return [Hash{Symbol => String, Array, Hash{Symbol => Array}}]
79
+ def to_hash
80
+ {
81
+ target_string: target_string,
82
+ target_uri: target_uri,
83
+ context_string: context_string,
84
+ context_uri: context_uri,
85
+ relations_string: relations_string,
86
+ relation_types: relation_types,
87
+ link_parameters: grouped_link_parameters
88
+ }
89
+ end
90
+
91
+ alias to_h to_hash
92
+
93
+ private
94
+
95
+ attr_reader :base
96
+
97
+ def field_value_match_data
98
+ @field_value_match_data ||= field_value.match(FIELD_VALUE_REGEXP_PATTERN)
99
+ end
100
+
101
+ def grouped_link_parameters
102
+ @grouped_link_parameters ||= link_parameters.map(&:to_a).group_by(&:shift).transform_keys(&:to_sym).transform_values(&:flatten)
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,43 @@
1
+ module LinkHeaderParser
2
+ class LinkHeaderParameter
3
+ PARAMETER_REGEXP_PATTERN = /^(?<name>.+?)(?:="?(?<value>.*?)"?)?$/.freeze
4
+
5
+ attr_reader :parameter
6
+
7
+ # @param parameter [String]
8
+ def initialize(parameter)
9
+ @parameter = parameter.to_str
10
+ end
11
+
12
+ def inspect
13
+ format(%(#<#{self.class.name}:%#0x name: #{name.inspect}, value: #{value.inspect}>), object_id)
14
+ end
15
+
16
+ # @see https://tools.ietf.org/html/rfc8288#appendix-B.3 (Appendix B.3.2.9)
17
+ #
18
+ # @return [String]
19
+ def name
20
+ @name ||= parameter_match_data[:name].downcase
21
+ end
22
+
23
+ # @see https://tools.ietf.org/html/rfc8288#appendix-B.3 (Appendix B.3.2.8)
24
+ #
25
+ # @return [String]
26
+ def value
27
+ @value ||= parameter_match_data[:value].to_s
28
+ end
29
+
30
+ # @return [Array<String>]
31
+ def to_ary
32
+ [name, value]
33
+ end
34
+
35
+ alias to_a to_ary
36
+
37
+ private
38
+
39
+ def parameter_match_data
40
+ @parameter_match_data ||= parameter.match(PARAMETER_REGEXP_PATTERN)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,48 @@
1
+ module LinkHeaderParser
2
+ class LinkHeadersCollection
3
+ extend Forwardable
4
+
5
+ include Enumerable
6
+
7
+ def_delegators :members, :[], :<<, :each, :last, :length, :push
8
+
9
+ attr_reader :headers
10
+
11
+ # @param headers [Array<String>]
12
+ # @param base [String]
13
+ def initialize(*headers, base:)
14
+ @headers = headers.to_ary.flatten.map(&:to_str)
15
+ @base = base.to_str
16
+
17
+ distinct_headers.each { |header| push(LinkHeader.new(header, base: base)) }
18
+ end
19
+
20
+ # @return [Hash{Symbol => Array<LinkHeaderParser::LinkHeader>}]
21
+ def group_by_relation_type
22
+ relation_types.map do |relation_type|
23
+ [relation_type, find_all { |member| member.relation_types.include?(relation_type) }]
24
+ end.to_h.transform_keys(&:to_sym)
25
+ end
26
+
27
+ def inspect
28
+ format(%(#<#{self.class.name}:%#0x headers: #{headers.inspect}, relation_types: #{relation_types.inspect}>), object_id)
29
+ end
30
+
31
+ # @return [Array<String>]
32
+ def relation_types
33
+ @relation_types ||= flat_map(&:relation_types).uniq.sort
34
+ end
35
+
36
+ private
37
+
38
+ attr_reader :base
39
+
40
+ def distinct_headers
41
+ @distinct_headers ||= headers.flat_map { |header| header.split(/,(?=[\s|<])/) }.map(&:strip)
42
+ end
43
+
44
+ def members
45
+ @members ||= []
46
+ end
47
+ end
48
+ end
@@ -1,3 +1,3 @@
1
1
  module LinkHeaderParser
2
- VERSION = '0.3.0'.freeze
2
+ VERSION = '3.0.0'.freeze
3
3
  end
@@ -1,10 +1,7 @@
1
- lib = File.expand_path('lib', __dir__)
2
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
-
4
- require 'link_header_parser/version'
1
+ require_relative 'lib/link_header_parser/version'
5
2
 
6
3
  Gem::Specification.new do |spec|
7
- spec.required_ruby_version = ['>= 2.4', '< 2.8']
4
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.5', '< 4')
8
5
 
9
6
  spec.name = 'link-header-parser'
10
7
  spec.version = LinkHeaderParser::VERSION
@@ -16,23 +13,14 @@ Gem::Specification.new do |spec|
16
13
  spec.homepage = 'https://github.com/jgarber623/link-header-parser-ruby'
17
14
  spec.license = 'MIT'
18
15
 
19
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(bin|spec)/}) }
16
+ spec.files = Dir['lib/**/*'].reject { |f| File.directory?(f) }
17
+ spec.files += %w[LICENSE CHANGELOG.md CONTRIBUTING.md README.md]
18
+ spec.files += %w[link-header-parser.gemspec]
20
19
 
21
20
  spec.require_paths = ['lib']
22
21
 
23
- spec.metadata = {
24
- 'bug_tracker_uri' => "#{spec.homepage}/issues",
25
- 'changelog_uri' => "#{spec.homepage}/blob/v#{spec.version}/CHANGELOG.md"
26
- }
27
-
28
- spec.add_development_dependency 'rake', '~> 13.0'
29
- spec.add_development_dependency 'reek', '~> 5.6'
30
- spec.add_development_dependency 'rspec', '~> 3.9'
31
- spec.add_development_dependency 'rubocop', '~> 0.79.0'
32
- spec.add_development_dependency 'rubocop-performance', '~> 1.5'
33
- spec.add_development_dependency 'rubocop-rspec', '~> 1.37'
34
- spec.add_development_dependency 'simplecov', '~> 0.17.1'
35
- spec.add_development_dependency 'simplecov-console', '~> 0.6.0'
22
+ spec.metadata['bug_tracker_uri'] = "#{spec.homepage}/issues"
23
+ spec.metadata['changelog_uri'] = "#{spec.homepage}/blob/v#{spec.version}/CHANGELOG.md"
36
24
 
37
- spec.add_runtime_dependency 'absolutely', '~> 3.1'
25
+ spec.add_runtime_dependency 'addressable', '~> 2.7'
38
26
  end
metadata CHANGED
@@ -1,141 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: link-header-parser
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jason Garber
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-01-20 00:00:00.000000000 Z
11
+ date: 2021-05-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: rake
14
+ name: addressable
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '13.0'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '13.0'
27
- - !ruby/object:Gem::Dependency
28
- name: reek
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '5.6'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '5.6'
41
- - !ruby/object:Gem::Dependency
42
- name: rspec
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '3.9'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '3.9'
55
- - !ruby/object:Gem::Dependency
56
- name: rubocop
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: 0.79.0
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: 0.79.0
69
- - !ruby/object:Gem::Dependency
70
- name: rubocop-performance
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: '1.5'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: '1.5'
83
- - !ruby/object:Gem::Dependency
84
- name: rubocop-rspec
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: '1.37'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - "~>"
95
- - !ruby/object:Gem::Version
96
- version: '1.37'
97
- - !ruby/object:Gem::Dependency
98
- name: simplecov
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - "~>"
102
- - !ruby/object:Gem::Version
103
- version: 0.17.1
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - "~>"
109
- - !ruby/object:Gem::Version
110
- version: 0.17.1
111
- - !ruby/object:Gem::Dependency
112
- name: simplecov-console
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - "~>"
116
- - !ruby/object:Gem::Version
117
- version: 0.6.0
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - "~>"
123
- - !ruby/object:Gem::Version
124
- version: 0.6.0
125
- - !ruby/object:Gem::Dependency
126
- name: absolutely
127
- requirement: !ruby/object:Gem::Requirement
128
- requirements:
129
- - - "~>"
130
- - !ruby/object:Gem::Version
131
- version: '3.1'
19
+ version: '2.7'
132
20
  type: :runtime
133
21
  prerelease: false
134
22
  version_requirements: !ruby/object:Gem::Requirement
135
23
  requirements:
136
24
  - - "~>"
137
25
  - !ruby/object:Gem::Version
138
- version: '3.1'
26
+ version: '2.7'
139
27
  description: Parse HTTP Link headers.
140
28
  email:
141
29
  - jason@sixtwothree.org
@@ -143,25 +31,14 @@ executables: []
143
31
  extensions: []
144
32
  extra_rdoc_files: []
145
33
  files:
146
- - ".editorconfig"
147
- - ".gitignore"
148
- - ".reek.yml"
149
- - ".rspec"
150
- - ".rubocop"
151
- - ".rubocop.yml"
152
- - ".ruby-version"
153
- - ".simplecov"
154
- - ".travis.yml"
155
34
  - CHANGELOG.md
156
35
  - CONTRIBUTING.md
157
- - Gemfile
158
36
  - LICENSE
159
37
  - README.md
160
- - Rakefile
161
38
  - lib/link-header-parser.rb
162
- - lib/link_header_parser/exceptions.rb
163
- - lib/link_header_parser/parsed_header.rb
164
- - lib/link_header_parser/parsed_header_collection.rb
39
+ - lib/link_header_parser/link_header.rb
40
+ - lib/link_header_parser/link_header_parameter.rb
41
+ - lib/link_header_parser/link_headers_collection.rb
165
42
  - lib/link_header_parser/version.rb
166
43
  - link-header-parser.gemspec
167
44
  homepage: https://github.com/jgarber623/link-header-parser-ruby
@@ -169,8 +46,8 @@ licenses:
169
46
  - MIT
170
47
  metadata:
171
48
  bug_tracker_uri: https://github.com/jgarber623/link-header-parser-ruby/issues
172
- changelog_uri: https://github.com/jgarber623/link-header-parser-ruby/blob/v0.3.0/CHANGELOG.md
173
- post_install_message:
49
+ changelog_uri: https://github.com/jgarber623/link-header-parser-ruby/blob/v3.0.0/CHANGELOG.md
50
+ post_install_message:
174
51
  rdoc_options: []
175
52
  require_paths:
176
53
  - lib
@@ -178,18 +55,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
178
55
  requirements:
179
56
  - - ">="
180
57
  - !ruby/object:Gem::Version
181
- version: '2.4'
58
+ version: '2.5'
182
59
  - - "<"
183
60
  - !ruby/object:Gem::Version
184
- version: '2.8'
61
+ version: '4'
185
62
  required_rubygems_version: !ruby/object:Gem::Requirement
186
63
  requirements:
187
64
  - - ">="
188
65
  - !ruby/object:Gem::Version
189
66
  version: '0'
190
67
  requirements: []
191
- rubygems_version: 3.0.6
192
- signing_key:
68
+ rubygems_version: 3.2.16
69
+ signing_key:
193
70
  specification_version: 4
194
71
  summary: Parse HTTP Link headers.
195
72
  test_files: []
data/.editorconfig DELETED
@@ -1,14 +0,0 @@
1
- # EditorConfig is awesome: https://EditorConfig.org
2
- root = true
3
-
4
- [*]
5
- charset = utf-8
6
- end_of_line = lf
7
- insert_final_newline = true
8
- indent_size = 2
9
- indent_style = space
10
- trim_trailing_whitespace = true
11
-
12
- [*.md]
13
- indent_size = 4
14
- indent_style = tab
data/.gitignore DELETED
@@ -1,34 +0,0 @@
1
- *.gem
2
- *.rbc
3
- /.config
4
- /coverage/
5
- /InstalledFiles
6
- /pkg/
7
- /spec/reports/
8
- /spec/examples.txt
9
- /test/tmp/
10
- /test/version_tmp/
11
- /tmp/
12
-
13
- # Used by dotenv library to load environment variables.
14
- # .env
15
-
16
- # Documentation cache and generated files:
17
- /.yardoc/
18
- /_yardoc/
19
- /doc/
20
- /rdoc/
21
-
22
- # Environment normalization:
23
- /.bundle/
24
- /vendor/bundle
25
- /lib/bundler/man/
26
-
27
- # for a library or gem, you might want to ignore these files since the code is
28
- # intended to run in multiple environments; otherwise, check them in:
29
- Gemfile.lock
30
- # .ruby-version
31
- # .ruby-gemset
32
-
33
- # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
34
- .rvmrc
data/.reek.yml DELETED
@@ -1,9 +0,0 @@
1
- detectors:
2
- IrresponsibleModule:
3
- enabled: false
4
- NilCheck:
5
- exclude:
6
- - "LinkHeaderParser::ParsedHeader#relation_types"
7
-
8
- exclude_paths:
9
- - vendor/
data/.rspec DELETED
@@ -1,2 +0,0 @@
1
- --order random
2
- --require spec_helper
data/.rubocop DELETED
@@ -1,3 +0,0 @@
1
- --display-style-guide
2
- --extra-details
3
- --parallel
data/.rubocop.yml DELETED
@@ -1,26 +0,0 @@
1
- require:
2
- - rubocop-performance
3
- - rubocop-rspec
4
-
5
- Layout/HashAlignment:
6
- EnforcedHashRocketStyle: table
7
-
8
- Layout/LineLength:
9
- Enabled: false
10
-
11
- Metrics/ModuleLength:
12
- Exclude:
13
- - spec/support/**/*.rb
14
-
15
- Naming/FileName:
16
- Exclude:
17
- - lib/link-header-parser.rb
18
-
19
- Style/Documentation:
20
- Enabled: false
21
-
22
- Style/FrozenStringLiteralComment:
23
- Enabled: false
24
-
25
- Style/SymbolArray:
26
- Enabled: false
data/.ruby-version DELETED
@@ -1 +0,0 @@
1
- 2.4.9
data/.simplecov DELETED
@@ -1,11 +0,0 @@
1
- require 'simplecov-console'
2
-
3
- formatters = [SimpleCov::Formatter::HTMLFormatter]
4
-
5
- if RSpec.configuration.files_to_run.length > 1
6
- formatters << SimpleCov::Formatter::Console
7
- end
8
-
9
- SimpleCov.start do
10
- formatter SimpleCov::Formatter::MultiFormatter.new(formatters)
11
- end
data/.travis.yml DELETED
@@ -1,19 +0,0 @@
1
- dist: bionic
2
- language: ruby
3
- rvm:
4
- - 2.4
5
- - 2.5
6
- - 2.6
7
- - 2.7
8
- cache: bundler
9
- before_script:
10
- - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
11
- - chmod +x ./cc-test-reporter
12
- - ./cc-test-reporter before-build
13
- after_script:
14
- - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
15
- notifications:
16
- email: false
17
- slack:
18
- rooms:
19
- secure: ZV5G4LIK9OXRXkXt3iC53QD1uVWmThzETj8CallRoZgGfUKfzzMjx2V7NqJLwkmFkaK9y/flt/bRVMVqkH60GY0Gq6YeS9zuKEWJYesC6+aWQa6scKaxgewLdfYth3ijMS91XbPKECbFj0LOs1WPTfqYDsdPpOnH2RBWUO6lx8ID4DZtvi7kMhcY2+K21Q3/3gW9y919KHSCppM24EsyIbkAlFce5RjXn5WmfyTOTyTrxbHyByFr3SSTGdyO3KK0Fa4hhKNC2pa5WmlLHvZ/c5P7MYf1Eo+Rji5I0QY3pcGOIvbK0xBBnXnGI6Zfp088evElGpw8qgUdkR03QbxjTYANdN6mlk9DvnHqPWjI6OalF3SA7PP9mYjKVb2THDPqWOAvqYdMrrLylduWfdCzpxk4Z13DwF6rYSRgz3juKeKx80rhcAFmptLAC3uzQxgSgKoj9xZbbcYqhh/JQL9lNBZYJyLO5M0fOzrEDbLyNrWFYzUgKYgxRAOLWhJ8V2FlN3oR2cXXcf6vmlpPYfoCA0a8Ig82Htapan0JEhNEU6sS1ctdS7NegccEB9P0V5hUDy3AooaqCy8gFYE3TENYI1vwrevo5vqtNffR1PXTsliGe/gIIeFET6H2IJ+HYG9DefMHwYIR97enMFe50PauNGqdHS9kCoUV59r1PDOBJuU=
data/Gemfile DELETED
@@ -1,4 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in link-header-parser.gemspec
4
- gemspec
data/Rakefile DELETED
@@ -1,18 +0,0 @@
1
- require 'bundler/gem_tasks'
2
-
3
- require 'reek/rake/task'
4
- require 'rspec/core/rake_task'
5
- require 'rubocop/rake_task'
6
-
7
- Reek::Rake::Task.new do |task|
8
- task.fail_on_error = false
9
- task.source_files = FileList['**/*.rb'].exclude('vendor/**/*.rb')
10
- end
11
-
12
- RSpec::Core::RakeTask.new
13
-
14
- RuboCop::RakeTask.new do |task|
15
- task.fail_on_error = false
16
- end
17
-
18
- task default: [:rubocop, :reek, :spec]
@@ -1,5 +0,0 @@
1
- module LinkHeaderParser
2
- class LinkHeaderParserError < StandardError; end
3
-
4
- class ArgumentError < LinkHeaderParserError; end
5
- end
@@ -1,70 +0,0 @@
1
- module LinkHeaderParser
2
- class ParsedHeader
3
- attr_reader :header
4
-
5
- def initialize(header, base:)
6
- raise ArgumentError, "header must be a String (given #{header.class})" unless header.is_a?(String)
7
- raise ArgumentError, "base must be a String (given #{base.class})" unless base.is_a?(String)
8
-
9
- @header = header
10
- @base = base
11
- end
12
-
13
- # https://tools.ietf.org/html/rfc8288#section-3.2
14
- def context
15
- @context ||= parameters.anchor || target
16
- end
17
-
18
- def context_uri
19
- @context_uri ||= Absolutely.to_abs(base: target_uri, relative: context)
20
- end
21
-
22
- def inspect
23
- format(%(#<#{self.class.name}:%#0x @header="#{header.gsub('"', '\"')}">), object_id)
24
- end
25
-
26
- def parameters
27
- @parameters ||= OpenStruct.new(header_attributes)
28
- end
29
-
30
- def relation_types
31
- @relation_types ||= relations&.split(' ') || nil
32
- end
33
-
34
- # https://tools.ietf.org/html/rfc8288#section-3.3
35
- def relations
36
- @relations ||= parameters.rel || nil
37
- end
38
-
39
- # https://tools.ietf.org/html/rfc8288#section-3.1
40
- def target
41
- @target ||= header_match_data[:target]
42
- end
43
-
44
- def target_uri
45
- @target_uri ||= Absolutely.to_abs(base: @base, relative: target)
46
- end
47
-
48
- def to_h
49
- {
50
- target: target,
51
- target_uri: target_uri,
52
- context: context,
53
- context_uri: context_uri,
54
- relations: relations,
55
- relation_types: relation_types,
56
- parameters: parameters.to_h
57
- }
58
- end
59
-
60
- private
61
-
62
- def header_attributes
63
- @header_attributes ||= header_match_data[:attributes].tr('"', '').split(';').map { |tuple| tuple.split('=').map(&:strip) }.sort.to_h
64
- end
65
-
66
- def header_match_data
67
- @header_match_data ||= header.match(/^<\s*(?<target>[^>]+)\s*>\s*;\s*(?<attributes>.*)$/)
68
- end
69
- end
70
- end
@@ -1,58 +0,0 @@
1
- module LinkHeaderParser
2
- class ParsedHeaderCollection
3
- include Enumerable
4
-
5
- attr_reader :headers
6
-
7
- def initialize(*headers, base:)
8
- @headers = headers.flatten
9
- @base = base
10
- end
11
-
12
- def by_relation_type
13
- @by_relation_type ||= OpenStruct.new(mapped_relation_types)
14
- end
15
-
16
- def each
17
- return to_enum unless block_given?
18
-
19
- parsed_headers.each { |parsed_header| yield parsed_header }
20
-
21
- self
22
- end
23
-
24
- def inspect
25
- format(%(#<#{self.class.name}:%#0x @headers=#{headers}>), object_id)
26
- end
27
-
28
- def last
29
- @last ||= parsed_headers[-1]
30
- end
31
-
32
- def length
33
- @length ||= parsed_headers.length
34
- end
35
-
36
- def relation_types
37
- @relation_types ||= parsed_headers.map(&:relation_types).flatten.uniq.sort
38
- end
39
-
40
- private
41
-
42
- def find_all_by_relation_type(relation_type)
43
- find_all { |parsed_header| parsed_header.relation_types.include?(relation_type) }
44
- end
45
-
46
- def mapped_relation_types
47
- @mapped_relation_types ||= relation_types.map { |relation_type| [relation_type, find_all_by_relation_type(relation_type)] }.to_h
48
- end
49
-
50
- def parsed_headers
51
- @parsed_headers ||= uniq_headers.map { |header| ParsedHeader.new(header, base: @base) }
52
- end
53
-
54
- def uniq_headers
55
- @uniq_headers ||= headers.map { |header| header.split(/,(?=[\s|<])/) }.flatten.map(&:strip)
56
- end
57
- end
58
- end