link-header-parser 0.1.0 → 2.1.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: 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