hermeneutics 1.11 → 1.14

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,16 @@ 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
+ input.encode! input.encoding, universal_newline: true
27
+ list = input.split /^--#{Regexp.quote b}/
28
+ prolog = list.shift
29
+ epilog = list.pop
30
+ epilog and epilog.slice! /\A--\n/ or raise "Missing last separator."
31
+ list.each { |p|
32
+ p.slice! /\A\n/ or raise "Malformed separator: #{b + p[/.*/]}."
33
+ }
34
+ list.map! { |t| Message.parse t }
35
+ new b, prolog, list, epilog
144
36
  end
145
37
 
146
38
  end
@@ -164,7 +56,7 @@ module Hermeneutics
164
56
 
165
57
  def inspect
166
58
  r = ""
167
- r << "#<#{cls}:"
59
+ r << "#<#{self.class}:"
168
60
  r << "0x%x" % (object_id<<1)
169
61
  r << " n=#{@list.length}"
170
62
  r << ">"
@@ -181,10 +73,10 @@ module Hermeneutics
181
73
  s = p.to_s
182
74
  s =~ re rescue nil
183
75
  $& and raise IllegalBoundary
184
- r << splitter << $/ << s << $/
76
+ r << splitter << "\n" << s
185
77
  }
186
78
  @epilog =~ re and raise IllegalBoundary
187
- r << splitter << "--" << @epilog
79
+ r << splitter << "--\n" << @epilog
188
80
  rescue IllegalBoundary
189
81
  boundary!
190
82
  retry
@@ -209,111 +101,140 @@ module Hermeneutics
209
101
 
210
102
  class ParseError < StandardError ; end
211
103
 
212
- class Headers
104
+ class Header
213
105
 
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
106
+ @line_max = 78
107
+ @indent = 4
108
+
109
+ class <<self
110
+
111
+ attr_accessor :line_max, :indent
112
+
113
+ private :new
114
+
115
+ def parse str
116
+ str =~ /:\s*/ or
117
+ raise ParseError, "Header line without a colon: #{str}"
118
+ new $`, $'
238
119
  end
239
- attr_reader :name, :sep, :data
240
- def initialize name, sep, data
241
- @name, @sep, @data, @contents = name, sep, data
120
+
121
+ def create name, *contents
122
+ name = build_name name
123
+ i = new name.to_s, nil
124
+ i.set *contents
242
125
  end
243
- def to_s
244
- "#@name#@sep#@data"
126
+
127
+ def build_name name
128
+ n = name.to_s
129
+ unless n.equal? name then
130
+ n.gsub! /_/, "-"
131
+ n.gsub! /\b[a-z]/ do |c| c.upcase end
132
+ end
133
+ n
245
134
  end
246
- def contents type
247
- if type then
248
- unless @contents and @contents.is_a? type then
135
+
136
+ end
137
+
138
+ attr_reader :name, :data
139
+
140
+ def initialize name, data
141
+ @name, @data, @contents = name, data
142
+ end
143
+
144
+ def to_s
145
+ "#@name: #@data"
146
+ end
147
+
148
+ def contents type = nil
149
+ if type then
150
+ if @contents then
151
+ if not @contents.is_a? type then
249
152
  @contents = type.parse @data
250
153
  end
251
- @contents
252
154
  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
155
+ @contents = type.parse @data
275
156
  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
157
+ else
158
+ unless @contents then
159
+ @contents = @data
160
+ @contents.strip!
161
+ @contents.gsub! /\s\+/m, " "
283
162
  end
284
- self
285
163
  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
164
+ @contents
165
+ end
166
+
167
+ def name_is? name
168
+ (@name.casecmp name).zero?
169
+ end
170
+
171
+ def set *contents
172
+ type, *args = *contents
173
+ d = case type
174
+ when Class then
175
+ @contents = type.new *args
176
+ case (e = @contents.encode)
177
+ when Array then e
178
+ when nil then []
179
+ else [ e]
299
180
  end
300
- data << e
301
- m -= e.length
302
- }
303
- data
181
+ when nil then
182
+ @contents = nil
183
+ split_args args
184
+ else
185
+ @contents = nil
186
+ split_args contents
304
187
  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
188
+ @data = mk_lines d
189
+ self
190
+ end
191
+
192
+ def reset type
193
+ d = if type then
194
+ (contents type).encode
195
+ else
196
+ @contents
314
197
  end
198
+ @data = mk_lines d
199
+ self
315
200
  end
316
201
 
202
+ private
203
+
204
+ def mk_lines strs
205
+ m = self.class.line_max - @name.length - 2 # 2 == ": ".size
206
+ data = ""
207
+ strs.each { |e|
208
+ unless data.empty? then
209
+ if 1 + e.length <= m then
210
+ data << " "
211
+ m -= 1
212
+ else
213
+ data << "\n" << (" "*self.class.indent)
214
+ m = self.class.line_max - self.class.indent
215
+ end
216
+ end
217
+ data << e
218
+ m -= e.length
219
+ }
220
+ data
221
+ end
222
+
223
+ def split_args ary
224
+ r = []
225
+ ary.each { |a|
226
+ r.concat case a
227
+ when Array then split_args a
228
+ else a.to_s.split
229
+ end
230
+ }
231
+ r
232
+ end
233
+
234
+ end
235
+
236
+ class Headers
237
+
317
238
  @types = {
318
239
  "Content-Type" => ContentType,
319
240
  "To" => AddrList,
@@ -345,7 +266,7 @@ module Hermeneutics
345
266
 
346
267
  class <<self
347
268
  def set_field_type name, type
348
- e = Entry.create name
269
+ e = Header.create name
349
270
  if type then
350
271
  @types[ e.name] = type
351
272
  else
@@ -364,7 +285,7 @@ module Hermeneutics
364
285
  private :new
365
286
  def parse *list
366
287
  list.flatten!
367
- list.map! { |h| Entry.parse h }
288
+ list.map! { |h| Header.parse h }
368
289
  new list
369
290
  end
370
291
  def create
@@ -382,17 +303,28 @@ module Hermeneutics
382
303
  alias size length
383
304
 
384
305
  def to_s
385
- @list.map { |e| "#{e}#$/" }.join
306
+ @list.map { |e| "#{e}\n" }.join
307
+ end
308
+
309
+ private
310
+
311
+ def header_contents entry, type = nil
312
+ type ||= Headers.find_type entry
313
+ entry.contents type
386
314
  end
387
315
 
316
+ public
317
+
388
318
  def each
389
- @list.each { |e|
390
- type = Headers.find_type e
391
- c = e.contents type
392
- yield e.name, c
393
- }
319
+ @list.each { |e| yield e.name, (header_contents e) }
394
320
  self
395
321
  end
322
+ include Enumerable
323
+
324
+ def has? name
325
+ e = find_entry name
326
+ not e.nil?
327
+ end
396
328
 
397
329
  def raw name
398
330
  e = find_entry name
@@ -401,10 +333,7 @@ module Hermeneutics
401
333
 
402
334
  def field name, type = nil
403
335
  e = find_entry name
404
- if e then
405
- type ||= Headers.find_type e
406
- e.contents type
407
- end
336
+ header_contents e, type if e
408
337
  end
409
338
  def [] name, type = nil
410
339
  case name
@@ -415,9 +344,9 @@ module Hermeneutics
415
344
 
416
345
  private
417
346
 
418
- def method_missing sym, *args
347
+ def method_missing sym, type = nil, *args
419
348
  if args.empty? and not sym =~ /[!?=]\z/ then
420
- field sym, *args
349
+ field sym, type
421
350
  else
422
351
  super
423
352
  end
@@ -425,28 +354,80 @@ module Hermeneutics
425
354
 
426
355
  public
427
356
 
428
- def add name, *contents
357
+ def insert name, *contents
429
358
  e = build_entry name, *contents
430
- add_entry e
359
+ @list.unshift e
431
360
  self
432
361
  end
433
362
 
434
- def replace name, *contents
363
+ def add name, *contents
435
364
  e = build_entry name, *contents
436
- remove_entries e
437
- add_entry e
365
+ @list.push e
438
366
  self
439
367
  end
440
368
 
441
- def remove name
442
- e = Entry.create name
443
- remove_entries e
369
+ def remove name, type = nil
370
+ block_given? or return remove name, type do |_| true end
371
+ pat = Header.create name
372
+ @list.reject! { |e|
373
+ e.name_is? pat.name and yield (header_contents e, type)
374
+ }
444
375
  self
445
376
  end
446
377
  alias delete remove
447
378
 
379
+ def compact name, type = nil
380
+ remove name, type do |c| c.empty? end
381
+ end
382
+ alias remove_empty compact
383
+
384
+ def replace name, *contents
385
+ block_given? or return replace name, *contents do true end
386
+ entry = build_entry name, *contents
387
+ @list.map! { |e|
388
+ if e.name_is? entry.name and yield (header_contents e) then
389
+ entry
390
+ else
391
+ e
392
+ end
393
+ }
394
+ self
395
+ end
396
+
397
+ def replace_all name, type = nil
398
+ pat = Header.create name
399
+ type ||= Headers.find_type pat
400
+ @list.map! { |e|
401
+ if e.name_is? pat.name then
402
+ c = e.contents type
403
+ y = yield c
404
+ if y.equal? c then
405
+ e.reset type
406
+ else
407
+ y = [ type, *y] if type
408
+ next build_entry name, *y
409
+ end
410
+ end
411
+ e
412
+ }
413
+ self
414
+ end
415
+
416
+ def replace_add name, *contents
417
+ entry = build_entry name, *contents
418
+ replace_all name do |c|
419
+ if entry then
420
+ c.push entry.contents
421
+ entry = nil
422
+ end
423
+ c
424
+ end
425
+ @list.push entry if entry
426
+ self
427
+ end
428
+
448
429
  def recode name, type = nil
449
- n = Entry.build_name name
430
+ n = Header.build_name name
450
431
  @list.each { |e|
451
432
  next unless e.name_is? n
452
433
  type ||= Headers.find_type e
@@ -457,7 +438,7 @@ module Hermeneutics
457
438
 
458
439
  def inspect
459
440
  r = ""
460
- r << "#<#{cls}:"
441
+ r << "#<#{self.class}:"
461
442
  r << "0x%x" % (object_id<<1)
462
443
  r << " (#{length})"
463
444
  r << ">"
@@ -466,12 +447,12 @@ module Hermeneutics
466
447
  private
467
448
 
468
449
  def find_entry name
469
- e = Entry.build_name name
450
+ e = Header.build_name name
470
451
  @list.find { |x| x.name_is? e }
471
452
  end
472
453
 
473
454
  def build_entry name, *contents
474
- e = Entry.create name
455
+ e = Header.create name
475
456
  type, = *contents
476
457
  case type
477
458
  when Class then
@@ -483,14 +464,6 @@ module Hermeneutics
483
464
  e
484
465
  end
485
466
 
486
- def add_entry entry
487
- @list.unshift entry
488
- end
489
-
490
- def remove_entries entry
491
- @list.reject! { |e| e.name_is? entry.name }
492
- end
493
-
494
467
  end
495
468
 
496
469
  class <<self
@@ -510,12 +483,13 @@ module Hermeneutics
510
483
  private
511
484
 
512
485
  def parse_hb input
513
- h = parse_headers input
486
+ hinput, input = input.split /^\n/, 2
487
+ h = parse_headers hinput
514
488
  c = h.content_type
515
489
  b = c.parse_mime input if c
516
490
  unless b then
517
491
  b = ""
518
- input.eat_lines { |l| b << l }
492
+ input.each_line { |l| b << l }
519
493
  b
520
494
  end
521
495
  yield h, b
@@ -523,7 +497,7 @@ module Hermeneutics
523
497
 
524
498
  def parse_headers input
525
499
  h = []
526
- input.eat_lines { |l|
500
+ input.each_line { |l|
527
501
  l.chomp!
528
502
  case l
529
503
  when /^$/ then
@@ -531,7 +505,7 @@ module Hermeneutics
531
505
  when /^\s+/ then
532
506
  h.last or
533
507
  raise ParseError, "First line may not be a continuation."
534
- h.last << $/ << l
508
+ h.last << "\n" << l
535
509
  else
536
510
  h.push l
537
511
  end
@@ -548,19 +522,28 @@ module Hermeneutics
548
522
  @headers ||= Headers.create
549
523
  end
550
524
 
525
+ def header sym, type = nil
526
+ @headers.field sym, type
527
+ end
528
+
551
529
  private
552
530
 
553
531
  def method_missing sym, *args, &block
554
532
  case sym
555
- when /h_(.*)/, /header_(.*)/ then
556
- @headers.field $1.to_sym, *args
533
+ when /\Ah_(.*)/, /\Aheader_(.*)/ then
534
+ header $1.to_sym, *args
557
535
  else
558
- @headers.field sym, *args or super
536
+ super
559
537
  end
560
538
  end
561
539
 
562
540
  public
563
541
 
542
+ def has? name
543
+ @headers.has? name
544
+ end
545
+ alias has_header? has?
546
+
564
547
  def [] name, type = nil
565
548
  @headers[ name, type]
566
549
  end
@@ -572,7 +555,7 @@ module Hermeneutics
572
555
 
573
556
  def inspect
574
557
  r = ""
575
- r << "#<#{cls}:"
558
+ r << "#<#{self.class}:"
576
559
  r << "0x%x" % (object_id<<1)
577
560
  r << " headers:#{@headers.length}"
578
561
  r << " multipart" if is_multipart?
@@ -588,7 +571,8 @@ module Hermeneutics
588
571
  @headers.replace :content_type, c.fulltype, boundary: u
589
572
  end
590
573
  end
591
- r << @headers.to_s << $/ << @body.to_s
574
+ r << @headers.to_s << "\n" << @body.to_s
575
+ r.ends_with? "\n" or r << "\n"
592
576
  r
593
577
  end
594
578