redhead 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|