hermeneutics 1.10 → 1.13

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,57 +7,6 @@ require "hermeneutics/contents"
7
7
  require "hermeneutics/addrs"
8
8
 
9
9
 
10
- class NilClass
11
- def eat_lines
12
- end
13
- def rewind
14
- end
15
- end
16
- class String
17
- def eat_lines
18
- @pos ||= 0
19
- while @pos < length do
20
- p = index /.*\n?/, @pos
21
- l = $&.length
22
- begin
23
- yield self[ @pos, l]
24
- ensure
25
- @pos += l
26
- end
27
- end
28
- end
29
- def rewind
30
- @pos = 0
31
- end
32
- end
33
- class Array
34
- def eat_lines &block
35
- @pos ||= 0
36
- while @pos < length do
37
- begin
38
- self[ @pos].eat_lines &block
39
- ensure
40
- @pos += 1
41
- end
42
- end
43
- end
44
- def rewind
45
- each { |e| e.rewind }
46
- @pos = 0
47
- end
48
- end
49
- class IO
50
- def eat_lines &block
51
- each_line &block
52
- nil
53
- end
54
- def to_s
55
- rewind
56
- read
57
- end
58
- end
59
-
60
-
61
10
  module Hermeneutics
62
11
 
63
12
  class Multipart < Mime
@@ -67,65 +16,6 @@ module Hermeneutics
67
16
  class IllegalBoundary < StandardError ; end
68
17
  class ParseError < StandardError ; end
69
18
 
70
- # :stopdoc:
71
- class PartFile
72
- class <<self
73
- def open file, sep
74
- i = new file, sep
75
- yield i
76
- end
77
- private :new
78
- end
79
- public
80
- attr_reader :prolog, :epilog
81
- def initialize file, sep
82
- @file = file
83
- @sep = /^--#{Regexp.quote sep}(--)?/
84
- read_part
85
- @prolog = norm_nl @a
86
- end
87
- def next_part
88
- return if @epilog
89
- read_part
90
- @a.first.chomp!
91
- true
92
- end
93
- def eat_lines
94
- yield @a.pop while @a.any?
95
- end
96
- private
97
- def read_part
98
- @a = []
99
- e = nil
100
- @file.eat_lines { |l|
101
- l =~ @sep rescue nil
102
- if $& then
103
- e = [ $'] if $1
104
- @a.reverse!
105
- return
106
- end
107
- @a.push l
108
- }
109
- raise ParseError, "Missing separator #@sep"
110
- ensure
111
- if e then
112
- @file.eat_lines { |l| e.push l }
113
- e.reverse!
114
- @epilog = norm_nl e
115
- end
116
- end
117
- def norm_nl a
118
- r = ""
119
- while a.any? do
120
- l = a.pop
121
- l.chomp! and l << $/
122
- r << l
123
- end
124
- r
125
- end
126
- end
127
- # :startdoc:
128
-
129
19
  public
130
20
 
131
21
  class <<self
@@ -133,14 +23,15 @@ module Hermeneutics
133
23
  def parse input, **parameters
134
24
  b = parameters[ :boundary]
135
25
  b or raise ParseError, "Missing boundary parameter."
136
- PartFile.open input, b do |partfile|
137
- list = []
138
- while partfile.next_part do
139
- m = Message.parse partfile
140
- list.push m
141
- end
142
- new b, partfile.prolog, list, partfile.epilog
143
- end
26
+ list = input.split /^--#{Regexp.quote b}/
27
+ prolog = list.shift
28
+ epilog = list.pop
29
+ epilog and epilog.slice! /\A--\n/ or raise "Missing last separator."
30
+ list.each { |p|
31
+ p.slice! /\A\n/ or raise "Malformed separator."
32
+ }
33
+ list.map! { |t| Message.parse t }
34
+ new b, prolog, list, epilog
144
35
  end
145
36
 
146
37
  end
@@ -164,7 +55,7 @@ module Hermeneutics
164
55
 
165
56
  def inspect
166
57
  r = ""
167
- r << "#<#{cls}:"
58
+ r << "#<#{self.class}:"
168
59
  r << "0x%x" % (object_id<<1)
169
60
  r << " n=#{@list.length}"
170
61
  r << ">"
@@ -181,10 +72,10 @@ module Hermeneutics
181
72
  s = p.to_s
182
73
  s =~ re rescue nil
183
74
  $& and raise IllegalBoundary
184
- r << splitter << $/ << s << $/
75
+ r << splitter << "\n" << s
185
76
  }
186
77
  @epilog =~ re and raise IllegalBoundary
187
- r << splitter << "--" << @epilog
78
+ r << splitter << "--\n" << @epilog
188
79
  rescue IllegalBoundary
189
80
  boundary!
190
81
  retry
@@ -209,111 +100,140 @@ module Hermeneutics
209
100
 
210
101
  class ParseError < StandardError ; end
211
102
 
212
- class Headers
103
+ class Header
213
104
 
214
- class Entry
215
- LINE_LENGTH = 78
216
- INDENT = " "
217
- class <<self
218
- private :new
219
- def parse str
220
- str =~ /:\s*/ or
221
- raise ParseError, "Header line without a colon: #{str}"
222
- data = $'
223
- new $`, $&, data
224
- end
225
- def create name, *contents
226
- name = build_name name
227
- i = new name.to_s, ": ", nil
228
- i.set *contents
229
- end
230
- def build_name name
231
- n = name.to_s
232
- unless n.equal? name then
233
- n.gsub! /_/, "-"
234
- n.gsub! /\b[a-z]/ do |c| c.upcase end
235
- end
236
- n
237
- end
105
+ @line_max = 78
106
+ @indent = 4
107
+
108
+ class <<self
109
+
110
+ attr_accessor :line_max, :indent
111
+
112
+ private :new
113
+
114
+ def parse str
115
+ str =~ /:\s*/ or
116
+ raise ParseError, "Header line without a colon: #{str}"
117
+ new $`, $'
238
118
  end
239
- attr_reader :name, :sep, :data
240
- def initialize name, sep, data
241
- @name, @sep, @data, @contents = name, sep, data
119
+
120
+ def create name, *contents
121
+ name = build_name name
122
+ i = new name.to_s, nil
123
+ i.set *contents
242
124
  end
243
- def to_s
244
- "#@name#@sep#@data"
125
+
126
+ def build_name name
127
+ n = name.to_s
128
+ unless n.equal? name then
129
+ n.gsub! /_/, "-"
130
+ n.gsub! /\b[a-z]/ do |c| c.upcase end
131
+ end
132
+ n
245
133
  end
246
- def contents type
247
- if type then
248
- unless @contents and @contents.is_a? type then
134
+
135
+ end
136
+
137
+ attr_reader :name, :data
138
+
139
+ def initialize name, data
140
+ @name, @data, @contents = name, data
141
+ end
142
+
143
+ def to_s
144
+ "#@name: #@data"
145
+ end
146
+
147
+ def contents type = nil
148
+ if type then
149
+ if @contents then
150
+ if not @contents.is_a? type then
249
151
  @contents = type.parse @data
250
152
  end
251
- @contents
252
153
  else
253
- @data
254
- end
255
- end
256
- def name_is? name
257
- (@name.casecmp name).zero?
258
- end
259
- def set *contents
260
- type, *args = *contents
261
- d = case type
262
- when Class then
263
- @contents = type.new *args
264
- case (e = @contents.encode)
265
- when Array then e
266
- when nil then []
267
- else [ e]
268
- end
269
- when nil then
270
- @contents = nil
271
- split_args args
272
- else
273
- @contents = nil
274
- split_args contents
154
+ @contents = type.parse @data
275
155
  end
276
- @data = mk_lines d
277
- self
278
- end
279
- def reset type
280
- if type then
281
- c = contents type
282
- @data = mk_lines c.encode if c
156
+ else
157
+ unless @contents then
158
+ @contents = @data
159
+ @contents.strip!
160
+ @contents.gsub! /\s\+/m, " "
283
161
  end
284
- self
285
162
  end
286
- private
287
- def mk_lines strs
288
- m = LINE_LENGTH - @name.length - @sep.length
289
- data = ""
290
- strs.each { |e|
291
- unless data.empty? then
292
- if 1 + e.length <= m then
293
- data << " "
294
- m -= 1
295
- else
296
- data << $/ << INDENT
297
- m = LINE_LENGTH - INDENT.length
298
- end
163
+ @contents
164
+ end
165
+
166
+ def name_is? name
167
+ (@name.casecmp name).zero?
168
+ end
169
+
170
+ def set *contents
171
+ type, *args = *contents
172
+ d = case type
173
+ when Class then
174
+ @contents = type.new *args
175
+ case (e = @contents.encode)
176
+ when Array then e
177
+ when nil then []
178
+ else [ e]
299
179
  end
300
- data << e
301
- m -= e.length
302
- }
303
- data
180
+ when nil then
181
+ @contents = nil
182
+ split_args args
183
+ else
184
+ @contents = nil
185
+ split_args contents
304
186
  end
305
- def split_args ary
306
- r = []
307
- ary.each { |a|
308
- r.concat case a
309
- when Array then split_args a
310
- else a.to_s.split
311
- end
312
- }
313
- r
187
+ @data = mk_lines d
188
+ self
189
+ end
190
+
191
+ def reset type
192
+ d = if type then
193
+ (contents type).encode
194
+ else
195
+ @contents
314
196
  end
197
+ @data = mk_lines d
198
+ self
199
+ end
200
+
201
+ private
202
+
203
+ def mk_lines strs
204
+ m = self.class.line_max - @name.length - 2 # 2 == ": ".size
205
+ data = ""
206
+ strs.each { |e|
207
+ unless data.empty? then
208
+ if 1 + e.length <= m then
209
+ data << " "
210
+ m -= 1
211
+ else
212
+ data << "\n" << (" "*self.class.indent)
213
+ m = self.class.line_max - self.class.indent
214
+ end
215
+ end
216
+ data << e
217
+ m -= e.length
218
+ }
219
+ data
220
+ end
221
+
222
+ def split_args ary
223
+ r = []
224
+ ary.each { |a|
225
+ r.concat case a
226
+ when Array then split_args a
227
+ else a.to_s.split
228
+ end
229
+ }
230
+ r
315
231
  end
316
232
 
233
+ end
234
+
235
+ class Headers
236
+
317
237
  @types = {
318
238
  "Content-Type" => ContentType,
319
239
  "To" => AddrList,
@@ -345,7 +265,7 @@ module Hermeneutics
345
265
 
346
266
  class <<self
347
267
  def set_field_type name, type
348
- e = Entry.create name
268
+ e = Header.create name
349
269
  if type then
350
270
  @types[ e.name] = type
351
271
  else
@@ -364,7 +284,7 @@ module Hermeneutics
364
284
  private :new
365
285
  def parse *list
366
286
  list.flatten!
367
- list.map! { |h| Entry.parse h }
287
+ list.map! { |h| Header.parse h }
368
288
  new list
369
289
  end
370
290
  def create
@@ -382,17 +302,28 @@ module Hermeneutics
382
302
  alias size length
383
303
 
384
304
  def to_s
385
- @list.map { |e| "#{e}#$/" }.join
305
+ @list.map { |e| "#{e}\n" }.join
306
+ end
307
+
308
+ private
309
+
310
+ def header_contents entry, type = nil
311
+ type ||= Headers.find_type entry
312
+ entry.contents type
386
313
  end
387
314
 
315
+ public
316
+
388
317
  def each
389
- @list.each { |e|
390
- type = Headers.find_type e
391
- c = e.contents type
392
- yield e.name, c
393
- }
318
+ @list.each { |e| yield e.name, (header_contents e) }
394
319
  self
395
320
  end
321
+ include Enumerable
322
+
323
+ def has? name
324
+ e = find_entry name
325
+ not e.nil?
326
+ end
396
327
 
397
328
  def raw name
398
329
  e = find_entry name
@@ -401,10 +332,7 @@ module Hermeneutics
401
332
 
402
333
  def field name, type = nil
403
334
  e = find_entry name
404
- if e then
405
- type ||= Headers.find_type e
406
- e.contents type
407
- end
335
+ header_contents e, type if e
408
336
  end
409
337
  def [] name, type = nil
410
338
  case name
@@ -413,36 +341,92 @@ module Hermeneutics
413
341
  end
414
342
  end
415
343
 
416
- def method_missing sym, *args
344
+ private
345
+
346
+ def method_missing sym, type = nil, *args
417
347
  if args.empty? and not sym =~ /[!?=]\z/ then
418
- field sym, *args
348
+ field sym, type
419
349
  else
420
350
  super
421
351
  end
422
352
  end
423
353
 
424
- def add name, *contents
354
+ public
355
+
356
+ def insert name, *contents
425
357
  e = build_entry name, *contents
426
- add_entry e
358
+ @list.unshift e
427
359
  self
428
360
  end
429
361
 
430
- def replace name, *contents
362
+ def add name, *contents
431
363
  e = build_entry name, *contents
432
- remove_entries e
433
- add_entry e
364
+ @list.push e
434
365
  self
435
366
  end
436
367
 
437
- def remove name
438
- e = Entry.create name
439
- remove_entries e
368
+ def remove name, type = nil
369
+ block_given? or return remove name, type do |_| true end
370
+ pat = Header.create name
371
+ @list.reject! { |e|
372
+ e.name_is? pat.name and yield (header_contents e, type)
373
+ }
440
374
  self
441
375
  end
442
376
  alias delete remove
443
377
 
378
+ def compact name, type = nil
379
+ remove name, type do |c| c.empty? end
380
+ end
381
+ alias remove_empty compact
382
+
383
+ def replace name, *contents
384
+ block_given? or return replace name, *contents do true end
385
+ entry = build_entry name, *contents
386
+ @list.map! { |e|
387
+ if e.name_is? entry.name and yield (header_contents e) then
388
+ entry
389
+ else
390
+ e
391
+ end
392
+ }
393
+ self
394
+ end
395
+
396
+ def replace_all name, type = nil
397
+ pat = Header.create name
398
+ type ||= Headers.find_type pat
399
+ @list.map! { |e|
400
+ if e.name_is? pat.name then
401
+ c = e.contents type
402
+ y = yield c
403
+ if y.equal? c then
404
+ e.reset type
405
+ else
406
+ y = [ type, *y] if type
407
+ next build_entry name, *y
408
+ end
409
+ end
410
+ e
411
+ }
412
+ self
413
+ end
414
+
415
+ def replace_add name, *contents
416
+ entry = build_entry name, *contents
417
+ replace_all name do |c|
418
+ if entry then
419
+ c.push entry.contents
420
+ entry = nil
421
+ end
422
+ c
423
+ end
424
+ @list.push entry if entry
425
+ self
426
+ end
427
+
444
428
  def recode name, type = nil
445
- n = Entry.build_name name
429
+ n = Header.build_name name
446
430
  @list.each { |e|
447
431
  next unless e.name_is? n
448
432
  type ||= Headers.find_type e
@@ -453,7 +437,7 @@ module Hermeneutics
453
437
 
454
438
  def inspect
455
439
  r = ""
456
- r << "#<#{cls}:"
440
+ r << "#<#{self.class}:"
457
441
  r << "0x%x" % (object_id<<1)
458
442
  r << " (#{length})"
459
443
  r << ">"
@@ -462,12 +446,12 @@ module Hermeneutics
462
446
  private
463
447
 
464
448
  def find_entry name
465
- e = Entry.build_name name
449
+ e = Header.build_name name
466
450
  @list.find { |x| x.name_is? e }
467
451
  end
468
452
 
469
453
  def build_entry name, *contents
470
- e = Entry.create name
454
+ e = Header.create name
471
455
  type, = *contents
472
456
  case type
473
457
  when Class then
@@ -479,14 +463,6 @@ module Hermeneutics
479
463
  e
480
464
  end
481
465
 
482
- def add_entry entry
483
- @list.unshift entry
484
- end
485
-
486
- def remove_entries entry
487
- @list.reject! { |e| e.name_is? entry.name }
488
- end
489
-
490
466
  end
491
467
 
492
468
  class <<self
@@ -506,12 +482,13 @@ module Hermeneutics
506
482
  private
507
483
 
508
484
  def parse_hb input
509
- h = parse_headers input
485
+ hinput, input = input.split /^\n/, 2
486
+ h = parse_headers hinput
510
487
  c = h.content_type
511
488
  b = c.parse_mime input if c
512
489
  unless b then
513
490
  b = ""
514
- input.eat_lines { |l| b << l }
491
+ input.each_line { |l| b << l }
515
492
  b
516
493
  end
517
494
  yield h, b
@@ -519,7 +496,7 @@ module Hermeneutics
519
496
 
520
497
  def parse_headers input
521
498
  h = []
522
- input.eat_lines { |l|
499
+ input.each_line { |l|
523
500
  l.chomp!
524
501
  case l
525
502
  when /^$/ then
@@ -527,7 +504,7 @@ module Hermeneutics
527
504
  when /^\s+/ then
528
505
  h.last or
529
506
  raise ParseError, "First line may not be a continuation."
530
- h.last << $/ << l
507
+ h.last << "\n" << l
531
508
  else
532
509
  h.push l
533
510
  end
@@ -544,15 +521,28 @@ module Hermeneutics
544
521
  @headers ||= Headers.create
545
522
  end
546
523
 
524
+ def header sym, type = nil
525
+ @headers.field sym, type
526
+ end
527
+
528
+ private
529
+
547
530
  def method_missing sym, *args, &block
548
531
  case sym
549
- when /h_(.*)/, /header_(.*)/ then
550
- @headers.field $1.to_sym, *args
532
+ when /\Ah_(.*)/, /\Aheader_(.*)/ then
533
+ header $1.to_sym, *args
551
534
  else
552
- @headers.field sym, *args or super
535
+ super
553
536
  end
554
537
  end
555
538
 
539
+ public
540
+
541
+ def has? name
542
+ @headers.has? name
543
+ end
544
+ alias has_header? has?
545
+
556
546
  def [] name, type = nil
557
547
  @headers[ name, type]
558
548
  end
@@ -564,7 +554,7 @@ module Hermeneutics
564
554
 
565
555
  def inspect
566
556
  r = ""
567
- r << "#<#{cls}:"
557
+ r << "#<#{self.class}:"
568
558
  r << "0x%x" % (object_id<<1)
569
559
  r << " headers:#{@headers.length}"
570
560
  r << " multipart" if is_multipart?
@@ -580,7 +570,8 @@ module Hermeneutics
580
570
  @headers.replace :content_type, c.fulltype, boundary: u
581
571
  end
582
572
  end
583
- r << @headers.to_s << $/ << @body.to_s
573
+ r << @headers.to_s << "\n" << @body.to_s
574
+ r.ends_with? "\n" or r << "\n"
584
575
  r
585
576
  end
586
577
 
@@ -288,12 +288,16 @@ module Hermeneutics
288
288
  nil
289
289
  end
290
290
 
291
+ private
292
+
291
293
  def method_missing sym, *args
292
294
  (tag sym, *args) or super
293
295
  rescue
294
296
  super
295
297
  end
296
298
 
299
+ public
300
+
297
301
  def data
298
302
  d = ""
299
303
  gather_data self, d