vpim 0.16 → 0.17

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/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