rfc-web-link 0.0.0 → 0.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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/README.adoc +108 -112
- data/lib/rfc/web/link/decoder.rb +6 -6
- data/lib/rfc/web/link/encoder.rb +19 -6
- data/lib/rfc/web/link/models/link.rb +11 -1
- data/lib/rfc/web/link/models/list.rb +6 -5
- data/lib/rfc/web/link/models/pair.rb +3 -6
- data/lib/rfc/web/link/parsers/header.rb +5 -4
- data/lib/rfc/web/link/parsers/line.rb +16 -6
- data/lib/rfc/web/link/parsers/list.rb +20 -22
- data/lib/rfc/web/link/parsers/pair.rb +10 -6
- data/lib/rfc/web/link/parsers.rb +31 -0
- data/lib/rfc/web/link.rb +2 -1
- data/rfc-web-link.gemspec +1 -1
- data.tar.gz.sig +0 -0
- metadata +2 -1
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e5349bcccb250a2fb639bc10efcecb8313d1f27a9bc7f0c16fdde8ee38d59058
|
|
4
|
+
data.tar.gz: 3c47df4d22c978196942d28a03ef7cc3f1bdb28fd0937086661fe82e3e522c34
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 248afa0c4f87e8f4b57188dd43441238686e40e6379961e3e83ca3abb01e173d6095fdb838409c80e92ae15cf785b3fd119bc85b50f2ebdd8fb4dd905ea3708e
|
|
7
|
+
data.tar.gz: ec99e8ab113533799c1532ca5a69ae0c1fa44a9363f3f5158aac1e69bb7910c0b3e6ff40e1af0342dc48f777f1f6d96946bd9d80beb2f7a14e82607b56e144bb
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/README.adoc
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
:figure-caption!:
|
|
4
4
|
|
|
5
5
|
:data_link: link:https://alchemists.io/articles/ruby_data[Data]
|
|
6
|
+
:enumerable_link: link:https://docs.ruby-lang.org/en/master/Enumerable.html[Enumerable]
|
|
6
7
|
:rfc_8288_link: link:https://www.rfc-editor.org/info/rfc8288[RFC 8288]
|
|
7
8
|
|
|
8
9
|
= RFC Web Link
|
|
@@ -22,7 +23,7 @@ toc::[]
|
|
|
22
23
|
|
|
23
24
|
* Implements {rfc_8288_link}.
|
|
24
25
|
* Parses web link headers into whole value objects for processing and/or inspection.
|
|
25
|
-
*
|
|
26
|
+
* Provides explicit or implicit casting of whole value objects into a web links.
|
|
26
27
|
|
|
27
28
|
== Requirements
|
|
28
29
|
|
|
@@ -66,14 +67,14 @@ The fastest way to get started is to create a new instance of the parser that yo
|
|
|
66
67
|
|
|
67
68
|
[source,ruby]
|
|
68
69
|
----
|
|
69
|
-
parser = RFC::Web::Link.new
|
|
70
|
+
parser = RFC::Web::Link.new "https://demo.io"
|
|
70
71
|
----
|
|
71
72
|
|
|
72
73
|
Then you can use the `parser` instance to parse HTTP headers and build lists you can interact with:
|
|
73
74
|
|
|
74
75
|
[source,ruby]
|
|
75
76
|
----
|
|
76
|
-
parser.call({"link" => "</articles>; rel=index"}
|
|
77
|
+
parser.call({"link" => "</articles>; rel=index"})
|
|
77
78
|
----
|
|
78
79
|
|
|
79
80
|
The above will yield the following set of links (in this case, only with a single link):
|
|
@@ -97,39 +98,67 @@ The above will yield the following set of links (in this case, only with a singl
|
|
|
97
98
|
>
|
|
98
99
|
----
|
|
99
100
|
|
|
100
|
-
As you can see, you get an immutable {data_link} whole value object for which you can directly access all attributes.
|
|
101
|
+
As you can see, you get an immutable {data_link} (whole value) object for which you can directly access all attributes.
|
|
101
102
|
|
|
102
103
|
=== Root URI
|
|
103
104
|
|
|
104
|
-
A root URI (`root_uri`) must be supplied in order to resolve all relative URIs as absolute URIs. Generally, the `root_uri` is your host (i.e. primary domain).
|
|
105
|
+
A root URI (`root_uri`) must be supplied in order to resolve all relative URIs as absolute URIs. Generally, the `root_uri` is your host (i.e. primary domain). There are a few ways you can supply the root URI:
|
|
106
|
+
|
|
107
|
+
*Initialization*
|
|
108
|
+
|
|
109
|
+
The following sets the root URI globally so you only have to define it once:
|
|
110
|
+
|
|
111
|
+
[source,ruby]
|
|
112
|
+
----
|
|
113
|
+
parser = RFC::Web::Link.new "https://demo.io"
|
|
114
|
+
----
|
|
115
|
+
|
|
116
|
+
*Call*
|
|
117
|
+
|
|
118
|
+
The following sets the root URI per call.
|
|
119
|
+
|
|
120
|
+
[source,ruby]
|
|
121
|
+
----
|
|
122
|
+
parser = RFC::Web::Link.new
|
|
123
|
+
parser.call({"link" => "</articles>; rel=index"}, root_uri: "https://demo.io")
|
|
124
|
+
----
|
|
125
|
+
|
|
126
|
+
*Call (override)*
|
|
127
|
+
|
|
128
|
+
The following sets the root URI globally but also allows you to override the global value with a custom value per call.
|
|
129
|
+
|
|
130
|
+
[source,ruby]
|
|
131
|
+
----
|
|
132
|
+
parser = RFC::Web::Link.new "https://demo.io"
|
|
133
|
+
|
|
134
|
+
parser.call({"link" => "</articles>; rel=index"}, root_uri: "https://alt.io")
|
|
135
|
+
----
|
|
105
136
|
|
|
106
137
|
=== Delimiters
|
|
107
138
|
|
|
108
|
-
|
|
139
|
+
Each delimiter provides the following behavior:
|
|
140
|
+
|
|
141
|
+
* *Semicolon* (`;`): Delimits each pair associated with a line.
|
|
142
|
+
* *Comma* (`,`): Delimits each line of a link.
|
|
109
143
|
|
|
110
|
-
|
|
111
|
-
* *Comma* (`,`): Used to delimit each line of a link.
|
|
144
|
+
They must be quoted when not used as delimiters.
|
|
112
145
|
|
|
113
146
|
=== Attributes
|
|
114
147
|
|
|
115
|
-
There are
|
|
148
|
+
There are six {rfc_8288_link} registered target attributes:
|
|
116
149
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
| rel | relation
|
|
124
|
-
| title | title
|
|
125
|
-
| type | type
|
|
126
|
-
|===
|
|
150
|
+
* anchor
|
|
151
|
+
* hreflang
|
|
152
|
+
* media
|
|
153
|
+
* rel
|
|
154
|
+
* title
|
|
155
|
+
* type
|
|
127
156
|
|
|
128
|
-
|
|
157
|
+
Each is described in detail below.
|
|
129
158
|
|
|
130
|
-
====
|
|
159
|
+
==== anchor
|
|
131
160
|
|
|
132
|
-
|
|
161
|
+
Optional but, when supplied, can be a single string, quoted with special characters, a relative URI, or an absolute URI. Here's a few examples:
|
|
133
162
|
|
|
134
163
|
* *Fragment*: `#footer`
|
|
135
164
|
* *Quoted (special characters)*: `"#overview,body,footer"`
|
|
@@ -142,10 +171,8 @@ Relative URIs will automatically be expanded into an absolute URIs when parsed.
|
|
|
142
171
|
|
|
143
172
|
[source,ruby]
|
|
144
173
|
----
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
root_uri: "https://demo.io"
|
|
148
|
-
)
|
|
174
|
+
parser = RFC::Web::Link.new "https://demo.io"
|
|
175
|
+
list = parser.call({"link" => "</articles>; anchor=#footer"})
|
|
149
176
|
|
|
150
177
|
# #<data RFC::Web::Link::Models::List:0x000016f0
|
|
151
178
|
# links = [
|
|
@@ -173,10 +200,8 @@ list.to_s
|
|
|
173
200
|
|
|
174
201
|
[source,ruby]
|
|
175
202
|
----
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
root_uri: "https://demo.io"
|
|
179
|
-
)
|
|
203
|
+
parser = RFC::Web::Link.new "https://demo.io"
|
|
204
|
+
list = parser.call({"link" => %(</articles>; anchor="#overview,body,footer")})
|
|
180
205
|
|
|
181
206
|
# #<data RFC::Web::Link::Models::List:0x00001660
|
|
182
207
|
# links = [
|
|
@@ -204,10 +229,8 @@ list.to_s
|
|
|
204
229
|
|
|
205
230
|
[source,ruby]
|
|
206
231
|
----
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
root_uri: "https://demo.io"
|
|
210
|
-
)
|
|
232
|
+
parser = RFC::Web::Link.new "https://demo.io"
|
|
233
|
+
list = parser.call({"link" => "</articles>; anchor=/policies"})
|
|
211
234
|
|
|
212
235
|
# #<data RFC::Web::Link::Models::List:0x000017c0
|
|
213
236
|
# links = [
|
|
@@ -235,10 +258,8 @@ list.to_s
|
|
|
235
258
|
|
|
236
259
|
[source,ruby]
|
|
237
260
|
----
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
root_uri: "https://demo.io"
|
|
241
|
-
)
|
|
261
|
+
parser = RFC::Web::Link.new "https://demo.io"
|
|
262
|
+
list = parser.call({"link" => "</articles>; anchor=https://demo.io/about"})
|
|
242
263
|
|
|
243
264
|
# #<data RFC::Web::Link::Models::List:0x00001890
|
|
244
265
|
# links = [
|
|
@@ -262,18 +283,16 @@ list.to_s
|
|
|
262
283
|
# "<https://demo.io/articles>; anchor=https://demo.io/about"
|
|
263
284
|
----
|
|
264
285
|
|
|
265
|
-
====
|
|
286
|
+
==== hreflang
|
|
266
287
|
|
|
267
|
-
|
|
288
|
+
Optional but, when supplied, is a hint indicating the language of the associated link. This _does not_ override the value of the `content-language` header. Example:
|
|
268
289
|
|
|
269
290
|
*Single*
|
|
270
291
|
|
|
271
292
|
[source,ruby]
|
|
272
293
|
----
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
root_uri: "https://demo.io"
|
|
276
|
-
)
|
|
294
|
+
parser = RFC::Web::Link.new "https://demo.io"
|
|
295
|
+
list = parser.call({"link" => "</articles>; hreflang=en"})
|
|
277
296
|
|
|
278
297
|
# #<data RFC::Web::Link::Models::List:0x00000890
|
|
279
298
|
# links = [
|
|
@@ -301,10 +320,8 @@ list.to_s
|
|
|
301
320
|
|
|
302
321
|
[source,ruby]
|
|
303
322
|
----
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
root_uri: "https://demo.io"
|
|
307
|
-
)
|
|
323
|
+
parser = RFC::Web::Link.new "https://demo.io"
|
|
324
|
+
list = parser.call({"link" => "</articles>; hreflang=en; hreflang=de"})
|
|
308
325
|
|
|
309
326
|
# #<data RFC::Web::Link::Models::List:0x000016c0
|
|
310
327
|
# links = [
|
|
@@ -335,16 +352,14 @@ list.to_s
|
|
|
335
352
|
# "<https://demo.io/articles>; hreflang=en; hreflang=de"
|
|
336
353
|
----
|
|
337
354
|
|
|
338
|
-
====
|
|
355
|
+
==== media
|
|
339
356
|
|
|
340
|
-
|
|
357
|
+
Optional but, when supplied, is a hint indicating the kind of media associated with the link. Example:
|
|
341
358
|
|
|
342
359
|
[source,ruby]
|
|
343
360
|
----
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
root_uri: "https://demo.io"
|
|
347
|
-
)
|
|
361
|
+
parser = RFC::Web::Link.new "https://demo.io"
|
|
362
|
+
list = parser.call({"link" => "</articles>; media=print"})
|
|
348
363
|
|
|
349
364
|
# #<data RFC::Web::Link::Models::List:0x000017d0
|
|
350
365
|
# links = [
|
|
@@ -368,18 +383,16 @@ list.to_s
|
|
|
368
383
|
# "<https://demo.io/articles>; media=print"
|
|
369
384
|
----
|
|
370
385
|
|
|
371
|
-
====
|
|
386
|
+
==== rel
|
|
372
387
|
|
|
373
|
-
|
|
388
|
+
Required and must be first defined. Example:
|
|
374
389
|
|
|
375
390
|
*Single*
|
|
376
391
|
|
|
377
392
|
[source,ruby]
|
|
378
393
|
----
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
root_uri: "https://demo.io"
|
|
382
|
-
)
|
|
394
|
+
parser = RFC::Web::Link.new "https://demo.io"
|
|
395
|
+
list = parser.call({"link" => "</articles>; rel=index"})
|
|
383
396
|
|
|
384
397
|
# #<data RFC::Web::Link::Models::List:0x00000890
|
|
385
398
|
# links = [
|
|
@@ -388,7 +401,7 @@ list = RFC::Web::Link.new.call(
|
|
|
388
401
|
# #<data RFC::Web::Link::Models::Pair:0x000008d0
|
|
389
402
|
# delimiter = "=",
|
|
390
403
|
# encoding = nil,
|
|
391
|
-
# key = "
|
|
404
|
+
# key = "rel",
|
|
392
405
|
# language = nil,
|
|
393
406
|
# value = "index"
|
|
394
407
|
# >
|
|
@@ -409,10 +422,8 @@ Short hand, for multiple relations, is supported as well. To use, each relation
|
|
|
409
422
|
|
|
410
423
|
[source,ruby]
|
|
411
424
|
----
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
root_uri: "https://demo.io"
|
|
415
|
-
)
|
|
425
|
+
parser = RFC::Web::Link.new "https://demo.io"
|
|
426
|
+
list = parser.call({"link" => %(</articles>; rel="one two three")})
|
|
416
427
|
|
|
417
428
|
# #<data RFC::Web::Link::Models::List:0x00000960
|
|
418
429
|
# links = [
|
|
@@ -462,9 +473,9 @@ list.to_s
|
|
|
462
473
|
|
|
463
474
|
Notice that the original relation short hand of `"one two three"` produced three unique links for each relation which allows you to type less while still producing three distinct links.
|
|
464
475
|
|
|
465
|
-
====
|
|
476
|
+
==== title
|
|
466
477
|
|
|
467
|
-
|
|
478
|
+
Optional but, when supplied, allows you to provide a human readable label for the associated link. This includes being able to encode and decode the value based on the delimiter used. For example, notice the difference in delimiters used below:
|
|
468
479
|
|
|
469
480
|
* *Plain* (`=`): `title=Demo` (example)
|
|
470
481
|
* *Encoded* (`*=`): `+title*=UTF-8'en'd%C3%A9j%C3%A0%20vu+` (example)
|
|
@@ -475,10 +486,8 @@ The title _must not_ appear more than once. If multiple occurrences are detected
|
|
|
475
486
|
|
|
476
487
|
[source,ruby]
|
|
477
488
|
----
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
root_uri: "https://demo.io"
|
|
481
|
-
)
|
|
489
|
+
parser = RFC::Web::Link.new "https://demo.io"
|
|
490
|
+
list = parser.call({"link" => "</articles>; title=Demo"})
|
|
482
491
|
|
|
483
492
|
# #<data RFC::Web::Link::Models::List:0x00000890
|
|
484
493
|
# links = [
|
|
@@ -506,10 +515,8 @@ list.to_s
|
|
|
506
515
|
|
|
507
516
|
[source,ruby]
|
|
508
517
|
----
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
root_uri: "https://demo.io"
|
|
512
|
-
)
|
|
518
|
+
parser = RFC::Web::Link.new "https://demo.io"
|
|
519
|
+
list = parser.call({"link" => "</articles>; title*=UTF-8'en'd%C3%A9j%C3%A0%20vu"})
|
|
513
520
|
|
|
514
521
|
# #<data RFC::Web::Link::Models::List:0x00000890
|
|
515
522
|
# links = [
|
|
@@ -533,16 +540,14 @@ list.to_s
|
|
|
533
540
|
# "<https://demo.io/articles>; title*=UTF-8'en'd%C3%A9j%C3%A0%20vu"
|
|
534
541
|
----
|
|
535
542
|
|
|
536
|
-
====
|
|
543
|
+
==== type
|
|
537
544
|
|
|
538
|
-
|
|
545
|
+
Optional but, when supplied, is a hint indicating the MIME Type of the associated link. This _does not_ override the value of the `content-type` header. Example:
|
|
539
546
|
|
|
540
547
|
[source,ruby]
|
|
541
548
|
----
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
root_uri: "https://demo.io"
|
|
545
|
-
)
|
|
549
|
+
parser = RFC::Web::Link.new "https://demo.io"
|
|
550
|
+
list = parser.call({"link" => "</articles>; type=text/html"})
|
|
546
551
|
|
|
547
552
|
# #<data RFC::Web::Link::Models::List:0x00000890
|
|
548
553
|
# links = [
|
|
@@ -572,9 +577,10 @@ Extensions allow you to extend your implementation beyond the registered target
|
|
|
572
577
|
|
|
573
578
|
[source,ruby]
|
|
574
579
|
----
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
580
|
+
parser = RFC::Web::Link.new "https://demo.io"
|
|
581
|
+
|
|
582
|
+
list = parser.call(
|
|
583
|
+
{"link" => %(</articles>; hint="A demo."; errata="For demonstration only."; copyright=2025)}
|
|
578
584
|
)
|
|
579
585
|
|
|
580
586
|
# #<data RFC::Web::Link::Models::List:0x00000980
|
|
@@ -617,10 +623,8 @@ You can also encode your extensions by using the `+*=+` delimiter. Example:
|
|
|
617
623
|
|
|
618
624
|
[source,ruby]
|
|
619
625
|
----
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
root_uri: "https://demo.io"
|
|
623
|
-
)
|
|
626
|
+
parser = RFC::Web::Link.new "https://demo.io"
|
|
627
|
+
list = parser.call({"link" => "</articles>; demo*=UTF-8'en'd%C3%A9j%C3%A0%20vu"})
|
|
624
628
|
|
|
625
629
|
# #<data RFC::Web::Link::Models::List:0x00000ad0
|
|
626
630
|
# links = [
|
|
@@ -650,7 +654,7 @@ You've already seen all of the models (whole value objects) used in the examples
|
|
|
650
654
|
|
|
651
655
|
==== List
|
|
652
656
|
|
|
653
|
-
This model encapsulates the list of links parsed from an HTTP link header or created by you. A list can be created multiple ways:
|
|
657
|
+
This model has both {data_link} and {enumerable_link} behavior because it encapsulates the list of links parsed from an HTTP link header or created by you. A list can be created multiple ways:
|
|
654
658
|
|
|
655
659
|
*All At Once*
|
|
656
660
|
|
|
@@ -679,23 +683,13 @@ list = RFC::Web::Link::Models::Link[uri: "https://demo.io"].add(pair)
|
|
|
679
683
|
list = RFC::Web::Link::Models::List.new.add(link)
|
|
680
684
|
----
|
|
681
685
|
|
|
682
|
-
Once you have a list, you can send the following messages
|
|
686
|
+
Once you have a list, you can send the following messages including {data_link} and {enumerable_link} messages:
|
|
683
687
|
|
|
684
688
|
[source,ruby]
|
|
685
689
|
----
|
|
686
690
|
list.add line
|
|
687
|
-
list.all?
|
|
688
|
-
list.any?
|
|
689
691
|
list.clear
|
|
690
|
-
list.each
|
|
691
692
|
list.empty?
|
|
692
|
-
list.find
|
|
693
|
-
list.include? line
|
|
694
|
-
list.map
|
|
695
|
-
list.none?
|
|
696
|
-
list.one?
|
|
697
|
-
list.reject
|
|
698
|
-
list.select
|
|
699
693
|
list.size
|
|
700
694
|
list.to_s
|
|
701
695
|
list.to_str
|
|
@@ -717,7 +711,7 @@ This model encapsulates a single HTTP link header. A link can be created multipl
|
|
|
717
711
|
|
|
718
712
|
[source,ruby]
|
|
719
713
|
----
|
|
720
|
-
|
|
714
|
+
link = RFC::Web::Link::Models::Link[
|
|
721
715
|
uri: "https://demo.io",
|
|
722
716
|
pairs: Set[
|
|
723
717
|
RFC::Web::Link::Models::Pair[key: :title, value: "Demo"]
|
|
@@ -732,7 +726,7 @@ list = RFC::Web::Link::Models::Link[
|
|
|
732
726
|
[source,ruby]
|
|
733
727
|
----
|
|
734
728
|
pair = RFC::Web::Link::Models::Pair[key: :title, value: "Demo"]
|
|
735
|
-
|
|
729
|
+
link = RFC::Web::Link::Models::Link[uri: "https://demo.io"].add(pair)
|
|
736
730
|
----
|
|
737
731
|
|
|
738
732
|
Once you have a link, you can then send the following messages (including {data_link} messages):
|
|
@@ -748,8 +742,11 @@ link.append :title,
|
|
|
748
742
|
delimiter: "*=",
|
|
749
743
|
encoding: "UTF-8",
|
|
750
744
|
language: "en"
|
|
751
|
-
link.
|
|
752
|
-
link.
|
|
745
|
+
link.find_pair key: "title", value: "Demo"
|
|
746
|
+
link.find_pair key: "title", value: /dem/i
|
|
747
|
+
link.find_pair key: "title", delimiter: /\*=/, value: "Demo"
|
|
748
|
+
link.pair? key: "title"
|
|
749
|
+
link.pair? key: "title", value: "Demo"
|
|
753
750
|
list.to_s
|
|
754
751
|
list.to_str
|
|
755
752
|
----
|
|
@@ -818,12 +815,11 @@ As with `List` and `Link`, you have full access to all {data_link} messages and
|
|
|
818
815
|
|
|
819
816
|
=== Error Handling
|
|
820
817
|
|
|
821
|
-
As per {rfc_8288_link}, this implementation is designed to safely ignore malformed web links. To illustrate, we'll start with an initialized parser
|
|
818
|
+
As per {rfc_8288_link}, this implementation is designed to safely ignore malformed web links. To illustrate, we'll start with an initialized parser:
|
|
822
819
|
|
|
823
820
|
[source,ruby]
|
|
824
821
|
----
|
|
825
|
-
parser = RFC::Web::Link.new
|
|
826
|
-
root_uri = "https://demo.io"
|
|
822
|
+
parser = RFC::Web::Link.new "https://demo.io"
|
|
827
823
|
----
|
|
828
824
|
|
|
829
825
|
The following examples show the result of parsing different malformed URIs:
|
|
@@ -832,7 +828,7 @@ The following examples show the result of parsing different malformed URIs:
|
|
|
832
828
|
|
|
833
829
|
[source,ruby]
|
|
834
830
|
----
|
|
835
|
-
parser.call({"link" => nil}
|
|
831
|
+
parser.call({"link" => nil})
|
|
836
832
|
|
|
837
833
|
# #<data RFC::Web::Link::Models::List:0x00000880
|
|
838
834
|
# links = []
|
|
@@ -843,7 +839,7 @@ parser.call({"link" => nil}, root_uri:)
|
|
|
843
839
|
|
|
844
840
|
[source,ruby]
|
|
845
841
|
----
|
|
846
|
-
parser.call({"link" => ""}
|
|
842
|
+
parser.call({"link" => ""})
|
|
847
843
|
|
|
848
844
|
# #<data RFC::Web::Link::Models::List:0x00000880
|
|
849
845
|
# links = []
|
|
@@ -854,7 +850,7 @@ parser.call({"link" => ""}, root_uri:)
|
|
|
854
850
|
|
|
855
851
|
[source,ruby]
|
|
856
852
|
----
|
|
857
|
-
parser.call({"link" => "/articles; rel=index"}
|
|
853
|
+
parser.call({"link" => "/articles; rel=index"})
|
|
858
854
|
|
|
859
855
|
# #<data RFC::Web::Link::Models::List:0x00000880
|
|
860
856
|
# links = []
|
|
@@ -865,7 +861,7 @@ parser.call({"link" => "/articles; rel=index"}, root_uri:)
|
|
|
865
861
|
|
|
866
862
|
[source,ruby]
|
|
867
863
|
----
|
|
868
|
-
parser.call({"link" => "</articles>"}
|
|
864
|
+
parser.call({"link" => "</articles>"})
|
|
869
865
|
|
|
870
866
|
# #<data RFC::Web::Link::Models::List:0x00000880
|
|
871
867
|
# links = [
|
|
@@ -881,7 +877,7 @@ parser.call({"link" => "</articles>"}, root_uri:)
|
|
|
881
877
|
|
|
882
878
|
[source,ruby]
|
|
883
879
|
----
|
|
884
|
-
parser.call({"link" => "</articles>; rel"}
|
|
880
|
+
parser.call({"link" => "</articles>; rel"})
|
|
885
881
|
|
|
886
882
|
# #<data RFC::Web::Link::Models::List:0x000008a0
|
|
887
883
|
# links = [
|
data/lib/rfc/web/link/decoder.rb
CHANGED
|
@@ -7,18 +7,18 @@ module RFC
|
|
|
7
7
|
module Link
|
|
8
8
|
# A HTTP header field decoder which adheres to RFC 8187.
|
|
9
9
|
class Decoder
|
|
10
|
-
def initialize delimiter: "'",
|
|
10
|
+
def initialize delimiter: "'", client: URI
|
|
11
11
|
@delimiter = delimiter
|
|
12
|
-
@default_encoding = default_encoding
|
|
13
12
|
@client = client
|
|
13
|
+
@quote = %(")
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def call text
|
|
17
17
|
value, encoding, language = parse text
|
|
18
18
|
|
|
19
19
|
value = client.decode_uri_component(value)
|
|
20
|
-
.force_encoding(encoding ||
|
|
21
|
-
.encode(
|
|
20
|
+
.force_encoding(encoding || Encoding::UTF_8)
|
|
21
|
+
.encode(Encoding::UTF_8)
|
|
22
22
|
|
|
23
23
|
{value:, encoding:, language:}
|
|
24
24
|
rescue ArgumentError, NoMethodError
|
|
@@ -27,10 +27,10 @@ module RFC
|
|
|
27
27
|
|
|
28
28
|
private
|
|
29
29
|
|
|
30
|
-
attr_reader :delimiter, :
|
|
30
|
+
attr_reader :delimiter, :client, :quote
|
|
31
31
|
|
|
32
32
|
def parse text
|
|
33
|
-
case String(text).split delimiter
|
|
33
|
+
case String(text).delete_prefix(quote).delete_suffix(quote).split delimiter
|
|
34
34
|
in [value] then [value, nil, nil]
|
|
35
35
|
in [encoding, language, value] then [value, encoding, language]
|
|
36
36
|
else [nil, nil, nil]
|
data/lib/rfc/web/link/encoder.rb
CHANGED
|
@@ -3,27 +3,40 @@
|
|
|
3
3
|
module RFC
|
|
4
4
|
module Web
|
|
5
5
|
module Link
|
|
6
|
-
# A HTTP header
|
|
6
|
+
# A HTTP header value encoder which adheres to RFC 8187.
|
|
7
7
|
class Encoder
|
|
8
|
-
def initialize
|
|
9
|
-
|
|
8
|
+
def initialize ignored_characters: /\A[0-9a-zA-Z!\#$%&'*+\-.^_`|~]*\z/,
|
|
9
|
+
ignored_keys: %w[type]
|
|
10
|
+
@ignored_characters = ignored_characters
|
|
11
|
+
@ignored_keys = ignored_keys
|
|
10
12
|
end
|
|
11
13
|
|
|
12
|
-
def call
|
|
14
|
+
def call value, key: nil
|
|
15
|
+
text = String value
|
|
16
|
+
|
|
17
|
+
if text.match?(ignored_characters) || ignored_keys.include?(key) then text
|
|
18
|
+
elsif text.match? ignored_characters_and_spaces then text.dump
|
|
19
|
+
else convert_characters(text).join
|
|
20
|
+
end
|
|
21
|
+
end
|
|
13
22
|
|
|
14
23
|
private
|
|
15
24
|
|
|
16
|
-
attr_reader :
|
|
25
|
+
attr_reader :ignored_characters, :ignored_keys
|
|
17
26
|
|
|
18
27
|
def convert_characters text
|
|
19
28
|
text.each_char.map do |character|
|
|
20
|
-
next character
|
|
29
|
+
next character if character.match? ignored_characters
|
|
21
30
|
|
|
22
31
|
character.bytes
|
|
23
32
|
.map { format "%%%02X", it }
|
|
24
33
|
.join
|
|
25
34
|
end
|
|
26
35
|
end
|
|
36
|
+
|
|
37
|
+
def ignored_characters_and_spaces
|
|
38
|
+
@ignored_characters_and_spaces ||= Regexp.new ignored_characters.source.sub("]", "\\s]")
|
|
39
|
+
end
|
|
27
40
|
end
|
|
28
41
|
end
|
|
29
42
|
end
|
|
@@ -26,7 +26,17 @@ module RFC
|
|
|
26
26
|
self
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
def
|
|
29
|
+
def find_pair **attributes
|
|
30
|
+
pairs.find do |pair|
|
|
31
|
+
attributes.all? { |key, value| pair.public_send(key).match? value }
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def pair? **attributes
|
|
36
|
+
pairs.any? do |pair|
|
|
37
|
+
attributes.all? { |key, value| pair.public_send(key).match? value }
|
|
38
|
+
end
|
|
39
|
+
end
|
|
30
40
|
|
|
31
41
|
def to_s(delimiter: "; ") = "<#{uri}>; #{pairs.join delimiter}"
|
|
32
42
|
|
|
@@ -9,8 +9,9 @@ module RFC
|
|
|
9
9
|
# Models a list of links.
|
|
10
10
|
List = Data.define :links do
|
|
11
11
|
extend Forwardable
|
|
12
|
+
include Enumerable
|
|
12
13
|
|
|
13
|
-
delegate %i[
|
|
14
|
+
delegate %i[empty? size] => :links
|
|
14
15
|
|
|
15
16
|
def initialize links: Set.new
|
|
16
17
|
super
|
|
@@ -26,11 +27,11 @@ module RFC
|
|
|
26
27
|
self
|
|
27
28
|
end
|
|
28
29
|
|
|
29
|
-
def each
|
|
30
|
+
def each
|
|
31
|
+
return enum_for :each unless block_given?
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def select(&) = with links: Set[*links.select(&)]
|
|
33
|
+
links.each { yield it }
|
|
34
|
+
end
|
|
34
35
|
|
|
35
36
|
def to_s(delimiter: ", ") = links.join delimiter
|
|
36
37
|
|
|
@@ -12,15 +12,12 @@ module RFC
|
|
|
12
12
|
|
|
13
13
|
def encoded? = delimiter == "*="
|
|
14
14
|
|
|
15
|
-
def to_s
|
|
16
|
-
encoder: Encoder.new
|
|
17
|
-
transformed_key = key_map.fetch(key) { it }
|
|
18
|
-
|
|
15
|
+
def to_s encoder: Encoder.new
|
|
19
16
|
if encoding
|
|
20
|
-
"#{
|
|
17
|
+
"#{key}#{delimiter}#{encoding}'#{language}'" \
|
|
21
18
|
"#{encoder.call value.encode(encoding)}"
|
|
22
19
|
else
|
|
23
|
-
"#{
|
|
20
|
+
"#{key}#{delimiter}#{encoder.call value, key:}"
|
|
24
21
|
end
|
|
25
22
|
end
|
|
26
23
|
|
|
@@ -6,19 +6,20 @@ module RFC
|
|
|
6
6
|
module Parsers
|
|
7
7
|
# Parses a header link into a list of records.
|
|
8
8
|
class Header
|
|
9
|
-
def initialize pattern: /link/i, list: List.new
|
|
9
|
+
def initialize root_uri = nil, pattern: /link/i, list: List.new
|
|
10
|
+
@root_uri = root_uri
|
|
10
11
|
@pattern = pattern
|
|
11
12
|
@list = list
|
|
12
13
|
end
|
|
13
14
|
|
|
14
|
-
def call headers, root_uri:
|
|
15
|
+
def call headers, root_uri: nil
|
|
15
16
|
text = headers.find { |key, value| break value if key.match? pattern }
|
|
16
|
-
list.call text, root_uri:
|
|
17
|
+
list.call text, root_uri: root_uri || self.root_uri
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
private
|
|
20
21
|
|
|
21
|
-
attr_reader :pattern, :list
|
|
22
|
+
attr_reader :root_uri, :pattern, :list
|
|
22
23
|
end
|
|
23
24
|
end
|
|
24
25
|
end
|
|
@@ -6,8 +6,8 @@ module RFC
|
|
|
6
6
|
module Parsers
|
|
7
7
|
# Parses a header line into a link record.
|
|
8
8
|
class Line
|
|
9
|
-
def initialize
|
|
10
|
-
@
|
|
9
|
+
def initialize patterns: PATTERNS, pair: Pair.new, model: Models::Link
|
|
10
|
+
@patterns = patterns
|
|
11
11
|
@pair = pair
|
|
12
12
|
@model = model
|
|
13
13
|
end
|
|
@@ -21,7 +21,9 @@ module RFC
|
|
|
21
21
|
|
|
22
22
|
private
|
|
23
23
|
|
|
24
|
-
attr_reader :
|
|
24
|
+
attr_reader :patterns, :pair, :model
|
|
25
|
+
|
|
26
|
+
def instance_variables_to_inspect = %i[@pair @model]
|
|
25
27
|
|
|
26
28
|
def process parts,
|
|
27
29
|
root_uri,
|
|
@@ -43,9 +45,17 @@ module RFC
|
|
|
43
45
|
end
|
|
44
46
|
|
|
45
47
|
def build_uri value, root_uri:
|
|
46
|
-
value.
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
value.match(uri_pattern)[:value].then do |uri|
|
|
49
|
+
uri.start_with?("/") ? "#{root_uri}#{uri}" : uri
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def uri_pattern
|
|
54
|
+
@uri_pattern ||= patterns.fetch :uri
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def delimiter
|
|
58
|
+
@delimiter ||= patterns.fetch :line_delimiter
|
|
49
59
|
end
|
|
50
60
|
end
|
|
51
61
|
end
|
|
@@ -8,37 +8,31 @@ module RFC
|
|
|
8
8
|
module Parsers
|
|
9
9
|
# Parses header links into a list of records.
|
|
10
10
|
class List
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
.+ # One or more characters.
|
|
14
|
-
\s+? # One or more spaces, lazy.
|
|
15
|
-
.+ # One or more characters.
|
|
16
|
-
(?<suffix>") # Suffix.
|
|
17
|
-
/x
|
|
18
|
-
|
|
19
|
-
def initialize relation_pattern: RELATION_PATTERN, line: Line.new, list: Models::List.new
|
|
20
|
-
@scanner = StringScanner.new ""
|
|
21
|
-
@relation_pattern = relation_pattern
|
|
11
|
+
def initialize patterns: PATTERNS, line: Line.new, list: Models::List.new
|
|
12
|
+
@patterns = patterns
|
|
22
13
|
@line = line
|
|
23
14
|
@list = list
|
|
15
|
+
@scanner = StringScanner.new ""
|
|
24
16
|
@comma = ","
|
|
25
17
|
@quote = %(")
|
|
26
18
|
end
|
|
27
19
|
|
|
28
20
|
def call text, root_uri:
|
|
29
|
-
|
|
21
|
+
match = String(text).match uri_pattern
|
|
30
22
|
list.clear
|
|
31
23
|
|
|
32
|
-
return list unless
|
|
24
|
+
return list unless match
|
|
33
25
|
|
|
34
|
-
scanner.string =
|
|
26
|
+
scanner.string = match.string
|
|
35
27
|
|
|
36
28
|
build_list root_uri
|
|
37
29
|
end
|
|
38
30
|
|
|
39
31
|
private
|
|
40
32
|
|
|
41
|
-
attr_reader :
|
|
33
|
+
attr_reader :patterns, :line, :list, :scanner, :comma, :quote
|
|
34
|
+
|
|
35
|
+
def instance_variables_to_inspect = %i[@line @list]
|
|
42
36
|
|
|
43
37
|
def build_list root_uri, buffer: +"", lines: []
|
|
44
38
|
check scanner.getch, buffer, lines until scanner.eos?
|
|
@@ -65,19 +59,23 @@ module RFC
|
|
|
65
59
|
buffer << %("#{scanner.pre_match[start..]}")
|
|
66
60
|
end
|
|
67
61
|
|
|
68
|
-
# rubocop:todo Metrics/AbcSize
|
|
69
62
|
def maybe_split_by_relation text, root_uri:
|
|
70
63
|
match = text.match relation_pattern
|
|
71
64
|
|
|
72
65
|
return list.add line.call(text, root_uri:) unless match
|
|
73
66
|
|
|
74
|
-
match.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
67
|
+
match[:value].split.each do |value|
|
|
68
|
+
list.add line.call(text.sub(relation_pattern, "rel=#{value}"), root_uri:)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def uri_pattern
|
|
73
|
+
@uri_pattern ||= patterns.fetch :uri
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def relation_pattern
|
|
77
|
+
@relation_pattern ||= patterns.fetch :relation
|
|
79
78
|
end
|
|
80
|
-
# rubocop:enable Metrics/AbcSize
|
|
81
79
|
end
|
|
82
80
|
end
|
|
83
81
|
end
|
|
@@ -6,16 +6,14 @@ module RFC
|
|
|
6
6
|
module Parsers
|
|
7
7
|
# Parses a header key/value pair into a record.
|
|
8
8
|
class Pair
|
|
9
|
-
def initialize
|
|
10
|
-
|
|
11
|
-
model: Models::Pair
|
|
12
|
-
@split_pattern = split_pattern
|
|
9
|
+
def initialize patterns: PATTERNS, decoder: Decoder.new, model: Models::Pair
|
|
10
|
+
@patterns = patterns
|
|
13
11
|
@decoder = decoder
|
|
14
12
|
@model = model
|
|
15
13
|
end
|
|
16
14
|
|
|
17
15
|
def call text, root_uri:
|
|
18
|
-
key, delimiter, value = text.split
|
|
16
|
+
key, delimiter, value = text.split delimiter_pattern
|
|
19
17
|
key.strip!
|
|
20
18
|
|
|
21
19
|
attributes = decoder.call value
|
|
@@ -28,7 +26,13 @@ module RFC
|
|
|
28
26
|
|
|
29
27
|
private
|
|
30
28
|
|
|
31
|
-
attr_reader :
|
|
29
|
+
attr_reader :patterns, :decoder, :model
|
|
30
|
+
|
|
31
|
+
def instance_variables_to_inspect = %i[@decoder @model]
|
|
32
|
+
|
|
33
|
+
def delimiter_pattern
|
|
34
|
+
@delimiter_pattern ||= patterns.fetch :pair_delimiter
|
|
35
|
+
end
|
|
32
36
|
end
|
|
33
37
|
end
|
|
34
38
|
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RFC
|
|
4
|
+
module Web
|
|
5
|
+
module Link
|
|
6
|
+
module Parsers
|
|
7
|
+
PATTERNS = {
|
|
8
|
+
uri: /
|
|
9
|
+
< # Prefix.
|
|
10
|
+
(?<value>.+) # Value named group.
|
|
11
|
+
> # Suffix.
|
|
12
|
+
/x,
|
|
13
|
+
relation: /
|
|
14
|
+
rel=" # Prefix.
|
|
15
|
+
(?<value>.+) # Value named group.
|
|
16
|
+
" # Suffix.
|
|
17
|
+
/x,
|
|
18
|
+
line_delimiter: /
|
|
19
|
+
; # Semicolon.
|
|
20
|
+
\s*? # Optional spaces, lazy.
|
|
21
|
+
/x,
|
|
22
|
+
pair_delimiter: /
|
|
23
|
+
(?<target>=) # Target named group.
|
|
24
|
+
| # Or.
|
|
25
|
+
(?<extended>\*=) # Extended named group.
|
|
26
|
+
/x
|
|
27
|
+
}.freeze
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
data/lib/rfc/web/link.rb
CHANGED
|
@@ -5,6 +5,7 @@ require "rfc/web/link/encoder"
|
|
|
5
5
|
require "rfc/web/link/models/link"
|
|
6
6
|
require "rfc/web/link/models/list"
|
|
7
7
|
require "rfc/web/link/models/pair"
|
|
8
|
+
require "rfc/web/link/parsers"
|
|
8
9
|
require "rfc/web/link/parsers/header"
|
|
9
10
|
require "rfc/web/link/parsers/line"
|
|
10
11
|
require "rfc/web/link/parsers/list"
|
|
@@ -14,7 +15,7 @@ module RFC
|
|
|
14
15
|
module Web
|
|
15
16
|
# Main namespace.
|
|
16
17
|
module Link
|
|
17
|
-
def self.new(**) = Parsers::Header.new(**)
|
|
18
|
+
def self.new(*, **) = Parsers::Header.new(*, **)
|
|
18
19
|
end
|
|
19
20
|
end
|
|
20
21
|
end
|
data/rfc-web-link.gemspec
CHANGED
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rfc-web-link
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brooke Kuhlmann
|
|
@@ -51,6 +51,7 @@ files:
|
|
|
51
51
|
- lib/rfc/web/link/models/link.rb
|
|
52
52
|
- lib/rfc/web/link/models/list.rb
|
|
53
53
|
- lib/rfc/web/link/models/pair.rb
|
|
54
|
+
- lib/rfc/web/link/parsers.rb
|
|
54
55
|
- lib/rfc/web/link/parsers/header.rb
|
|
55
56
|
- lib/rfc/web/link/parsers/line.rb
|
|
56
57
|
- lib/rfc/web/link/parsers/list.rb
|
metadata.gz.sig
CHANGED
|
Binary file
|