protocol-media 0.2.0 → 0.3.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: 41cc93afe36dc9af35b5ca77721b8859543b270627a3cb9f7f6cc2df2334d83e
4
- data.tar.gz: 306bf200c34635b7eba643945121d6150054759ffa7ffa9ce5f08bf0668aa3b0
3
+ metadata.gz: b242140a081b74335a73f8e92656c41a45f7c07b6d25d49d710a3b10192de458
4
+ data.tar.gz: af83ab4f1cc8e8e02c110a38c2d38fbc62cf7d8649cfb0b5fdbd765a11ef663e
5
5
  SHA512:
6
- metadata.gz: 7ab4060ead68e7b62f6486e298155e23060019bf7eb4bc9d2524efcb5cfe65e0a018945d46305730359b0c7e6bca158b44632ce5b7ca06ffd6c3c0bc2b8d327c
7
- data.tar.gz: 84ccb4d5c42a6431cdc7da7d5ba6d1c776581bb561f0bde140d002c7cc5549f13304023c5d55bf4385c3e1de5a17be86c9d0aefcd2f708969aef3476772faa39
6
+ metadata.gz: f6536b595d6c5ec253898823de7ab9e0d8875e767e6b0e35d70865962beafedf80e3659f652894e17333612a3f7504d185c25ebd080570b0a75b6cc57cd44de4
7
+ data.tar.gz: adc4678525e9ac20e227db5b89256caadcbefeba5a9b4972cffd8937ae02b129c039ae301960142884773d0fb6d548c4b991605b81aa8c7ce45f96cd180ae7d7
checksums.yaml.gz.sig ADDED
Binary file
@@ -7,48 +7,8 @@ require_relative "type"
7
7
 
8
8
  module Protocol
9
9
  module Media
10
- # An immutable map from concrete media types to objects for range-based lookup.
10
+ # A map from concrete media types to objects for range-based lookup.
11
11
  class Map
12
- # Incrementally constructs an immutable media map.
13
- class Builder
14
- # Initialize an empty media map builder.
15
- def initialize
16
- @index = {}
17
- end
18
-
19
- # Associate a concrete media type with an object.
20
- #
21
- # @parameter media_type [String | Object] The concrete media type or compatible object.
22
- # @parameter object [Object] The object associated with the media type.
23
- def []=(media_type, object)
24
- media_type = Type.for(media_type)
25
-
26
- # Preserve the first registration as the default for wildcard queries:
27
- @index["*/*"] ||= object
28
- @index["#{media_type.type}/*"] ||= object
29
- @index[media_type.name] = object
30
- end
31
-
32
- # Compile the current registrations into an immutable map.
33
- # @returns [Map] The immutable media map.
34
- def build
35
- return Map.new(@index).freeze
36
- end
37
- end
38
-
39
- # Construct an immutable map using a builder.
40
- # @yields {|builder| ...} The mutable builder.
41
- # @returns [Map] The immutable media map.
42
- def self.build
43
- builder = Builder.new
44
-
45
- if block_given?
46
- yield builder
47
- end
48
-
49
- return builder.build
50
- end
51
-
52
12
  # Convert keyed entries into a media map.
53
13
  #
54
14
  # Existing maps are returned unchanged, including their frozen state.
@@ -58,28 +18,44 @@ module Protocol
58
18
  def self.for(entries)
59
19
  return entries if entries.instance_of?(self)
60
20
 
61
- return build do |builder|
62
- entries.each do |media_type, object|
63
- builder[media_type] = object
64
- end
65
- end
21
+ return new(entries).freeze
66
22
  end
67
23
 
68
- # Initialize a new media map with a given index.
24
+ # Initialize a new mutable media map with the given entries.
69
25
  #
70
- # The index is retained without copying or freezing it.
26
+ # Freezing the map compiles the entries into an efficient immutable index.
71
27
  #
72
- # @parameter index [Hash] The keyed media range entries.
73
- def initialize(index)
74
- @index = index
28
+ # @parameter entries [Enumerable] The keyed media type entries.
29
+ def initialize(entries = {})
30
+ @entries = {}
31
+ @index = nil
32
+
33
+ entries.each do |media_type, object|
34
+ self[media_type] = object
35
+ end
36
+ end
37
+
38
+ # Associate a concrete media type with an object.
39
+ # @parameter media_type [String | Object] The concrete media type or compatible object.
40
+ # @parameter object [Object] The object associated with the media type.
41
+ def []=(media_type, object)
42
+ raise FrozenError, "can't modify frozen #{self.class}" if frozen?
43
+
44
+ media_type = Type.for(media_type)
45
+
46
+ @entries[media_type.name] = [media_type, object]
47
+ @index = nil
75
48
  end
76
49
 
77
- # Freeze the media map and its index.
50
+ # Freeze the media map, compiling its entries into an efficient index.
78
51
  # @returns [self]
79
52
  def freeze
80
53
  return self if frozen?
81
54
 
82
- @index.freeze
55
+ index = self.index
56
+ @entries = nil
57
+
58
+ index.freeze
83
59
 
84
60
  return super
85
61
  end
@@ -110,7 +86,25 @@ module Protocol
110
86
 
111
87
  def lookup(media_range)
112
88
  media_range = Range.build(media_range.type, media_range.subtype)
113
- return @index[media_range.name]
89
+
90
+ return index[media_range.name]
91
+ end
92
+
93
+ def index
94
+ return @index ||= compile(@entries)
95
+ end
96
+
97
+ def compile(entries)
98
+ index = {}
99
+
100
+ entries.each_value do |media_type, object|
101
+ # Preserve the first registration as the default for wildcard queries:
102
+ index["*/*"] ||= object
103
+ index["#{media_type.type}/*"] ||= object
104
+ index[media_type.name] = object
105
+ end
106
+
107
+ return index
114
108
  end
115
109
  end
116
110
  end
@@ -9,7 +9,7 @@ require "set"
9
9
 
10
10
  module Protocol
11
11
  module Media
12
- # An immutable set of compatible media ranges.
12
+ # A set of compatible media ranges.
13
13
  class Set
14
14
  include Enumerable
15
15
 
@@ -56,73 +56,18 @@ module Protocol
56
56
  def empty?
57
57
  return false
58
58
  end
59
+
60
+ # Whether this set matches every media range.
61
+ # @returns [Boolean]
62
+ def universal?
63
+ return true
64
+ end
59
65
  end
60
66
 
61
67
  ANY = Any.new.freeze
62
68
 
63
69
  private_constant :Any, :ANY
64
70
 
65
- # Incrementally constructs an immutable media set.
66
- class Builder
67
- # Initialize an empty media set builder.
68
- def initialize
69
- @types = {}
70
- end
71
-
72
- # Add a media range to the set under construction.
73
- # @parameter media_range [String | Object] The media range or compatible object.
74
- # @returns [self]
75
- def add(media_range)
76
- if @types.instance_of?(Any)
77
- return self
78
- end
79
-
80
- media_range = Range.for(media_range)
81
- media_range = Range.build(media_range.type, media_range.subtype)
82
- type = media_range.type.freeze
83
- subtype = media_range.subtype.freeze
84
-
85
- if type == "*"
86
- @types = ANY
87
- elsif subtype == "*"
88
- @types[type] = ANY
89
- elsif subtypes = @types[type]
90
- unless subtypes.instance_of?(Any)
91
- subtypes.add(subtype)
92
- end
93
- else
94
- @types[type] = ::Set.new([subtype])
95
- end
96
-
97
- return self
98
- end
99
-
100
- alias << add
101
-
102
- # Compile the current ranges into an immutable set.
103
- # @returns [Set] The immutable media set.
104
- def build
105
- if @types.instance_of?(Any)
106
- return @types
107
- end
108
-
109
- return Set.new(@types).freeze
110
- end
111
- end
112
-
113
- # Construct an immutable set using a builder.
114
- # @yields {|builder| ...} The mutable builder.
115
- # @returns [Set] The immutable media set.
116
- def self.build
117
- builder = Builder.new
118
-
119
- if block_given?
120
- yield builder
121
- end
122
-
123
- return builder.build
124
- end
125
-
126
71
  # Convert a sequence of ranges into a media set.
127
72
  #
128
73
  # Existing sets are returned unchanged, including their frozen state.
@@ -140,29 +85,58 @@ module Protocol
140
85
  return media_ranges
141
86
  end
142
87
 
143
- return build do |builder|
144
- media_ranges.each do |media_range|
145
- builder << media_range
146
- end
88
+ set = new(media_ranges)
89
+ set.freeze
90
+
91
+ if set.universal?
92
+ return ANY
147
93
  end
94
+
95
+ return set
148
96
  end
149
97
 
150
- # Initialize a new media set with the given type index.
98
+ # Initialize a new mutable media set with the given media ranges.
151
99
  #
152
- # The index is retained without copying or freezing it.
100
+ # Freezing the set compiles the ranges into an efficient immutable index.
153
101
  #
154
- # @parameter types [Hash] The indexed media ranges.
155
- def initialize(types)
156
- @types = types
102
+ # @parameter media_ranges [Enumerable] The existing media ranges.
103
+ def initialize(media_ranges = [])
104
+ @entries = []
105
+ @types = nil
106
+
107
+ media_ranges.each do |media_range|
108
+ add(media_range)
109
+ end
110
+ end
111
+
112
+ # Add a media range to the set under construction.
113
+ # @parameter media_range [String | Object] The media range or compatible object.
114
+ # @returns [self]
115
+ def add(media_range)
116
+ raise FrozenError, "can't modify frozen #{self.class}" if frozen?
117
+
118
+ media_range = normalize(media_range)
119
+
120
+ @entries << media_range
121
+ @types = nil
122
+
123
+ return self
157
124
  end
158
125
 
159
- # Freeze the media set and its index.
126
+ alias << add
127
+
128
+ # Freeze the media set, compiling its ranges into an efficient index.
160
129
  # @returns [self]
161
130
  def freeze
162
131
  return self if frozen?
163
132
 
164
- @types.each_value(&:freeze)
165
- @types.freeze
133
+ types = self.types
134
+ @entries = nil
135
+
136
+ unless types.instance_of?(Any)
137
+ types.each_value(&:freeze)
138
+ types.freeze
139
+ end
166
140
 
167
141
  return super
168
142
  end
@@ -174,13 +148,18 @@ module Protocol
174
148
  media_range = normalize(media_range)
175
149
  type = media_range.type
176
150
  subtype = media_range.subtype
151
+ types = self.types
152
+
153
+ if types.instance_of?(Any)
154
+ return true
155
+ end
177
156
 
178
157
  # A wildcard type matches any non-empty set:
179
158
  if type == "*"
180
- return !@types.empty?
159
+ return !types.empty?
181
160
  end
182
161
 
183
- subtypes = @types[type]
162
+ subtypes = types[type]
184
163
 
185
164
  # An unknown type cannot match:
186
165
  unless subtypes
@@ -206,10 +185,17 @@ module Protocol
206
185
  # Enumerate the canonical media ranges which define membership.
207
186
  # @yields {|media_range| ...} Each canonical media range.
208
187
  # @returns [Enumerator | self]
209
- def each
210
- return to_enum unless block_given?
188
+ def each(&block)
189
+ return to_enum unless block
190
+
191
+ types = self.types
192
+
193
+ if types.instance_of?(Any)
194
+ types.each(&block)
195
+ return self
196
+ end
211
197
 
212
- @types.each do |type, subtypes|
198
+ types.each do |type, subtypes|
213
199
  if subtypes.instance_of?(Any)
214
200
  yield Range.new(type, "*")
215
201
  else
@@ -225,7 +211,13 @@ module Protocol
225
211
  # The number of canonical membership ranges.
226
212
  # @returns [Integer]
227
213
  def size
228
- return @types.sum do |_type, subtypes|
214
+ types = self.types
215
+
216
+ if types.instance_of?(Any)
217
+ return types.size
218
+ end
219
+
220
+ return types.sum do |_type, subtypes|
229
221
  subtypes.size
230
222
  end
231
223
  end
@@ -233,11 +225,44 @@ module Protocol
233
225
  # Whether the set contains no media ranges.
234
226
  # @returns [Boolean]
235
227
  def empty?
236
- return @types.empty?
228
+ return types.empty?
229
+ end
230
+
231
+ # Whether this set matches every media range.
232
+ # @returns [Boolean]
233
+ def universal?
234
+ return types.instance_of?(Any)
237
235
  end
238
236
 
239
237
  private
240
238
 
239
+ def types
240
+ return @types ||= compile(@entries)
241
+ end
242
+
243
+ def compile(media_ranges)
244
+ types = {}
245
+
246
+ media_ranges.each do |media_range|
247
+ type = media_range.type.freeze
248
+ subtype = media_range.subtype.freeze
249
+
250
+ if type == "*"
251
+ return ANY
252
+ elsif subtype == "*"
253
+ types[type] = ANY
254
+ elsif subtypes = types[type]
255
+ unless subtypes.instance_of?(Any)
256
+ subtypes.add(subtype)
257
+ end
258
+ else
259
+ types[type] = ::Set.new([subtype])
260
+ end
261
+ end
262
+
263
+ return types
264
+ end
265
+
241
266
  def normalize(media_range)
242
267
  media_range = Range.for(media_range)
243
268
  return Range.build(media_range.type, media_range.subtype)
@@ -7,6 +7,6 @@
7
7
  module Protocol
8
8
  # Models media types used by internet protocols and data formats.
9
9
  module Media
10
- VERSION = "0.2.0"
10
+ VERSION = "0.3.0"
11
11
  end
12
12
  end
data/readme.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Provides a small representation of media types which can be shared by protocol implementations and registry backends.
4
4
 
5
+ [![Development Status](https://github.com/socketry/protocol-media/workflows/Test/badge.svg)](https://github.com/socketry/protocol-media/actions?workflow=Test)
6
+
5
7
  ## Usage
6
8
 
7
9
  Please see the [project documentation](https://socketry.github.io/protocol-media/) for more details.
@@ -10,6 +12,20 @@ Please see the [project documentation](https://socketry.github.io/protocol-media
10
12
 
11
13
  - [Content Negotiation](https://socketry.github.io/protocol-media/guides/content-negotiation/index) - This guide explains how to combine `protocol-media` with an HTTP parser to select an application representation.
12
14
 
15
+ ## Releases
16
+
17
+ Please see the [project releases](https://socketry.github.io/protocol-media/releases/index) for all releases.
18
+
19
+ ### v0.3.0
20
+
21
+ - Replace media map and set builders with mutable collections that can be frozen after construction.
22
+ - Simplify lazy compilation of media map and set indexes.
23
+
24
+ ### v0.2.1
25
+
26
+ - Introduce `Protocol::Media::Set` for compatible media range membership.
27
+ - Make `Protocol::Media::Map` and `Protocol::Media::Set` immutable, with builders for incremental construction and indexed lookups.
28
+
13
29
  ## See Also
14
30
 
15
31
  - [Protocol::Media::Registry](https://github.com/socketry/protocol-media-registry) provides registry data and indexed lookup by media type or file extension.
data/releases.md CHANGED
@@ -1,6 +1,11 @@
1
1
  # Releases
2
2
 
3
- ## Unreleased
3
+ ## v0.3.0
4
+
5
+ - Replace media map and set builders with mutable collections that can be frozen after construction.
6
+ - Simplify lazy compilation of media map and set indexes.
7
+
8
+ ## v0.2.1
4
9
 
5
10
  - Introduce `Protocol::Media::Set` for compatible media range membership.
6
11
  - Make `Protocol::Media::Map` and `Protocol::Media::Set` immutable, with builders for incremental construction and indexed lookups.
data.tar.gz.sig ADDED
Binary file
metadata CHANGED
@@ -1,12 +1,41 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: protocol-media
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  bindir: bin
9
- cert_chain: []
9
+ cert_chain:
10
+ - |
11
+ -----BEGIN CERTIFICATE-----
12
+ MIIE2DCCA0CgAwIBAgIBATANBgkqhkiG9w0BAQsFADBhMRgwFgYDVQQDDA9zYW11
13
+ ZWwud2lsbGlhbXMxHTAbBgoJkiaJk/IsZAEZFg1vcmlvbnRyYW5zZmVyMRIwEAYK
14
+ CZImiZPyLGQBGRYCY28xEjAQBgoJkiaJk/IsZAEZFgJuejAeFw0yMjA4MDYwNDUz
15
+ MjRaFw0zMjA4MDMwNDUzMjRaMGExGDAWBgNVBAMMD3NhbXVlbC53aWxsaWFtczEd
16
+ MBsGCgmSJomT8ixkARkWDW9yaW9udHJhbnNmZXIxEjAQBgoJkiaJk/IsZAEZFgJj
17
+ bzESMBAGCgmSJomT8ixkARkWAm56MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIB
18
+ igKCAYEAomvSopQXQ24+9DBB6I6jxRI2auu3VVb4nOjmmHq7XWM4u3HL+pni63X2
19
+ 9qZdoq9xt7H+RPbwL28LDpDNflYQXoOhoVhQ37Pjn9YDjl8/4/9xa9+NUpl9XDIW
20
+ sGkaOY0eqsQm1pEWkHJr3zn/fxoKPZPfaJOglovdxf7dgsHz67Xgd/ka+Wo1YqoE
21
+ e5AUKRwUuvaUaumAKgPH+4E4oiLXI4T1Ff5Q7xxv6yXvHuYtlMHhYfgNn8iiW8WN
22
+ XibYXPNP7NtieSQqwR/xM6IRSoyXKuS+ZNGDPUUGk8RoiV/xvVN4LrVm9upSc0ss
23
+ RZ6qwOQmXCo/lLcDUxJAgG95cPw//sI00tZan75VgsGzSWAOdjQpFM0l4dxvKwHn
24
+ tUeT3ZsAgt0JnGqNm2Bkz81kG4A2hSyFZTFA8vZGhp+hz+8Q573tAR89y9YJBdYM
25
+ zp0FM4zwMNEUwgfRzv1tEVVUEXmoFCyhzonUUw4nE4CFu/sE3ffhjKcXcY//qiSW
26
+ xm4erY3XAgMBAAGjgZowgZcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0O
27
+ BBYEFO9t7XWuFf2SKLmuijgqR4sGDlRsMC4GA1UdEQQnMCWBI3NhbXVlbC53aWxs
28
+ aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MC4GA1UdEgQnMCWBI3NhbXVlbC53aWxs
29
+ aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MA0GCSqGSIb3DQEBCwUAA4IBgQB5sxkE
30
+ cBsSYwK6fYpM+hA5B5yZY2+L0Z+27jF1pWGgbhPH8/FjjBLVn+VFok3CDpRqwXCl
31
+ xCO40JEkKdznNy2avOMra6PFiQyOE74kCtv7P+Fdc+FhgqI5lMon6tt9rNeXmnW/
32
+ c1NaMRdxy999hmRGzUSFjozcCwxpy/LwabxtdXwXgSay4mQ32EDjqR1TixS1+smp
33
+ 8C/NCWgpIfzpHGJsjvmH2wAfKtTTqB9CVKLCWEnCHyCaRVuKkrKjqhYCdmMBqCws
34
+ JkxfQWC+jBVeG9ZtPhQgZpfhvh+6hMhraUYRQ6XGyvBqEUe+yo6DKIT3MtGE2+CP
35
+ eX9i9ZWBydWb8/rvmwmX2kkcBbX0hZS1rcR593hGc61JR6lvkGYQ2MYskBveyaxt
36
+ Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
37
+ voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
38
+ -----END CERTIFICATE-----
10
39
  date: 1980-01-02 00:00:00.000000000 Z
11
40
  dependencies: []
12
41
  executables: []
metadata.gz.sig ADDED
Binary file