link-header-parser 0.2.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 39406f09a5e4cfecc6d2e1918b947b4fef83ce65915f93af711fc816b92d3760
4
- data.tar.gz: 10e5a92616f6db295efbeb544dd650b2ca8099f98bb269c4dc8ec45540ee4fc7
3
+ metadata.gz: edd4ff4c4ffec2f5a5610c32c6814cd82fc1b6f33308648bac07e3444f5f007b
4
+ data.tar.gz: 2f533a163609ef49b0e8f44d3ddfe85c2450aedffd6da2ae971cee4b54a22d18
5
5
  SHA512:
6
- metadata.gz: 9d64a96e22925e26599216e3c6e3e27f90b546a9dea6d7aba08fd6566240ec25688e674424d2803eed7d63c424477c8f02841b9f0061e0100928f3c37d146628
7
- data.tar.gz: 6d2951bdd0d745f7ae9176f63bb0e5f7c9f2ecab722252f8f5c557e03ebb2afea6a41494a0d697f7ef9e73eb699150d4763d31e2246ae1ebf970ba19f0ac7e26
6
+ metadata.gz: 6bca6797af5591f2114b141a11aa7f1d1c80c61279fc79dedce4fcba6fa557777c3c2ae635e87012b4a3ce13e43d4ba42c506b274f8ddf6b6631b18e1c71b19d
7
+ data.tar.gz: db2871789cb01fdcd8b8e8e848320d0ff4c8444381f39a02fdbc24071caf2dce3fed31dc0106c53649c21f57a725348a5c4e1943e759c7be5d21e918a819f4da
data/CHANGELOG.md CHANGED
@@ -1,8 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.2.0 / 20201-04-02
4
+
5
+ - Expand supported Ruby versions to include 3.0 (7187878)
6
+
7
+ ## 2.1.0 / 2020-10-07
8
+
9
+ - Update Absolutely dependency to 5.0 (62988c0)
10
+
11
+ ## 2.0.0 / 2020-05-21
12
+
13
+ - **Breaking changes:** Rewrite gem code (5351010)
14
+ - `LinkHeaderParser.parse` returns `LinkHeadersCollection`
15
+ - New classes: `LinkHeadersCollection`, `LinkHeader`, and `LinkHeaderParameter`
16
+ - Renamed collection's `by_relation_type` method to `group_by_relation_type`
17
+ - **Breaking change:** Update project Ruby version to 2.5.8 and minimum Ruby version to 2.5 (05b2e82)
18
+ - Update inline documentation and refactor `ParsedHeader` and `ParsedHeaderCollection` classes (31ec43e)
19
+
20
+ ## 1.0.0 / 2020-05-14
21
+
22
+ - Update Absolutely dependency to 4.0 (4b78347)
23
+ - Update development Ruby version to 2.4.10 (8d26096)
24
+ - Move development dependencies to `Gemfile` (3f6b5dd)
25
+
26
+ ## 0.3.0 / 2020-01-20
27
+
28
+ - Expand supported Ruby versions to include 2.7 (c55624a)
29
+
3
30
  ## 0.2.0 / 2019-07-02
4
31
 
5
- - 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)).
32
+ - Add support for the `anchor` parameter to `ParsedHeader` exposed via `context` and `context_uri` methods (d2dff52)
6
33
 
7
34
  ## 0.1.0 / 2019-06-06
8
35
 
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.6 and is additionally tested against Ruby 2.5.5 and 2.6.3 using [Travis CI](https://travis-ci.com/jgarber623/link-header-parser-ruby).
11
+ link-header-parser-ruby is developed using Ruby 2.5.8 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.6. 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.6 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.8. 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.8 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.6 and is additionally tested against Ruby 2.5.5 and 2.6.3 using [Travis CI](https://travis-ci.com/jgarber623/link-header-parser-ruby).
15
+ link-header-parser-ruby is developed using Ruby 2.5.8 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
 
@@ -51,82 +50,101 @@ The `parse` method accepts two arguments:
51
50
  1. an `Array` of strings representing HTTP Link headers (e.g. `['</>; rel="home"', '</chapters/1>; anchor="#copyright"; rel="license"']`)
52
51
  1. a `String` 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:0x3fdea54716ac name: "rel", value: "alternate">, #<LinkHeaderParser::LinkHeaderParameter:0x3fdea5471684 name: "hreflang", value: "en-US">, #<LinkHeaderParser::LinkHeaderParameter:0x3fdea5471670 name: "title", value: "sixtwothree.org: Posts">, #<LinkHeaderParser::LinkHeaderParameter:0x3fdea547165c 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,21 @@
1
- require 'ostruct'
1
+ require 'forwardable'
2
2
 
3
3
  require 'absolutely'
4
4
 
5
5
  require 'link_header_parser/version'
6
6
  require 'link_header_parser/exceptions'
7
7
 
8
- require 'link_header_parser/parsed_header'
9
- require 'link_header_parser/parsed_header_collection'
8
+ require 'link_header_parser/link_header'
9
+ require 'link_header_parser/link_header_parameter'
10
+ require 'link_header_parser/link_headers_collection'
10
11
 
11
12
  module LinkHeaderParser
12
- class << self
13
- def parse(*headers, base:)
14
- ParsedHeaderCollection.new(headers, base: base)
15
- end
13
+ # Parse an array of HTTP Link headers
14
+ #
15
+ # @param headers [Array<String>]
16
+ # @param base [String]
17
+ # @return [LinkHeaderParser::LinkHeadersCollection]
18
+ def self.parse(*headers, base:)
19
+ LinkHeadersCollection.new(*headers, base: base)
16
20
  end
17
21
  end
@@ -0,0 +1,106 @@
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
+ raise ArgumentError, "field_value must be a String (given #{field_value.class})" unless field_value.is_a?(String)
15
+ raise ArgumentError, "base must be a String (given #{base.class})" unless base.is_a?(String)
16
+
17
+ @field_value = field_value
18
+ @base = base
19
+ end
20
+
21
+ # The context URL for this Link header extracted from field_value (or target URL if no context URL is present)
22
+ # @see https://tools.ietf.org/html/rfc8288#appendix-B.2 (Appendix B.2.2.11)
23
+ #
24
+ # @return [String]
25
+ def context_string
26
+ @context_string ||= grouped_link_parameters[:anchor]&.first || target_string
27
+ end
28
+
29
+ # The resolved context URL for this Link header
30
+ # @see https://tools.ietf.org/html/rfc8288#appendix-B.2 (Appendix B.2.2.12)
31
+ #
32
+ # @return [String]
33
+ def context_uri
34
+ @context_uri ||= Absolutely.to_abs(base: target_uri, relative: context_string)
35
+ end
36
+
37
+ def inspect
38
+ format(%(#<#{self.class.name}:%#0x target_uri: #{target_uri.inspect}, relation_types: #{relation_types.inspect}>), object_id)
39
+ end
40
+
41
+ # The parsed parameters for this Link header extracted from field_value
42
+ # @see https://tools.ietf.org/html/rfc8288#appendix-B.3
43
+ #
44
+ # @return [Array<LinkHeaderParser::LinkHeaderParameter>]
45
+ def link_parameters
46
+ @link_parameters ||= field_value_match_data[:parameters].scan(PARAMETERS_REGEXP_PATTERN).flatten.map { |parameter| LinkHeaderParameter.new(parameter) }
47
+ end
48
+
49
+ # The relations_string value returned as an Array
50
+ # @see https://tools.ietf.org/html/rfc8288#appendix-B.2 (Appendix B.2.2.10 and Appendix B.2.2.17.1)
51
+ #
52
+ # @return [Array<String>]
53
+ def relation_types
54
+ @relation_types ||= relations_string.split.map(&:downcase)
55
+ end
56
+
57
+ # The relation types for this Link header extracted from field_value
58
+ # @see https://tools.ietf.org/html/rfc8288#appendix-B.2 (Appendix B.2.2.9)
59
+ #
60
+ # @return [String]
61
+ def relations_string
62
+ @relations_string ||= grouped_link_parameters[:rel]&.first || ''
63
+ end
64
+
65
+ # The target URL for this Link header extracted from field_value
66
+ # @see https://tools.ietf.org/html/rfc8288#appendix-B.2 (Appendix B.2.2.4)
67
+ #
68
+ # @return [String]
69
+ def target_string
70
+ @target_string ||= field_value_match_data[:target_string]
71
+ end
72
+
73
+ # The resolved target URL for this Link header
74
+ # @see https://tools.ietf.org/html/rfc8288#appendix-B.2 (Appendix B.2.2.8)
75
+ #
76
+ # @return [String]
77
+ def target_uri
78
+ @target_uri ||= Absolutely.to_abs(base: base, relative: target_string)
79
+ end
80
+
81
+ # @return [Hash{Symbol => String, Array, Hash{Symbol => Array}}]
82
+ def to_h
83
+ {
84
+ target_string: target_string,
85
+ target_uri: target_uri,
86
+ context_string: context_string,
87
+ context_uri: context_uri,
88
+ relations_string: relations_string,
89
+ relation_types: relation_types,
90
+ link_parameters: grouped_link_parameters
91
+ }
92
+ end
93
+
94
+ private
95
+
96
+ attr_reader :base
97
+
98
+ def field_value_match_data
99
+ @field_value_match_data ||= field_value.match(FIELD_VALUE_REGEXP_PATTERN)
100
+ end
101
+
102
+ def grouped_link_parameters
103
+ @grouped_link_parameters ||= link_parameters.map(&:to_a).group_by(&:shift).transform_keys(&:to_sym).transform_values(&:flatten)
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,41 @@
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
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, nil]
26
+ def value
27
+ @value ||= parameter_match_data[:value] || ''
28
+ end
29
+
30
+ # @return [Array<String>]
31
+ def to_a
32
+ [name, value]
33
+ end
34
+
35
+ private
36
+
37
+ def parameter_match_data
38
+ @parameter_match_data ||= parameter.match(PARAMETER_REGEXP_PATTERN)
39
+ end
40
+ end
41
+ 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.flatten
15
+ @base = base
16
+
17
+ discrete_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 discrete_headers
41
+ @discrete_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.2.0'.freeze
2
+ VERSION = '2.2.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.7']
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', '~> 12.3'
29
- spec.add_development_dependency 'reek', '~> 5.4'
30
- spec.add_development_dependency 'rspec', '~> 3.8'
31
- spec.add_development_dependency 'rubocop', '~> 0.72.0'
32
- spec.add_development_dependency 'rubocop-performance', '~> 1.3'
33
- spec.add_development_dependency 'rubocop-rspec', '~> 1.33'
34
- spec.add_development_dependency 'simplecov', '~> 0.16.1'
35
- spec.add_development_dependency 'simplecov-console', '~> 0.5.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.0'
25
+ spec.add_runtime_dependency 'absolutely', '~> 5.1'
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.2.0
4
+ version: 2.2.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: 2019-07-02 00:00:00.000000000 Z
11
+ date: 2021-04-02 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: rake
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '12.3'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '12.3'
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.4'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '5.4'
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.8'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '3.8'
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.72.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.72.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.3'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: '1.3'
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.33'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - "~>"
95
- - !ruby/object:Gem::Version
96
- version: '1.33'
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.16.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.16.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.5.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.5.0
125
13
  - !ruby/object:Gem::Dependency
126
14
  name: absolutely
127
15
  requirement: !ruby/object:Gem::Requirement
128
16
  requirements:
129
17
  - - "~>"
130
18
  - !ruby/object:Gem::Version
131
- version: '3.0'
19
+ version: '5.1'
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.0'
26
+ version: '5.1'
139
27
  description: Parse HTTP Link headers.
140
28
  email:
141
29
  - jason@sixtwothree.org
@@ -143,25 +31,15 @@ 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
39
  - lib/link_header_parser/exceptions.rb
163
- - lib/link_header_parser/parsed_header.rb
164
- - lib/link_header_parser/parsed_header_collection.rb
40
+ - lib/link_header_parser/link_header.rb
41
+ - lib/link_header_parser/link_header_parameter.rb
42
+ - lib/link_header_parser/link_headers_collection.rb
165
43
  - lib/link_header_parser/version.rb
166
44
  - link-header-parser.gemspec
167
45
  homepage: https://github.com/jgarber623/link-header-parser-ruby
@@ -169,8 +47,8 @@ licenses:
169
47
  - MIT
170
48
  metadata:
171
49
  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.2.0/CHANGELOG.md
173
- post_install_message:
50
+ changelog_uri: https://github.com/jgarber623/link-header-parser-ruby/blob/v2.2.0/CHANGELOG.md
51
+ post_install_message:
174
52
  rdoc_options: []
175
53
  require_paths:
176
54
  - lib
@@ -178,18 +56,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
178
56
  requirements:
179
57
  - - ">="
180
58
  - !ruby/object:Gem::Version
181
- version: '2.4'
59
+ version: '2.5'
182
60
  - - "<"
183
61
  - !ruby/object:Gem::Version
184
- version: '2.7'
62
+ version: '4'
185
63
  required_rubygems_version: !ruby/object:Gem::Requirement
186
64
  requirements:
187
65
  - - ">="
188
66
  - !ruby/object:Gem::Version
189
67
  version: '0'
190
68
  requirements: []
191
- rubygems_version: 3.0.3
192
- signing_key:
69
+ rubygems_version: 3.1.2
70
+ signing_key:
193
71
  specification_version: 4
194
72
  summary: Parse HTTP Link headers.
195
73
  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/AlignHash:
6
- EnforcedHashRocketStyle: table
7
-
8
- Metrics/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.6
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,21 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.4.6
4
- - 2.5.5
5
- - 2.6.3
6
- cache:
7
- - bundler
8
- before_install:
9
- - gem update --system
10
- - gem install bundler
11
- before_script:
12
- - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
13
- - chmod +x ./cc-test-reporter
14
- - ./cc-test-reporter before-build
15
- after_script:
16
- - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
17
- notifications:
18
- email: false
19
- slack:
20
- rooms:
21
- 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,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