link-header-parser 0.1.0 → 2.1.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: 2af19382d4d537959b66ca55c6fcfab4cc52cb552748e6cc9cbf7d18176491e1
4
- data.tar.gz: f576800eaa08e75441edf34b65bac17302cbff52ad161ad84e5604fdd8a66890
3
+ metadata.gz: 49d324c80bd5c13900e144055a9a9470e77f2450ab08edc0b6579a918a7b9043
4
+ data.tar.gz: 38e3d233be9a7162b81f9300dce58c6f43e78b1cdf026d6bedd438ff650e347f
5
5
  SHA512:
6
- metadata.gz: d0823f00fe68edffc867354972a4e3efa4f7f4ebe5cd4ba14ff0dbefe7a291531daca5b789f3392d0b53a9231edc1261f4ea01c094545a74119aed0c9c7f668d
7
- data.tar.gz: 9bf55d725122524374d47024900768bb497239422e826a0d07f66052e091dc950f982e8df3d4275d75837d8e4b3e19bbaa92a8aa5f937299f2429b66e02ee215
6
+ metadata.gz: 57b0b8a602f4d85b3d151172f05fc428211800d66edf22725153b759b2bd6024f8af7dd2ddd693e663176626149073e5bd98e3745e588080a2e729dee33cd38e
7
+ data.tar.gz: 83f5739b85c3e96815eda70045ad39b34a7f01ba8e6cafc92a81a78b3ad500a46871baf1d93ef2d3c75ac663d9431e9abd0f9f38b7b8bf7728d931d38ff9cfcc
data/.reek.yml CHANGED
@@ -1,9 +1,9 @@
1
1
  detectors:
2
2
  IrresponsibleModule:
3
3
  enabled: false
4
- NilCheck:
4
+ NestedIterators:
5
5
  exclude:
6
- - "LinkHeaderParser::ParsedHeader#relation_types"
6
+ - LinkHeaderParser::LinkHeadersCollection#group_by_relation_type
7
7
 
8
8
  exclude_paths:
9
9
  - vendor/
@@ -2,12 +2,16 @@ require:
2
2
  - rubocop-performance
3
3
  - rubocop-rspec
4
4
 
5
- Layout/AlignHash:
6
- EnforcedHashRocketStyle: table
5
+ AllCops:
6
+ NewCops: enable
7
7
 
8
- Metrics/LineLength:
8
+ Layout/LineLength:
9
9
  Enabled: false
10
10
 
11
+ Metrics/ModuleLength:
12
+ Exclude:
13
+ - spec/support/**/*.rb
14
+
11
15
  Naming/FileName:
12
16
  Exclude:
13
17
  - lib/link-header-parser.rb
@@ -1 +1 @@
1
- 2.4.6
1
+ 2.5.8
data/.simplecov CHANGED
@@ -2,9 +2,11 @@ require 'simplecov-console'
2
2
 
3
3
  formatters = [SimpleCov::Formatter::HTMLFormatter]
4
4
 
5
+ # rubocop:disable Style/IfUnlessModifier
5
6
  if RSpec.configuration.files_to_run.length > 1
6
7
  formatters << SimpleCov::Formatter::Console
7
8
  end
9
+ # rubocop:enable Style/IfUnlessModifier
8
10
 
9
11
  SimpleCov.start do
10
12
  formatter SimpleCov::Formatter::MultiFormatter.new(formatters)
@@ -1,13 +1,10 @@
1
+ dist: bionic
1
2
  language: ruby
2
3
  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
4
+ - 2.5
5
+ - 2.6
6
+ - 2.7
7
+ cache: bundler
11
8
  before_script:
12
9
  - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
13
10
  - chmod +x ./cc-test-reporter
@@ -1,5 +1,32 @@
1
1
  # Changelog
2
2
 
3
- 0.1.0 / 2019-06-06
3
+ ## 2.1.0 / 2020-10-07
4
+
5
+ - Update Absolutely dependency to 5.0 (62988c0)
6
+
7
+ ## 2.0.0 / 2020-05-21
8
+
9
+ - **Breaking changes:** Rewrite gem code (5351010)
10
+ - `LinkHeaderParser.parse` returns `LinkHeadersCollection`
11
+ - New classes: `LinkHeadersCollection`, `LinkHeader`, and `LinkHeaderParameter`
12
+ - Renamed collection's `by_relation_type` method to `group_by_relation_type`
13
+ - **Breaking change:** Update project Ruby version to 2.5.8 and minimum Ruby version to 2.5 (05b2e82)
14
+ - Update inline documentation and refactor `ParsedHeader` and `ParsedHeaderCollection` classes (31ec43e)
15
+
16
+ ## 1.0.0 / 2020-05-14
17
+
18
+ - Update Absolutely dependency to 4.0 (4b78347)
19
+ - Update development Ruby version to 2.4.10 (8d26096)
20
+ - Move development dependencies to `Gemfile` (3f6b5dd)
21
+
22
+ ## 0.3.0 / 2020-01-20
23
+
24
+ - Expand supported Ruby versions to include 2.7 (c55624a)
25
+
26
+ ## 0.2.0 / 2019-07-02
27
+
28
+ - Add support for the `anchor` parameter to `ParsedHeader` exposed via `context` and `context_uri` methods (d2dff52)
29
+
30
+ ## 0.1.0 / 2019-06-06
4
31
 
5
32
  - Initial release!
@@ -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 and 2.7 using [Travis CI](https://travis-ci.com/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
data/Gemfile CHANGED
@@ -2,3 +2,13 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in link-header-parser.gemspec
4
4
  gemspec
5
+
6
+ gem 'pry-byebug', '~> 3.9'
7
+ gem 'rake', '~> 13.0'
8
+ gem 'reek', '~> 6.0'
9
+ gem 'rspec', '~> 3.9'
10
+ gem 'rubocop', '~> 0.92.0'
11
+ gem 'rubocop-performance', '~> 1.8'
12
+ gem 'rubocop-rspec', '~> 1.43'
13
+ gem 'simplecov', '~> 0.19.0'
14
+ gem 'simplecov-console', '~> 0.7.2'
data/README.md CHANGED
@@ -4,16 +4,16 @@
4
4
 
5
5
  [![Gem](https://img.shields.io/gem/v/link-header-parser.svg?style=for-the-badge)](https://rubygems.org/gems/link-header-parser)
6
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)
7
+ [![Build](https://img.shields.io/travis/com/jgarber623/link-header-parser-ruby/main.svg?style=for-the-badge)](https://travis-ci.com/jgarber623/link-header-parser-ruby)
8
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
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
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)
11
11
 
12
12
  ## Getting Started
13
13
 
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).
14
+ 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
15
 
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).
16
+ link-header-parser-ruby is developed using Ruby 2.5.8 and is additionally tested against Ruby 2.6 and 2.7 using [Travis CI](https://travis-ci.com/jgarber623/link-header-parser-ruby).
17
17
 
18
18
  ## Installation
19
19
 
@@ -44,44 +44,108 @@ response = HTTP.get('https://sixtwothree.org')
44
44
  link_headers = response.headers.get('link')
45
45
 
46
46
  collection = LinkHeaderParser.parse(link_headers, base: response.uri.to_s)
47
+ ```
48
+
49
+ The `parse` method accepts two arguments:
50
+
51
+ 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
53
+
54
+ 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
+
56
+ For example, you could retrieve an array of `target_uri`s:
47
57
 
48
- puts collection.relation_types # => ["preconnect", "webmention"]
49
- puts collection.map(&:target_uri) # => ["https://assets.sixtwothree.org/", "https://fonts.googleapis.com/", "https://fonts.gstatic.com/", "https://sixtwothree.org/webmentions"]
58
+ ```ruby
59
+ puts collection.map(&:target_uri)
60
+ #=> ["https://assets.sixtwothree.org/", "https://fonts.googleapis.com/", "https://fonts.gstatic.com/", "https://sixtwothree.org/webmentions"]
50
61
  ```
51
62
 
52
- 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` (as demonstrated above).
63
+ ### Working with a `LinkHeadersCollection`
64
+
65
+ In addition to the included `Enumerable` methods, the following methods may be used to interact with a `LinkHeadersCollection`:
53
66
 
54
- Additionally, `collection.by_relation_type` returns an `OpenStruct` with the following attributes:
67
+ #### The `relation_types` Method
68
+
69
+ ```ruby
70
+ puts collection.relation_types
71
+ #=> ["preconnect", "webmention"]
72
+ ```
73
+
74
+ #### The `group_by_relation_type` Method
75
+
76
+ Using the `collection` from above, the `group_by_relation_type` method returns a `Hash`:
55
77
 
56
78
  ```ruby
57
79
  {
58
80
  preconnect: [
59
- #<LinkHeaderParser::ParsedHeader @header="<https://assets.sixtwothree.org/>; rel=\"preconnect\"">,
60
- #<LinkHeaderParser::ParsedHeader @header="<https://fonts.googleapis.com/>; rel=\"preconnect\"">,
61
- #<LinkHeaderParser::ParsedHeader @header="<https://fonts.gstatic.com/>; rel=\"preconnect\"">
81
+ #<LinkHeaderParser::LinkHeader target_uri: "https://assets.sixtwothree.org/", relation_types: ["preconnect"]>,
82
+ #<LinkHeaderParser::LinkHeader target_uri: "https://fonts.googleapis.com/", relation_types: ["preconnect"]>,
83
+ #<LinkHeaderParser::LinkHeader target_uri: "https://fonts.gstatic.com/", relation_types: ["preconnect"]>
62
84
  ],
63
85
  webmention: [
64
- #<LinkHeaderParser::ParsedHeader @header="<https://sixtwothree.org/webmentions>; rel=\"webmention\"">
86
+ #<LinkHeaderParser::LinkHeader target_uri: "https://sixtwothree.org/webmentions", relation_types: ["webmention"]>
65
87
  ]
66
88
  }
67
89
  ```
68
90
 
69
- Building on this example, you may interact with one or more `ParsedHeader`s in a `ParsedHeaderCollection`:
91
+ ### Working with a `LinkHeader`
92
+
93
+ 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).
94
+
95
+ #### Link Target ([§ 3.1](https://tools.ietf.org/html/rfc8288#section-3.1))
96
+
97
+ ```ruby
98
+ link_header = LinkHeaderParser.parse('</index.html>; rel="home"', base: 'https://example.com/').first
99
+
100
+ link_header.target_string
101
+ #=> "/index.html"
102
+
103
+ link_header.target_uri
104
+ #=> "https://example.com/index.html"
105
+ ```
106
+
107
+ 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.
108
+
109
+ #### Link Context ([§ 3.2](https://tools.ietf.org/html/rfc8288#section-3.2))
110
+
111
+ ```ruby
112
+ link_header = LinkHeaderParser.parse('</chapters/1>; anchor="#copyright"; rel="license"', base: 'https://example.com/').first
113
+
114
+ link_header.context_string
115
+ #=> "#copyright"
116
+
117
+ link_header.context_uri
118
+ #=> "https://example.com/chapters/1#copyright"
119
+ ```
120
+
121
+ 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.
122
+
123
+ #### Relation Type ([§ 3.3](https://tools.ietf.org/html/rfc8288#section-3.3))
124
+
125
+ ```ruby
126
+ link_header = LinkHeaderParser.parse('</chapters/1>; rel="prev start"', base: 'https://example.com/').first
127
+
128
+ link_header.relations_string
129
+ #=> "prev start"
130
+
131
+ link_header.relation_types
132
+ #=> ["prev", "start"]
133
+ ```
134
+
135
+ #### Link Parameters ([Appendix B.3](https://tools.ietf.org/html/rfc8288#appendix-B.3))
70
136
 
71
137
  ```ruby
72
- parsed_header = collection.first
138
+ link_header = LinkHeaderParser.parse('</posts.rss>; rel="alternate"; hreflang="en-US"; title="sixtwothree.org: Posts"; type="application/rss+xml"', base: 'https://sixtwothree.org').first
73
139
 
74
- parsed_header.relation_types # => ['preconnect']
75
- parsed_header.relations # => 'preconnect'
76
- parsed_header.target # => 'https://assets.sixtwothree.org/'
77
- parsed_header.target_uri # => 'https://assets.sixtwothree.org/'
140
+ link_header.link_parameters
141
+ #=> [#<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">]
78
142
  ```
79
143
 
80
- 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).
144
+ 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).
81
145
 
82
146
  ## Contributing
83
147
 
84
- 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.
148
+ 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.
85
149
 
86
150
  ## Acknowledgments
87
151
 
@@ -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.1.0'.freeze
2
+ VERSION = '2.1.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', '< 2.8')
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.chdir(File.expand_path(__dir__)) do
17
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(bin|spec)/}) }
18
+ end
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.71.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.0'
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.1.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jason Garber
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-06-07 00:00:00.000000000 Z
11
+ date: 2020-10-08 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.71.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.71.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.0'
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.0'
139
27
  description: Parse HTTP Link headers.
140
28
  email:
141
29
  - jason@sixtwothree.org
@@ -160,8 +48,9 @@ files:
160
48
  - Rakefile
161
49
  - lib/link-header-parser.rb
162
50
  - lib/link_header_parser/exceptions.rb
163
- - lib/link_header_parser/parsed_header.rb
164
- - lib/link_header_parser/parsed_header_collection.rb
51
+ - lib/link_header_parser/link_header.rb
52
+ - lib/link_header_parser/link_header_parameter.rb
53
+ - lib/link_header_parser/link_headers_collection.rb
165
54
  - lib/link_header_parser/version.rb
166
55
  - link-header-parser.gemspec
167
56
  homepage: https://github.com/jgarber623/link-header-parser-ruby
@@ -169,7 +58,7 @@ licenses:
169
58
  - MIT
170
59
  metadata:
171
60
  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.1.0/CHANGELOG.md
61
+ changelog_uri: https://github.com/jgarber623/link-header-parser-ruby/blob/v2.1.0/CHANGELOG.md
173
62
  post_install_message:
174
63
  rdoc_options: []
175
64
  require_paths:
@@ -178,17 +67,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
178
67
  requirements:
179
68
  - - ">="
180
69
  - !ruby/object:Gem::Version
181
- version: '2.4'
70
+ version: '2.5'
182
71
  - - "<"
183
72
  - !ruby/object:Gem::Version
184
- version: '2.7'
73
+ version: '2.8'
185
74
  required_rubygems_version: !ruby/object:Gem::Requirement
186
75
  requirements:
187
76
  - - ">="
188
77
  - !ruby/object:Gem::Version
189
78
  version: '0'
190
79
  requirements: []
191
- rubygems_version: 3.0.3
80
+ rubygems_version: 3.1.2
192
81
  signing_key:
193
82
  specification_version: 4
194
83
  summary: Parse HTTP Link headers.
@@ -1,57 +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
- def inspect
14
- format(%(#<#{self.class.name}:%#0x @header="#{header.gsub('"', '\"')}">), object_id)
15
- end
16
-
17
- def parameters
18
- @parameters ||= OpenStruct.new(header_attributes)
19
- end
20
-
21
- def relation_types
22
- @relation_types ||= relations&.split(' ') || nil
23
- end
24
-
25
- def relations
26
- @relations ||= parameters.rel || nil
27
- end
28
-
29
- def target
30
- @target ||= header_match_data[:target]
31
- end
32
-
33
- def target_uri
34
- @target_uri ||= Absolutely.to_abs(base: @base, relative: target)
35
- end
36
-
37
- def to_h
38
- {
39
- target: target,
40
- target_uri: target_uri,
41
- relations: relations,
42
- relation_types: relation_types,
43
- parameters: parameters.to_h
44
- }
45
- end
46
-
47
- private
48
-
49
- def header_attributes
50
- @header_attributes ||= header_match_data[:attributes].tr('"', '').split(';').map { |tuple| tuple.split('=').map(&:strip) }.sort.to_h
51
- end
52
-
53
- def header_match_data
54
- @header_match_data ||= header.match(/^<\s*(?<target>[^>]+)\s*>\s*;\s*(?<attributes>.*)$/)
55
- end
56
- end
57
- 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 uniq_headers
51
- @uniq_headers ||= headers.map { |header| header.split(/,(?=[\s|<])/) }.flatten.map(&:strip)
52
- end
53
-
54
- def parsed_headers
55
- @parsed_headers ||= uniq_headers.map { |header| ParsedHeader.new(header, base: @base) }
56
- end
57
- end
58
- end