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