vpim 0.16 → 0.17

Sign up to get free protection for your applications and to get access to all the features.
data/lib/vpim/duration.rb CHANGED
@@ -1,7 +1,5 @@
1
1
  =begin
2
- $Id: duration.rb,v 1.4 2004/11/17 05:06:27 sam Exp $
3
-
4
- Copyright (C) 2005 Sam Roberts
2
+ Copyright (C) 2006 Sam Roberts
5
3
 
6
4
  This library is free software; you can redistribute it and/or modify it
7
5
  under the same terms as the ruby language itself, see the file COPYING for
@@ -0,0 +1,121 @@
1
+ =begin
2
+ $Id: duration.rb,v 1.4 2004/11/17 05:06:27 sam Exp $
3
+
4
+ Copyright (C) 2005 Sam Roberts
5
+
6
+ This library is free software; you can redistribute it and/or modify it
7
+ under the same terms as the ruby language itself, see the file COPYING for
8
+ details.
9
+ =end
10
+
11
+ module Vpim
12
+ class Duration
13
+ SECS_HOUR = 60 * 60
14
+ SECS_DAY = 24 * SECS_HOUR
15
+ MINS_HOUR = 60
16
+
17
+ # Initialize from a number of seconds.
18
+ def initialize(secs)
19
+ @secs = secs
20
+ end
21
+
22
+ def Duration.secs(secs)
23
+ Duration.new(secs)
24
+ end
25
+
26
+ def Duration.mins(mins)
27
+ Duration.new(mins * 60)
28
+ end
29
+
30
+ def Duration.hours(hours)
31
+ Duration.new(hours * SECS_HOUR)
32
+ end
33
+
34
+ def Duration.days(days)
35
+ Duration.new(days * SECS_DAY)
36
+ end
37
+
38
+ def secs
39
+ @secs
40
+ end
41
+
42
+ def mins
43
+ (@secs/60).to_i
44
+ end
45
+
46
+ def hours
47
+ (@secs/SECS_HOUR).to_i
48
+ end
49
+
50
+ def days
51
+ (@secs/SECS_DAY).to_i
52
+ end
53
+
54
+ def weeks
55
+ (days/7).to_i
56
+ end
57
+
58
+ def by_hours
59
+ [ hours, mins % MINS_HOUR, secs % 60]
60
+ end
61
+
62
+ def by_days
63
+ [ days, hours % 24, mins % MINS_HOUR, secs % 60]
64
+ end
65
+
66
+ def to_a
67
+ by_days
68
+ end
69
+
70
+ def to_s
71
+ Duration.as_str(self.to_a)
72
+ end
73
+
74
+ def Duration.as_str(arr)
75
+ s = ""
76
+ case arr.length
77
+ when 4
78
+ if arr[0] > 0
79
+ s << "#{arr[0]} days"
80
+ end
81
+ if arr[1] > 0
82
+ if s.length > 0
83
+ s << ', '
84
+ end
85
+ s << "#{arr[1]} hours"
86
+ end
87
+ if arr[2] > 0
88
+ if s.length > 0
89
+ s << ', '
90
+ end
91
+ s << "#{arr[2]} mins"
92
+ end
93
+ if arr[3] > 0
94
+ if s.length > 0
95
+ s << ', '
96
+ end
97
+ s << "#{arr[3]} secs"
98
+ end
99
+ when 3
100
+ if arr[0] > 0
101
+ s << "#{arr[0]} hours"
102
+ end
103
+ if arr[1] > 0
104
+ if s.length > 0
105
+ s << ', '
106
+ end
107
+ s << "#{arr[1]} mins"
108
+ end
109
+ if arr[2] > 0
110
+ if s.length > 0
111
+ s << ', '
112
+ end
113
+ s << "#{arr[2]} secs"
114
+ end
115
+ end
116
+
117
+ s
118
+ end
119
+ end
120
+ end
121
+
@@ -1,7 +1,5 @@
1
1
  =begin
2
- $Id: enumerator.rb,v 1.2 2004/11/17 05:06:27 sam Exp $
3
-
4
- Copyright (C) 2005 Sam Roberts
2
+ Copyright (C) 2006 Sam Roberts
5
3
 
6
4
  This library is free software; you can redistribute it and/or modify it
7
5
  under the same terms as the ruby language itself, see the file COPYING for
@@ -0,0 +1,29 @@
1
+ =begin
2
+ $Id: enumerator.rb,v 1.2 2004/11/17 05:06:27 sam Exp $
3
+
4
+ Copyright (C) 2005 Sam Roberts
5
+
6
+ This library is free software; you can redistribute it and/or modify it
7
+ under the same terms as the ruby language itself, see the file COPYING for
8
+ details.
9
+ =end
10
+
11
+ module Vpim
12
+ # This is a way for an object to have multiple ways of being enumerated via
13
+ # argument to it's #each() method. An Enumerator mixes in Enumerable, so the
14
+ # standard APIS such as Enumerable#map(), Enumerable#to_a(), and
15
+ # Enumerable#find_all() can be used on it.
16
+ class Enumerator
17
+ include Enumerable
18
+
19
+ def initialize(obj, *args)
20
+ @obj = obj
21
+ @args = args
22
+ end
23
+
24
+ def each(&block)
25
+ @obj.each(*@args, &block)
26
+ end
27
+ end
28
+ end
29
+
data/lib/vpim/field.rb CHANGED
@@ -1,7 +1,5 @@
1
1
  =begin
2
- $Id: field.rb,v 1.11 2005/01/07 03:32:16 sam Exp $
3
-
4
- Copyright (C) 2005 Sam Roberts
2
+ Copyright (C) 2006 Sam Roberts
5
3
 
6
4
  This library is free software; you can redistribute it and/or modify it
7
5
  under the same terms as the ruby language itself, see the file COPYING for
@@ -17,6 +15,18 @@ module Vpim
17
15
  class DirectoryInfo
18
16
 
19
17
  # A field in a directory info object.
18
+ #
19
+ # TODO
20
+ # - Field should know which param values and field vales are
21
+ # case-insensitive, configurably, so it can down case them
22
+ # - perhaps should have pvalue_set/del/add, perhaps case-insensitive, or
23
+ # pvalue_iset/idel/iadd, where set sets them all, add adds if not present,
24
+ # and del deletes any that are present
25
+ # - I really, really, need a case-insensitive string...
26
+ # - should allow nil as a field value, its not the same as '', if there is
27
+ # more than one pvalue, the empty string will show up. This isn't strictly
28
+ # disallowed, but its odd. Should also strip empty strings on decoding, if
29
+ # I don't already.
20
30
  class Field
21
31
  private_class_method :new
22
32
 
@@ -56,7 +66,7 @@ module Vpim
56
66
 
57
67
  pvalues.each do |pvalue|
58
68
  # check if we need to do any encoding
59
- if pname.downcase == 'encoding' && pvalue == :b64
69
+ if Vpim::Methods.casecmp?(pname, 'ENCODING') && pvalue == :b64
60
70
  pvalue = 'b' # the RFC definition of the base64 param value
61
71
  value = [ value.to_str ].pack('m').gsub("\n", '')
62
72
  end
@@ -100,11 +110,16 @@ module Vpim
100
110
  raise Vpim::InvalidEncodingError, atline
101
111
  end
102
112
 
103
- atgroup = $1
104
- atname = $2
113
+ atgroup = $1.upcase
114
+ atname = $2.upcase
105
115
  paramslist = $3
106
116
  atvalue = $~[-1]
107
117
 
118
+ # I've seen space that shouldn't be there, as in "BEGIN:VCARD ", so
119
+ # strip it. I'm not absolutely sure this is allowed... it certainly
120
+ # breaks round-trip encoding.
121
+ atvalue.strip!
122
+
108
123
  if atgroup.length > 0
109
124
  atgroup.chomp!('.')
110
125
  else
@@ -120,7 +135,7 @@ module Vpim
120
135
  paramslist.scan( %r{#{Bnf::PARAM}}i ) do
121
136
 
122
137
  # param names are case-insensitive, and multi-valued
123
- name = $1.downcase
138
+ name = $1.upcase
124
139
  params = $3
125
140
 
126
141
  # v2.1 params have no '=' sign, figure out what kind of param it
@@ -131,13 +146,13 @@ module Vpim
131
146
  params = $1
132
147
  case $1
133
148
  when /quoted-printable/i
134
- name = 'encoding'
149
+ name = 'ENCODING'
135
150
 
136
151
  when /base64/i
137
- name = 'encoding'
152
+ name = 'ENCODING'
138
153
 
139
154
  else
140
- name = 'type'
155
+ name = 'TYPE'
141
156
  end
142
157
  end
143
158
 
@@ -148,7 +163,7 @@ module Vpim
148
163
  end
149
164
 
150
165
  params.scan( %r{#{Bnf::PVALUE}} ) do
151
- atparams[name] << ($1 || $2) # Used to do this, want to stop! .downcase
166
+ atparams[name] << ($1 || $2)
152
167
  end
153
168
  end
154
169
  end
@@ -159,6 +174,10 @@ module Vpim
159
174
  def initialize(line) # :nodoc:
160
175
  @line = line.to_str
161
176
  @group, @name, @params, @value = Field.decode0(@line)
177
+
178
+ @params.each do |pname,pvalues|
179
+ pvalues.freeze
180
+ end
162
181
  self
163
182
  end
164
183
 
@@ -206,12 +225,13 @@ module Vpim
206
225
  # maximum line width of +width+, where +0+ means no wrapping, and nil is
207
226
  # to accept the default wrapping (75, recommended by RFC2425).
208
227
  #
209
- # Note: Apple's Address Book 3.0.3 neither understands to unwrap lines
210
- # when it imports vCards (it treats them as raw new-line characters), nor
211
- # wraps long lines on export. On import, this is mostly a cosmetic
212
- # problem, but wrapping can be disabled by setting width to +0+, if
213
- # desired. On export, its a more serious bug, since Address Book wil
214
- # encode the entire Note: section as a single, very long, line.
228
+ # Note: AddressBook.app 3.0.3 neither understands to unwrap lines when it
229
+ # imports vCards (it treats them as raw new-line characters), nor wraps
230
+ # long lines on export. This is mostly a cosmetic problem, but wrapping
231
+ # can be disabled by setting width to +0+, if desired.
232
+ #
233
+ # FIXME - breaks round-trip encoding, need to change this to not wrap
234
+ # fields that are already wrapped.
215
235
  def encode(width=nil)
216
236
  width = 75 unless width
217
237
  l = @line
@@ -236,30 +256,24 @@ module Vpim
236
256
  end
237
257
 
238
258
  # An Array of all the param names.
239
- def params
259
+ def pnames
240
260
  @params.keys
241
261
  end
242
262
 
243
- # The Array of all values of the param +name+, or nil if there is no
244
- # such param.
245
- #
246
- # TODO - this doesn't return [] if the param isn't found, because then
247
- # you can't distinguish between a param with no values, and a param that
248
- # isn't defined. But maybe it isn't useful? You can always do
249
- # params.include?(a_name) if you want to know if it exists. Maybe this
250
- # should take a block specifying the default value, even?
251
- def param(name)
252
- v = @params[name.downcase]
253
- if v
254
- v = v.collect do |p|
255
- p.downcase.freeze
256
- end
257
- v.freeze
258
- end
259
- v
263
+ # FIXME - remove my own uses of #params
264
+ alias params pnames # :nodoc:
265
+
266
+ # The Array of all values of the param +name+, nil if there is no such
267
+ # param, [] if the param has no values. If the Field isn't frozen, the
268
+ # Array is mutable.
269
+ def pvalues(name)
270
+ @params[name.upcase]
260
271
  end
261
272
 
262
- alias [] param
273
+ # FIXME - remove my own uses of #param
274
+ alias param pvalues # :nodoc:
275
+
276
+ alias [] pvalues
263
277
 
264
278
  # Yield once for each param, +name+ is the parameter name, +value+ is an
265
279
  # array of the parameter values.
@@ -287,21 +301,20 @@ module Vpim
287
301
 
288
302
  when 'quoted-printable' then @value.unpack('M*').first
289
303
 
290
- else raise Vpim::InvalidEncodingError, "unrecognized encoding (#{encoding})"
304
+ else
305
+ raise Vpim::InvalidEncodingError, "unrecognized encoding (#{encoding})"
291
306
  end
292
307
  end
293
308
 
294
309
  # Is the #name of this Field +name+? Names are case insensitive.
295
310
  def name?(name)
296
- name.to_s.downcase == @name.downcase
311
+ Vpim::Methods.casecmp?(@name, name)
297
312
  end
298
313
 
299
314
  # Is the #group of this field +group+? Group names are case insensitive.
300
315
  # A +group+ of nil matches if the field has no group.
301
316
  def group?(group)
302
- g1 = @group ? @group.downcase : nil
303
- g2 = group ? group.downcase : nil
304
- g1 == g2
317
+ Vpim::Methods.casecmp?(@group, group)
305
318
  end
306
319
 
307
320
  # Is the value of this field of type +kind+? RFC2425 allows the type of
@@ -321,7 +334,7 @@ module Vpim
321
334
  # - boolean:
322
335
  # - float:
323
336
  def kind?(kind)
324
- kind.downcase == self.kind
337
+ Vpim::Methods.casecmp?(self.kind == kind)
325
338
  end
326
339
 
327
340
  # Is one of the values of the TYPE parameter of this field +type+? The
@@ -331,10 +344,12 @@ module Vpim
331
344
  # TYPE parameters are used for general categories, such as
332
345
  # distinguishing between an email address used at home or at work.
333
346
  def type?(type)
347
+ type = type.to_str
348
+
334
349
  types = param('type')
335
350
 
336
351
  if types
337
- types = types.include?(type.to_str.downcase)
352
+ types = types.detect { |t| Vpim::Methods.casecmp?(t, type) }
338
353
  end
339
354
  end
340
355
 
@@ -342,12 +357,22 @@ module Vpim
342
357
  # #type?('pref'). This method is not necessarily meaningful for
343
358
  # non-vCard profiles.
344
359
  def pref?
345
- type?('pref')
360
+ type? 'pref'
361
+ end
362
+
363
+ # Set whether a field is marked as preferred. See #pref?
364
+ def pref=(ispref)
365
+ if ispref
366
+ pvalue_iadd('type', 'pref')
367
+ else
368
+ pvalue_idel('type', 'pref')
369
+ end
346
370
  end
347
371
 
348
372
  # Is the value of this field +value+? The check is case insensitive.
373
+ # FIXME - it shouldn't be insensitive, make a #casevalue? method.
349
374
  def value?(value)
350
- @value && @value.downcase == value.downcase
375
+ Vpim::Methods.casecmp?(@value, value.to_str)
351
376
  end
352
377
 
353
378
  # The value of the ENCODING parameter, if present, or nil if not
@@ -359,7 +384,7 @@ module Vpim
359
384
  if e.length > 1
360
385
  raise Vpim::InvalidEncodingError, "multi-valued param 'encoding' (#{e})"
361
386
  end
362
- e = e.first
387
+ e = e.first.downcase
363
388
  end
364
389
  e
365
390
  end
@@ -374,7 +399,7 @@ module Vpim
374
399
  end
375
400
  v = v.first
376
401
  end
377
- v
402
+ v.downcase
378
403
  end
379
404
 
380
405
  # The value as an array of Time objects (all times and dates in
@@ -382,7 +407,7 @@ module Vpim
382
407
  # birthday). The time will be UTC if marked as so (with a timezone of
383
408
  # "Z"), and in localtime otherwise.
384
409
  #
385
- # TODO: support timezone offsets
410
+ # TODO - support timezone offsets
386
411
  #
387
412
  # TODO - if year is before 1970, this won't work... but some people
388
413
  # are generating calendars saying Canada Day started in 1753!
@@ -452,10 +477,6 @@ module Vpim
452
477
  @value
453
478
  end
454
479
 
455
- def inspect # :nodoc:
456
- @line
457
- end
458
-
459
480
  # TODO def pretty_print() ...
460
481
 
461
482
  # Set the group of this field to +group+.
@@ -477,21 +498,85 @@ module Vpim
477
498
  def text=(text)
478
499
  end
479
500
 
480
- # Set a the param +param+'s value to +pvalue+. Valid values are as in
481
- # Field.create().
482
- def []=(param,pvalue)
501
+ # Set a the param +pname+'s value to +pvalue+, replacing any value it
502
+ # currently has. See Field.create() for a description of +pvalue+.
503
+ #
504
+ # Example:
505
+ # if field['type']
506
+ # field['type'] << 'home'
507
+ # else
508
+ # field['type'] = [ 'home'
509
+ # end
510
+ #
511
+ # TODO - this could be an alias to #pvalue_set
512
+ def []=(pname,pvalue)
483
513
  unless pvalue.respond_to?(:to_ary)
484
514
  pvalue = [ pvalue ]
485
515
  end
486
516
 
487
517
  h = @params.dup
488
518
 
489
- h[param.downcase] = pvalue
519
+ h[pname.upcase] = pvalue
490
520
 
491
521
  mutate(@group, @name, h, @value)
492
522
  pvalue
493
523
  end
494
524
 
525
+ # Add +pvalue+ to the param +pname+'s value. The values are treated as a
526
+ # set so duplicate values won't occur, and String values are case
527
+ # insensitive. See Field.create() for a description of +pvalue+.
528
+ def pvalue_iadd(pname, pvalue)
529
+ pname = pname.upcase
530
+
531
+ # Get a uniq set, where strings are compared case-insensitively.
532
+ values = [ pvalue, @params[pname] ].flatten.compact
533
+ values = values.collect do |v|
534
+ if v.respond_to? :to_str
535
+ v = v.to_str.upcase
536
+ end
537
+ v
538
+ end
539
+ values.uniq!
540
+
541
+ h = @params.dup
542
+
543
+ h[pname] = values
544
+
545
+ mutate(@group, @name, h, @value)
546
+ values
547
+ end
548
+
549
+ # Delete +pvalue+ from the param +pname+'s value. The values are treated
550
+ # as a set so duplicate values won't occur, and String values are case
551
+ # insensitive. +pvalue+ must be a single String or Symbol.
552
+ def pvalue_idel(pname, pvalue)
553
+ pname = pname.upcase
554
+ if pvalue.respond_to? :to_str
555
+ pvalue = pvalue.to_str.downcase
556
+ end
557
+
558
+ # Get a uniq set, where strings are compared case-insensitively.
559
+ values = [ nil, @params[pname] ].flatten.compact
560
+ values = values.collect do |v|
561
+ if v.respond_to? :to_str
562
+ v = v.to_str.downcase
563
+ end
564
+ v
565
+ end
566
+ values.uniq!
567
+ values.delete pvalue
568
+
569
+ h = @params.dup
570
+
571
+ h[pname] = values
572
+
573
+ mutate(@group, @name, h, @value)
574
+ values
575
+ end
576
+
577
+ # FIXME - should change this so it doesn't assign to @line here, so @line
578
+ # is used to preserve original encoding. That way, #encode can only wrap
579
+ # new fields, not old fields.
495
580
  def mutate(g, n, p, v) #:nodoc:
496
581
  line = Field.encode0(g, n, p, v)
497
582