deeplink 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f08c25f1f41d07f18e26e2b357f8f171458036cb7d567f0c4bf1aa85ee16b14b
4
- data.tar.gz: 80d3e34f8cd939d5abbf4e2d6069d94c8e11a1b3e998e2732bec3ab1ce11d2a1
3
+ metadata.gz: 5258494e8891ca8dcd0e05f5d19002c981d0d7ec213e96a2a9f446d08e9622e7
4
+ data.tar.gz: 6d80c29a397aa832ca8618d37b9c67dec410cf977072a1b50928aae9c5766bd1
5
5
  SHA512:
6
- metadata.gz: 1d9eaa03986c02c3c2be37c4de3a7ddb28c005ce6f6259d278f19017096fdbf85c5e07bdecd3c0fc6ff6f453da7f71456c259b9a28323948b8311d4e1dcab423
7
- data.tar.gz: 42a9936bd76ce3d1b92212bce9e776fb867de98d7bd30540d55172c4d8704d4973afb3f39eceef4c0c13780653250d5f13568464a43a4965f61e76d94a067631
6
+ metadata.gz: 897d7c31b35d1614539545155dd0403ce703ea34574868ea1ff55a8882c4fa6f203683f77c045b9a013e002cb98278ef138899a27341875416c57b4c3cdc7ef2
7
+ data.tar.gz: 58a3790de63fd53b4535c4ef3bdecdf9e70d13cbf2d9fb8477ccb7f51bbf4fb4b23c09a7fd987279601ba09b763963a578a45de57885904f4762eedd15018dc8
data/CHANGELOG.md ADDED
@@ -0,0 +1,64 @@
1
+ # Changelog
2
+
3
+ ## 3.0.0 (2026-10-20)
4
+
5
+ ### Breaking changes
6
+
7
+ - `query` returns decoded values (`a b` instead of `a+b`). Values are encoded only by `to_s`, and
8
+ query keys are now encoded too.
9
+ - `add_query` with a `nil` value adds a parameter without a value (`?flag`) instead of `flag=`.
10
+
11
+ ### Fixes
12
+
13
+ - Keep URL fragments (`deep://page#section`), available through `fragment`.
14
+ - Keep query parameters without a value (`?flag`), stored with a `nil` value.
15
+ - Encode query values that are not strings, such as symbols.
16
+ - Parse links without a double slash (`deep:foo`, `deep:/foo`) instead of returning `''`.
17
+ - Parse query values containing `{` or `}` in full.
18
+
19
+ ### Development
20
+
21
+ - Add `changelog_uri` to the gem metadata and ship `CHANGELOG.md` with the gem.
22
+
23
+ ## 2.0.0 (2026-10-20)
24
+
25
+ ### Breaking changes
26
+
27
+ - Require Ruby >= 3.2 (was >= 2.5).
28
+
29
+ ### Fixes
30
+
31
+ - Support Ruby 4.0, which removed the CGI library. Query values are encoded exactly as before.
32
+ - Already encoded query values are no longer double encoded (`?q=hello%20world` used to become
33
+ `q=hello%2520world`).
34
+ - `remove_query` without keys returns `nil` instead of raising `NoMethodError`.
35
+
36
+ ### Development
37
+
38
+ - CI tests Ruby 3.2, 3.3, 3.4 and 4.0, and runs RuboCop in a separate job.
39
+ - Add Dependabot for GitHub Actions and Bundler.
40
+ - Update development dependencies (RuboCop 1.92, RSpec 3.13, `debug` and `irb` instead of
41
+ `pry-byebug`).
42
+ - `rake` runs the specs and RuboCop.
43
+
44
+ ## 1.1.0 (2020-05-01)
45
+
46
+ - Drop support for Ruby 2.1.
47
+
48
+ ## 1.0.1 (2018-02-13)
49
+
50
+ - Code style fixes.
51
+
52
+ ## 1.0.0 (2016-05-06)
53
+
54
+ - First stable release.
55
+
56
+ ## 0.1.4 (2016-04-06)
57
+
58
+ - Fix `NoMethodError` when parsing query parameters on Ruby < 2.1.
59
+
60
+ ## 0.1.3 (2015-12-11)
61
+
62
+ - Fix parsing `nil` links.
63
+ - Parse query parameters with unusual characters.
64
+ - Allow removing a list of query parameters.
data/README.md CHANGED
@@ -3,96 +3,123 @@
3
3
 
4
4
  # Deeplink
5
5
 
6
- Handle deep links in an easy way.
6
+ Parse, inspect and build mobile deep links (`myapp://products/42?ref=email`) in an easy way.
7
7
 
8
- This gem needs ruby version >= 3.2
8
+ Ruby's `URI` treats the first part after `://` as a host, so deep links like `myapp://products/42`
9
+ end up with `products` as the host. This gem reads them the way apps do: a scheme, a path, a query
10
+ and a fragment.
9
11
 
10
- ## Installation
12
+ This gem needs Ruby >= 3.2.
11
13
 
12
- Add this line to your application's Gemfile:
14
+ ## Installation
13
15
 
14
- ```ruby
15
- gem 'deeplink'
16
+ ```sh
17
+ bundle add deeplink
16
18
  ```
17
19
 
18
- And then execute:
19
-
20
- $ bundle
21
-
22
- Or install it yourself as:
20
+ Or install it yourself:
23
21
 
24
- $ gem install deeplink
22
+ ```sh
23
+ gem install deeplink
24
+ ```
25
25
 
26
26
  ## Usage
27
27
 
28
28
  ### Parsing a deep link
29
29
 
30
- Just call `Deeplink.parse` with a deep link String and then you can read the scheme and path.
30
+ Call `Deeplink.parse` with a deep link string to read its scheme and path.
31
+
32
+ ```ruby
33
+ deeplink = Deeplink.parse('myshop://products/42')
34
+
35
+ deeplink.scheme # => "myshop"
36
+ deeplink.path # => "/products/42"
37
+ deeplink.to_s # => "myshop://products/42"
38
+ ```
39
+
40
+ Links without a double slash are supported too:
31
41
 
32
42
  ```ruby
33
- deeplink = Deeplink.parse('foursquare://checkins/12932')
43
+ deeplink = Deeplink.parse('sms:+351910000000?body=hello')
34
44
 
35
- deeplink.scheme # => "foursquare"
36
- deeplink.path # => "/checkins/12932"
37
- deeplink.to_s # => "foursquare://checkins/12932"
45
+ deeplink.scheme # => "sms"
46
+ deeplink.path # => "+351910000000"
47
+ deeplink.to_s # => "sms:+351910000000?body=hello"
38
48
  ```
39
49
 
40
50
  ### Query string
41
51
 
42
- To get the query parameters of a link use `query` method.
52
+ Use `query` to read the query parameters, and `query?` to check whether there are any.
43
53
 
44
54
  ```ruby
45
- deeplink = Deeplink.parse('foursquare://checkins/209823?test=true')
55
+ deeplink = Deeplink.parse('myshop://products/42?ref=email')
46
56
 
47
- deeplink.query # => { :test => "true" }
57
+ deeplink.query # => { ref: "email" }
58
+ deeplink.query? # => true
48
59
  ```
49
60
 
50
- #### Adding a query parameter
51
-
52
- You can add one or more query parameters sending a Hash to `add_query`.
61
+ Values are stored decoded and only encoded again by `to_s`. Parameters without a value get a `nil`
62
+ value.
53
63
 
54
64
  ```ruby
55
- deeplink = Deeplink.parse('foursquare://checkins/20982')
65
+ deeplink = Deeplink.parse('myshop://search?q=running%20shoes&in_stock')
66
+
67
+ deeplink.query # => { q: "running shoes", in_stock: nil }
68
+ deeplink.to_s # => "myshop://search?q=running+shoes&in_stock"
69
+ ```
70
+
71
+ #### Adding query parameters
56
72
 
57
- deeplink.add_query(foo: 'bar') # => { :foo => "bar" }
58
- deeplink.to_s # => "foursquare://checkins/20982?foo=bar"
73
+ Send a Hash with one or more parameters to `add_query`. Values are converted to strings.
59
74
 
60
- deeplink = Deeplink.parse('foursquare://checkins/20982')
75
+ ```ruby
76
+ deeplink = Deeplink.parse('myshop://cart')
61
77
 
62
- deeplink.add_query(foo: 'bar', biz: 'baz') # => { :foo => "bar", :biz => "baz" }
63
- deeplink.to_s # => "foursquare://checkins/20982?foo=bar&biz=baz"
78
+ deeplink.add_query(coupon: 'SUMMER 10', items: 3) # => { coupon: "SUMMER 10", items: "3" }
79
+ deeplink.to_s # => "myshop://cart?coupon=SUMMER+10&items=3"
64
80
  ```
65
81
 
66
- #### Removing a query parameter
82
+ #### Removing query parameters
67
83
 
68
- To remove query parameters call `remove_query` with the key (or list of keys) that you want to
69
- remove. The method will return the value of the deleted key(s).
84
+ Call `remove_query` with the key (or list of keys) to remove. It returns the value of the deleted
85
+ key(s).
70
86
 
71
87
  ```ruby
72
- deeplink = Deeplink.parse('foursquare://checkins/20982?foo=bar')
88
+ deeplink = Deeplink.parse('myshop://products/42?ref=email&utm_source=newsletter')
89
+
90
+ deeplink.remove_query(:utm_source) # => "newsletter"
91
+ deeplink.to_s # => "myshop://products/42?ref=email"
73
92
 
74
- deeplink.remove_query(:foo) # => "bar"
75
- deeplink.to_s # => "foursquare://checkins/20982"
93
+ deeplink.remove_query(:ref, :missing) # => ["email", nil]
94
+ deeplink.to_s # => "myshop://products/42"
76
95
  ```
77
96
 
97
+ ### Fragment
98
+
99
+ The fragment is available through `fragment`, and is kept as given (percent-encoded).
100
+
78
101
  ```ruby
79
- deeplink = Deeplink.parse('foursquare://checkins/20982?foo=bar&fu=baz')
102
+ deeplink = Deeplink.parse('myshop://products/42#reviews')
80
103
 
81
- deeplink.remove_query(:foo, :fu) # => ["bar", "baz"]
82
- deeplink.to_s # => "foursquare://checkins/20982"
104
+ deeplink.fragment # => "reviews"
105
+ deeplink.to_s # => "myshop://products/42#reviews"
83
106
  ```
84
107
 
85
108
  ## Development
86
109
 
87
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake` to run the tests and RuboCop. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
110
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake`
111
+ to run the tests and RuboCop. You can also run `bin/console` for an interactive prompt that will
112
+ allow you to experiment.
88
113
 
89
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
114
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new
115
+ version, update the version number in `version.rb` and `CHANGELOG.md`, and then run
116
+ `bundle exec rake release`, which will create a git tag for the version, push git commits and tags,
117
+ and push the `.gem` file to [rubygems.org](https://rubygems.org).
90
118
 
91
119
  ## Contributing
92
120
 
93
121
  Bug reports and pull requests are welcome on GitHub at https://github.com/rikas/deeplink.
94
122
 
95
-
96
123
  ## License
97
124
 
98
- The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
125
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/lib/deeplink/link.rb CHANGED
@@ -11,9 +11,11 @@ end
11
11
 
12
12
  module Deeplink
13
13
  class Link
14
- attr_accessor :scheme, :path
14
+ # The fragment is kept as given, so it should already be percent-encoded.
15
+ attr_accessor :scheme, :path, :fragment
15
16
 
16
- # To add and remove items to the query string use #add_query and #remove_query
17
+ # Query values are stored decoded and only encoded by #to_s. To add and remove items to the
18
+ # query string use #add_query and #remove_query.
17
19
  attr_reader :query
18
20
 
19
21
  def initialize(uri)
@@ -22,9 +24,12 @@ module Deeplink
22
24
  uri = parse(uri)
23
25
 
24
26
  self.scheme = uri.scheme
25
- self.path = uri.path
27
+ self.fragment = uri.fragment
26
28
 
27
- @query = sanitize(parse_query(uri.query)) if uri.query
29
+ # Links without "//" (e.g. "deep:foo?a=1") are opaque, and URI leaves the query in there.
30
+ self.path, query_string = uri.opaque ? uri.opaque.split('?', 2) : [uri.path, uri.query]
31
+
32
+ @query = parse_query(query_string) if query_string
28
33
  end
29
34
 
30
35
  # Add query parameters to the link. You can add one or more parameters since this method
@@ -37,6 +42,8 @@ module Deeplink
37
42
  # deeplink.add_query(lat: 38.7179233, lon: -9.150129)
38
43
  #
39
44
  # deeplink.to_s # => "link://directions?lat=38.7179233&lon=-9.150129"
45
+ #
46
+ # Values are converted to strings. A nil value adds a parameter without a value ("?flag").
40
47
  def add_query(hash)
41
48
  @query ||= {}
42
49
 
@@ -84,13 +91,9 @@ module Deeplink
84
91
  def to_s
85
92
  return '' unless scheme && path
86
93
 
87
- uri = "#{scheme}:/#{path}"
88
-
89
- if query?
90
- query_string = query.map { |key, value| "#{key}=#{value}" }.join('&')
91
-
92
- uri += "?#{query_string}"
93
- end
94
+ uri = @double_slash ? "#{scheme}:/#{path}" : "#{scheme}:#{path}"
95
+ uri += "?#{query_string}" if query?
96
+ uri += "##{fragment}" if fragment
94
97
 
95
98
  uri
96
99
  end
@@ -98,31 +101,34 @@ module Deeplink
98
101
  private
99
102
 
100
103
  def parse(original_uri)
104
+ @double_slash = original_uri.match?(%r{\A[^:/?#]+://})
105
+
101
106
  # Because URI is dumb and thinks that every uri needs to have a host. Adding an extra slash
102
107
  # we are basically tricking URI to think that host is nil (and avoid errors).
103
- uri = original_uri.sub(%r{://}, ':///')
108
+ uri = @double_slash ? original_uri.sub('://', ':///') : original_uri
104
109
 
105
110
  URI.parse(uri)
106
111
  end
107
112
 
108
113
  def sanitize(hash)
109
- sanitized = {}
114
+ hash.to_h { |key, value| [key.to_sym, value&.to_s] }
115
+ end
110
116
 
111
- hash.each_key do |key|
112
- value = hash[key].is_a?(String) ? CGI.escape(hash[key]) : hash[key].to_s
117
+ def parse_query(query_str)
118
+ query_str.split('&').each_with_object({}) do |pair, params|
119
+ key, value = pair.split('=', 2)
120
+ next if key.nil? || key.empty?
113
121
 
114
- sanitized[key.to_sym] = value
122
+ params[CGI.unescape(key).to_sym] = value && CGI.unescape(value)
115
123
  end
116
-
117
- sanitized
118
124
  end
119
125
 
120
- def parse_query(query_str)
121
- return unless query_str
126
+ def query_string
127
+ query.map do |key, value|
128
+ key = CGI.escape(key.to_s)
122
129
 
123
- # Values are unescaped here because #sanitize escapes them again; otherwise already encoded
124
- # values (e.g. "%20") would end up double encoded.
125
- query_str.scan(/([^&=]+)=([^&{}#]*)/).to_h { |key, value| [key, CGI.unescape(value)] }
130
+ value.nil? ? key : "#{key}=#{CGI.escape(value)}"
131
+ end.join('&')
126
132
  end
127
133
  end
128
134
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Deeplink
4
- VERSION = '2.0.0'
4
+ VERSION = '3.0.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deeplink
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ricardo Otero
@@ -16,6 +16,7 @@ executables: []
16
16
  extensions: []
17
17
  extra_rdoc_files: []
18
18
  files:
19
+ - CHANGELOG.md
19
20
  - LICENSE.txt
20
21
  - README.md
21
22
  - lib/deeplink.rb
@@ -27,6 +28,7 @@ licenses:
27
28
  metadata:
28
29
  source_code_uri: https://github.com/rikas/deeplink
29
30
  bug_tracker_uri: https://github.com/rikas/deeplink/issues
31
+ changelog_uri: https://github.com/rikas/deeplink/blob/main/CHANGELOG.md
30
32
  rubygems_mfa_required: 'true'
31
33
  rdoc_options: []
32
34
  require_paths: