redhead 0.0.1
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.
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +445 -0
- data/lib/redhead.rb +37 -0
- data/lib/redhead/header.rb +83 -0
- data/lib/redhead/header_set.rb +134 -0
- data/lib/redhead/redhead_string.rb +62 -0
- data/rakefile +11 -0
- data/test/header_set_spec.rb +344 -0
- data/test/header_spec.rb +270 -0
- data/test/redhead_spec.rb +1 -0
- data/test/redhead_string_spec.rb +114 -0
- data/test/test_helper.rb +1 -0
- metadata +87 -0
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
(MIT License)
|
2
|
+
|
3
|
+
Copyright (c) 2010 Adam Prescott
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,445 @@
|
|
1
|
+
# What is it?
|
2
|
+
|
3
|
+
Redhead is for header metadata in strings, in the style of HTTP and email. It makes just a few assumptions about your header names and values, keeps them all as strings, and leaves the rest to you.
|
4
|
+
|
5
|
+
# How do I use this thing?
|
6
|
+
|
7
|
+
## Basics
|
8
|
+
|
9
|
+
A variable `post` referencing a simple string can be wrapped up in a `Redhead::String` object.
|
10
|
+
|
11
|
+
>> puts post
|
12
|
+
Title: Redhead is for headers!
|
13
|
+
Tags: redhead, ruby, headers
|
14
|
+
|
15
|
+
Since time immemorial, ...
|
16
|
+
|
17
|
+
>> post = Redhead::String[post]
|
18
|
+
=> +"Since time immemorial, ..."
|
19
|
+
|
20
|
+
>> post.headers
|
21
|
+
=> { { :title => "Redhead is for headers" }, { :tags => "redhead, ruby, headers" } }
|
22
|
+
|
23
|
+
>> post
|
24
|
+
=> +"Since time immemorial, ..."
|
25
|
+
|
26
|
+
>> post.to_s
|
27
|
+
=> "Since time immemorial, ..."
|
28
|
+
|
29
|
+
(Note that the `:tags` header has a string value, not an array!)
|
30
|
+
|
31
|
+
A Redhead::String is prefixed with `+` when inspecting it, so as to distinguish it from a proper `String` instance.
|
32
|
+
|
33
|
+
## Regular string functionality
|
34
|
+
|
35
|
+
A Redhead string has the functionality of a regular Ruby string.
|
36
|
+
|
37
|
+
>> post.split(/,/).first.reverse
|
38
|
+
=> "lairomemmi emit ecniS"
|
39
|
+
|
40
|
+
>> post.split(/ /).first
|
41
|
+
=> "Since"
|
42
|
+
|
43
|
+
# Modifies the receiver!
|
44
|
+
>> post.reverse!
|
45
|
+
=> "... ,lairomemmi emit ecniS"
|
46
|
+
|
47
|
+
>> post.to_s
|
48
|
+
=> "..., lairomemmi emit ecniS"
|
49
|
+
|
50
|
+
>> post.headers
|
51
|
+
=> { { :title => "Redhead is for headers" }, { :tags => "redhead, ruby, headers" } }
|
52
|
+
|
53
|
+
>> post.replace("Better content.")
|
54
|
+
=> "Better content."
|
55
|
+
|
56
|
+
>> post.to_s
|
57
|
+
=> "Better content."
|
58
|
+
|
59
|
+
>> post.headers
|
60
|
+
=> { { :title => "Redhead is for headers" }, { :tags => "redhead, ruby, headers" } }
|
61
|
+
|
62
|
+
Note that `String` instance methods which are not receiver-modifying will return proper `String` instances, and so headers will be lost.
|
63
|
+
|
64
|
+
## Accessing
|
65
|
+
|
66
|
+
In addition, you get access to headers.
|
67
|
+
|
68
|
+
>> post.headers[:title]
|
69
|
+
=> { :title => "Redhead is for headers!" }
|
70
|
+
|
71
|
+
>> post.headers[:tags].to_s
|
72
|
+
=> "redhead, ruby, headers"
|
73
|
+
|
74
|
+
>> post.headers.to_s
|
75
|
+
=> "Title: Redhead is for headers!\nTags: redhead, ruby, headers"
|
76
|
+
|
77
|
+
## Changing values
|
78
|
+
|
79
|
+
Modifying a header value is easy.
|
80
|
+
|
81
|
+
>> post.headers[:title] = "A change of title."
|
82
|
+
=> "A change of title."
|
83
|
+
|
84
|
+
>> post.headers[:title]
|
85
|
+
=> { :title => "A change of title." }
|
86
|
+
|
87
|
+
And changes will carry through:
|
88
|
+
|
89
|
+
>> post.headers.to_s
|
90
|
+
=> "Title: A change of title.\nTags: redhead, ruby, headers"
|
91
|
+
|
92
|
+
## Objects themselves
|
93
|
+
|
94
|
+
Alternatively, you can work with the header name-value object itself.
|
95
|
+
|
96
|
+
>> title_header = post.headers[:title]
|
97
|
+
=> { :title => "A change of title." }
|
98
|
+
|
99
|
+
>> title_header.value = "A better title."
|
100
|
+
=> "A better title."
|
101
|
+
|
102
|
+
>> title_header
|
103
|
+
=> { :title => "A better title." }
|
104
|
+
|
105
|
+
>> title_header.to_s
|
106
|
+
=> "Title: A better title."
|
107
|
+
|
108
|
+
## Adding
|
109
|
+
|
110
|
+
You can also create and add new headers, in a similar way to modifying an existing header.
|
111
|
+
|
112
|
+
>> post.headers[:awesome] = "very"
|
113
|
+
=> "very"
|
114
|
+
|
115
|
+
>> post.headers
|
116
|
+
=> { { :title => "A better title." }, { :tags => "redhead, ruby, headers" }, { :awesome => "very" } }
|
117
|
+
|
118
|
+
>> post.headers.to_s
|
119
|
+
=> "Title: A better title.\nTags: redhead, ruby, headers\nAwesome: very"
|
120
|
+
|
121
|
+
Since Ruby assignments always return the right-hand side, there is an alternative syntax which will return the created header.
|
122
|
+
|
123
|
+
>> post.headers.add(:amount_of_awesome, "high")
|
124
|
+
=> { :amount_of_awesome => "high" }
|
125
|
+
|
126
|
+
>> post.headers[:amount_of_awesome].to_s
|
127
|
+
=> "Amount-Of-Awesome: high"
|
128
|
+
|
129
|
+
## Deleting
|
130
|
+
|
131
|
+
Deleting headers is just as easy.
|
132
|
+
|
133
|
+
>> post.headers
|
134
|
+
=> { { :title => "A better title." }, { :tags => "redhead, ruby, headers" }, { :awesome => "very" }, { :amount_of_awesome => "high" } }
|
135
|
+
|
136
|
+
>> post.headers.delete(:amount_of_awesome)
|
137
|
+
=> { :amount_of_awesome => "high" }
|
138
|
+
|
139
|
+
>> post.headers
|
140
|
+
=> { { :title => "A better title." }, { :tags => "redhead, ruby, headers" }, { :awesome => "very" } }
|
141
|
+
|
142
|
+
# Finer points
|
143
|
+
|
144
|
+
There are conventions followed in creating a Redhead string and modifying certain values.
|
145
|
+
|
146
|
+
## Names and `:symbols`
|
147
|
+
|
148
|
+
By default, newly added header field names (i.e., the keys) will become capitalised and hyphen-separated to create their raw header name, so that the symbolic header name `:if_modified_since` becomes the raw header name `If-Modified-Since`. A similar process also happens when turning a string into a Redhead string (in reverse), and when using the default behaviour of `to_s`. To keep symbol names pleasant, by default, anything which isn't `A-Z`, `a-z` or `_` is converted into a `_` character. So `"Some! Long! Header! Name!"` becomes `:some_long_header_name` for accessing within Ruby.
|
149
|
+
|
150
|
+
For information on changing the formatting rules, see the section on <a href="#special_circumstances">special circumstances</a>.
|
151
|
+
|
152
|
+
## Header name memory
|
153
|
+
|
154
|
+
Original header names are remembered from the input string, and not simply created on-the-fly when using `to_s`. This is to make sure you get the same raw heading name, as a string, that you originally read in, instead of assuming you want it to be changed. Access as a Ruby object, however, is with a symbol by default.
|
155
|
+
|
156
|
+
If the original string is:
|
157
|
+
|
158
|
+
WaCKY fieLd NaME: value
|
159
|
+
|
160
|
+
String's content.
|
161
|
+
|
162
|
+
Then the header will be turned into a symbol:
|
163
|
+
|
164
|
+
>> str.headers
|
165
|
+
=> { { :wacky_field_name => "value" } }
|
166
|
+
|
167
|
+
But `to_s` will give the original:
|
168
|
+
|
169
|
+
>> str.headers.to_s
|
170
|
+
=> "WaCKY fieLd NaME: value"
|
171
|
+
|
172
|
+
If this had been dynamically produced, it would return `Wacky-Field-Name` by default.
|
173
|
+
|
174
|
+
If you'd prefer to just let Redhead give you a header name it produces, based off the symbolic header name, use `to_s!`:
|
175
|
+
|
176
|
+
>> str.headers.to_s!
|
177
|
+
=> "Wacky-Field-Name: value"
|
178
|
+
|
179
|
+
For more on this, see below.
|
180
|
+
|
181
|
+
<h2 id="special_circumstances">Special circumstances</h2>
|
182
|
+
|
183
|
+
While the default conventions should suit you, you may need to break them. This is for you.
|
184
|
+
|
185
|
+
## Caveats
|
186
|
+
|
187
|
+
Redhead has two main conversions which take place, related to header names. One is to convert a raw header name to what is by default a symbol, the other is to convert from the symbol back to the string, where necessary, for example when using `to_s!`. There may be unexpected behaviour if the symbolic header name does not convert to a raw header name, and back again, i.e., in pseudocode, if `to_symbolic_header_name(to_raw_header_name(some_header.key)) != some_header.key`.
|
188
|
+
|
189
|
+
>> str.headers
|
190
|
+
=> { { :awesome_rating => "quite" } }
|
191
|
+
|
192
|
+
>> output = str.headers.to_s(:awesome_rating => "Something-Completely-Different") + "\n\n" + str.to_s
|
193
|
+
=> "Something-Completely-Different: quite\n\nString's content."
|
194
|
+
|
195
|
+
>> input = Redhead::String[output]
|
196
|
+
=> "String's content."
|
197
|
+
|
198
|
+
>> input.headers
|
199
|
+
=> { { :something_completely_different => "quite" } }
|
200
|
+
|
201
|
+
>> input.headers[:awesome_rating]
|
202
|
+
=> nil
|
203
|
+
|
204
|
+
(See below for this use of `to_s`.)
|
205
|
+
|
206
|
+
There will, however, eventually be a situation where the raw header name needs a better symbolic reference name, or vice versa.
|
207
|
+
|
208
|
+
With the above caveats in mind, to actually change the raw header name, you can work with the header itself.
|
209
|
+
|
210
|
+
>> str.headers[:awesome] = "quite"
|
211
|
+
=> "quite"
|
212
|
+
|
213
|
+
>> awesome_header = str.headers[:awesome]
|
214
|
+
=> { :awesome => "quite" }
|
215
|
+
|
216
|
+
>> awesome_header.raw = "Some-Awe"
|
217
|
+
=> "Some-Awe"
|
218
|
+
|
219
|
+
>> awesome_header
|
220
|
+
=> { :awesome => "quite" }
|
221
|
+
|
222
|
+
>> awesome_header.to_s
|
223
|
+
=> "Some-Awe: quite"
|
224
|
+
|
225
|
+
>> str.headers.to_s
|
226
|
+
=> "Some-Awe: quite"
|
227
|
+
|
228
|
+
You can also change the symbolic header name in the same fashion.
|
229
|
+
|
230
|
+
# Delete to forget about the above
|
231
|
+
>> str.headers.delete(:awesome)
|
232
|
+
=> { :awesome => "quite" }
|
233
|
+
|
234
|
+
>> str.headers[:awesome] = "quite"
|
235
|
+
=> "quite"
|
236
|
+
|
237
|
+
>> awesome_header = str.headers[:awesome]
|
238
|
+
=> { :awesome => "quite" }
|
239
|
+
|
240
|
+
>> awesome_header.key = :different_kind_of_awesome
|
241
|
+
=> :different_kind_of_awesome
|
242
|
+
|
243
|
+
>> awesome_header
|
244
|
+
=> { :different_kind_of_awesome => "quite" }
|
245
|
+
|
246
|
+
>> awesome_header.to_s
|
247
|
+
=> "Awesome: quite"
|
248
|
+
|
249
|
+
The original symbolic header name will no longer work.
|
250
|
+
|
251
|
+
>> str.headers[:awesome]
|
252
|
+
=> nil
|
253
|
+
|
254
|
+
>> str.headers[:different_kind_of_awesome].key = :awesome
|
255
|
+
=> :awesome
|
256
|
+
|
257
|
+
>> awesome_header
|
258
|
+
=> { :awesome => "quite" }
|
259
|
+
|
260
|
+
>> awesome_header.to_s
|
261
|
+
=> "Awesome: quite"
|
262
|
+
|
263
|
+
>> str.headers[:different_kind_of_awesome]
|
264
|
+
=> nil
|
265
|
+
|
266
|
+
As a further option, there is `headers!`, which allows more one-step control, working with a hash argument. All _changed_ headers are returned.
|
267
|
+
|
268
|
+
>> str.headers
|
269
|
+
=> { { :awesome => "quite" } }
|
270
|
+
|
271
|
+
>> str.headers[:temp] = "temp"
|
272
|
+
=> "temp"
|
273
|
+
|
274
|
+
>> str.headers
|
275
|
+
=> { { :awesome => "quite" }, { :temp => "temp" } }
|
276
|
+
|
277
|
+
>> str.headers.to_s
|
278
|
+
=> "Some-Awe: quite\nTemp: temp"
|
279
|
+
|
280
|
+
>> str.headers!(:awesome => { :key => :awesome_rating, :raw => "Awesome-Rating" })
|
281
|
+
=> { { :awesome_rating => "quite" } }
|
282
|
+
|
283
|
+
>> str.headers
|
284
|
+
=> { { :awesome => "quite" }, { :temp => "temp" } }
|
285
|
+
|
286
|
+
Omitting one of `:raw` and `:key` will work as you expect.
|
287
|
+
|
288
|
+
## Non-destructive raw header changes
|
289
|
+
|
290
|
+
To work with a different raw header name, without modifying anything, you can pass a hash to `to_s`. This does not leave a side-effect and is only temporary.
|
291
|
+
|
292
|
+
>> str.headers
|
293
|
+
=> { { :awesome => "quite" }, { :temp => "temp" } }
|
294
|
+
|
295
|
+
>> str.headers.to_s
|
296
|
+
=> "Awesome-Rating: quite\nTemp: temp"
|
297
|
+
|
298
|
+
>> str.headers.to_s(:awesome => "Something-To-Do with Awesome-ness", :temp => "A very TEMPORARY header name")
|
299
|
+
=> "Something-To-Do with Awesome-ness: quite\nA very TEMPORARY header name"
|
300
|
+
|
301
|
+
# Nothing changed.
|
302
|
+
>> str.headers
|
303
|
+
=> { { :awesome => "quite" }, { :temp => "temp" } }
|
304
|
+
|
305
|
+
>> str.headers.to_s
|
306
|
+
=> "Awesome-Rating: quite\nTemp: temp"
|
307
|
+
|
308
|
+
## Mismatching at creation
|
309
|
+
|
310
|
+
The custom raw header name can also be given explicitly at creation time.
|
311
|
+
|
312
|
+
>> str.headers
|
313
|
+
=> { { :awesome => "quite" }, { :temp => "temp" } }
|
314
|
+
|
315
|
+
>> str.headers.delete(:temp)
|
316
|
+
=> { :temp => "temp" }
|
317
|
+
|
318
|
+
>> str.headers
|
319
|
+
=> { { :awesome => "quite" } }
|
320
|
+
|
321
|
+
>> str.heders.add(:temporary, "temp", "A-Rather-Temporary-Value")
|
322
|
+
=> { :temp => "temp" }
|
323
|
+
|
324
|
+
>> str.headers.to_s
|
325
|
+
=> "Awesome-Rating: quite\nA-Rather-Temporary-Value: temp"
|
326
|
+
|
327
|
+
# Enterprise Header Solutions
|
328
|
+
|
329
|
+
As mentioned, Redhead performs two conversions. One to produce a symbolic header name from the raw header name, and one to produce the raw header name from the symbolic header name. The symbolic -> raw process is used in `to_s!`, to force header names to be produced instead of using the header name remembered from when the header was created.
|
330
|
+
|
331
|
+
If you need to control the format of header names beyond the simple separator option given above, you can provide a block, the result of which is the name used for the raw or symbolic header name.
|
332
|
+
|
333
|
+
If that doesn't make a lot of sense on the first read, don't worry, here's some code.
|
334
|
+
|
335
|
+
>> string = "A-Header-Name: a header value\n\nContent."
|
336
|
+
=> "A-Header-Name: a header value\n\nContent."
|
337
|
+
|
338
|
+
>> str = Redhead::String.new(string) do |name|
|
339
|
+
?> name.split(/-/).join("").upcase.to_sym
|
340
|
+
?> end
|
341
|
+
=> +"Content."
|
342
|
+
|
343
|
+
>> str.headers
|
344
|
+
=> { { :AHEADERNAME => "a header value" } }
|
345
|
+
|
346
|
+
Note that this uses `Redhead::String.new` instead of `Redhead::String.[]` because of the block argument.
|
347
|
+
|
348
|
+
The above defines how symbolic headers are created in when creating the header objects. Using this approach, you can work with non-standard headers quite easily.
|
349
|
+
|
350
|
+
Note that `to_sym` is _not_ implicit, to suggest you use a pleasant symbol as the key.
|
351
|
+
|
352
|
+
It's also possible to specify the code to be used when calling `to_s`:
|
353
|
+
|
354
|
+
>> str.headers
|
355
|
+
=> { { :AHEADERNAME => "a header value" } }
|
356
|
+
|
357
|
+
>> str.headers.to_s do |name|
|
358
|
+
?> name.to_s.downcase.scan(/..?.?/).join("-").capitalize
|
359
|
+
?> end
|
360
|
+
=> "ahe-ade-rna-me: a header value"
|
361
|
+
|
362
|
+
The block to `to_s` will not modify the headers in-place, in keeping with the behaviour of the block-less `to_s`. To change how the symbolic-to-raw header name conversion works, you can do so on the object holding the headers.
|
363
|
+
|
364
|
+
>> str.headers.to_raw = lambda do |name|
|
365
|
+
?> name.to_s.downcase.scan(/..?.?/).join("-").capitalize
|
366
|
+
?> end
|
367
|
+
=> #<Proc:...>
|
368
|
+
|
369
|
+
Similarly, you can modify `to_key`. You can also change `to_raw` and `to_key` for each individual header. If no block is given for a specific header, it defaults to the block for the containing `headers` object. If nothing is given _there_, then it goes to the default.
|
370
|
+
|
371
|
+
If `to_raw(produced_key) != original_key` for all the headers in the object, then the headers are in a mismatched state. Equally, a single header is in a mismatched state if the condition fails for that header.
|
372
|
+
|
373
|
+
This can be checked with `reversible?`.
|
374
|
+
|
375
|
+
>> string = "A-Header-Name: a header value\n\nContent."
|
376
|
+
=> "A-Header-Name: a header value\n\nContent."
|
377
|
+
|
378
|
+
>> str = Redhead::String.new(string) do |name|
|
379
|
+
?> name.gsub(/-/, "").upcase.to_sym
|
380
|
+
?> end
|
381
|
+
=> +"Content."
|
382
|
+
|
383
|
+
# At this point, `to_key` is not reversible via `to_raw`
|
384
|
+
|
385
|
+
>> str.headers.reversible?
|
386
|
+
=> false
|
387
|
+
|
388
|
+
>> str = Redhead::String.new(string) do |name|
|
389
|
+
?> name.split(/-/).map { |e| e.upcase }.join("zzz").to_sym
|
390
|
+
?> end
|
391
|
+
=> +"Content."
|
392
|
+
|
393
|
+
>> str.headers
|
394
|
+
=> { { :AzzzHEADERzzzNAME => "a header value" } }
|
395
|
+
|
396
|
+
>> str.headers.reversible?
|
397
|
+
=> false
|
398
|
+
|
399
|
+
>> str.headers.to_raw = lambda do |name|
|
400
|
+
?> name.to_s.split(/zzz/).map { |e| e.capitalize }.join("-")
|
401
|
+
?> end
|
402
|
+
=> #<Proc:...>
|
403
|
+
|
404
|
+
# We can go back and forth without issue on this string
|
405
|
+
|
406
|
+
>> str.headers.reversible?
|
407
|
+
=> true
|
408
|
+
|
409
|
+
>> str.headers
|
410
|
+
=> { { :AzzzHEADERzzzzNAME => "a header value" } }
|
411
|
+
|
412
|
+
>> str.headers.to_s
|
413
|
+
=> "A-Header-Name: a header value"
|
414
|
+
|
415
|
+
Reversibility is checked by calling `reversible?` on all the headers in `str.headers`, since each header can have its own `to_key` and `to_raw` blocks. `reversible?` returning false will not raise an error or stop execution.
|
416
|
+
|
417
|
+
When creating new headers, `to_raw`, is used, meaning your custom block will be picked up and used to create the raw header as though it had been created from a string.
|
418
|
+
|
419
|
+
>> str.headers.to_raw = proc { "Genuinely" }
|
420
|
+
|
421
|
+
>> str.headers[:foo_bar] = "temp"
|
422
|
+
=> "temp"
|
423
|
+
|
424
|
+
>> temp_header = str.headers[:foo_bar]
|
425
|
+
=> { :foo_bar => "temp" }
|
426
|
+
|
427
|
+
>> temp_header.to_s
|
428
|
+
=> "Genuinely: temp"
|
429
|
+
|
430
|
+
Changing `to_raw` after-the-fact will not change the raw header name stored for the object. To force `to_raw` to be used instead of the stored value, use `to_s!`, which _always_ uses `to_raw`.
|
431
|
+
|
432
|
+
>> temp_header.to_raw = lambda { "nothing meaningful" }
|
433
|
+
=> #<Proc:...>
|
434
|
+
|
435
|
+
>> temp_header.to_s
|
436
|
+
=> "Temp-Orary-Header: temp"
|
437
|
+
|
438
|
+
>> temp_header.to_s!
|
439
|
+
=> "nothing meaningful: temp"
|
440
|
+
|
441
|
+
# TODO
|
442
|
+
|
443
|
+
Headers on different lines with the same raw name. Important for HTTP.
|
444
|
+
|
445
|
+
Improve docs.
|