vpim2 0.0.1

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.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES +504 -0
  3. data/COPYING +58 -0
  4. data/README +182 -0
  5. data/lib/atom.rb +728 -0
  6. data/lib/plist.rb +22 -0
  7. data/lib/vpim.rb +13 -0
  8. data/lib/vpim/address.rb +219 -0
  9. data/lib/vpim/attachment.rb +102 -0
  10. data/lib/vpim/date.rb +222 -0
  11. data/lib/vpim/dirinfo.rb +277 -0
  12. data/lib/vpim/duration.rb +119 -0
  13. data/lib/vpim/enumerator.rb +32 -0
  14. data/lib/vpim/field.rb +614 -0
  15. data/lib/vpim/icalendar.rb +381 -0
  16. data/lib/vpim/maker/vcard.rb +16 -0
  17. data/lib/vpim/property/base.rb +193 -0
  18. data/lib/vpim/property/common.rb +315 -0
  19. data/lib/vpim/property/location.rb +38 -0
  20. data/lib/vpim/property/priority.rb +43 -0
  21. data/lib/vpim/property/recurrence.rb +69 -0
  22. data/lib/vpim/property/resources.rb +24 -0
  23. data/lib/vpim/repo.rb +181 -0
  24. data/lib/vpim/rfc2425.rb +367 -0
  25. data/lib/vpim/rrule.rb +591 -0
  26. data/lib/vpim/vcard.rb +1430 -0
  27. data/lib/vpim/version.rb +18 -0
  28. data/lib/vpim/vevent.rb +187 -0
  29. data/lib/vpim/view.rb +90 -0
  30. data/lib/vpim/vjournal.rb +58 -0
  31. data/lib/vpim/vpim.rb +65 -0
  32. data/lib/vpim/vtodo.rb +103 -0
  33. data/samples/README.mutt +93 -0
  34. data/samples/ab-query.rb +57 -0
  35. data/samples/cmd-itip.rb +156 -0
  36. data/samples/ex_cpvcard.rb +55 -0
  37. data/samples/ex_get_vcard_photo.rb +22 -0
  38. data/samples/ex_mkv21vcard.rb +34 -0
  39. data/samples/ex_mkvcard.rb +64 -0
  40. data/samples/ex_mkyourown.rb +29 -0
  41. data/samples/ics-dump.rb +210 -0
  42. data/samples/ics-to-rss.rb +84 -0
  43. data/samples/mutt-aliases-to-vcf.rb +45 -0
  44. data/samples/osx-wrappers.rb +86 -0
  45. data/samples/reminder.rb +203 -0
  46. data/samples/rrule.rb +71 -0
  47. data/samples/tabbed-file-to-vcf.rb +390 -0
  48. data/samples/vcf-dump.rb +86 -0
  49. data/samples/vcf-lines.rb +61 -0
  50. data/samples/vcf-to-ics.rb +22 -0
  51. data/samples/vcf-to-mutt.rb +121 -0
  52. data/test/test_all.rb +17 -0
  53. data/test/test_date.rb +120 -0
  54. data/test/test_dur.rb +41 -0
  55. data/test/test_field.rb +156 -0
  56. data/test/test_ical.rb +415 -0
  57. data/test/test_repo.rb +158 -0
  58. data/test/test_rrule.rb +1030 -0
  59. data/test/test_vcard.rb +973 -0
  60. data/test/test_view.rb +79 -0
  61. metadata +117 -0
@@ -0,0 +1,29 @@
1
+ require 'vpim/vcard'
2
+
3
+ module Vpim
4
+ class Vcard
5
+ class Maker
6
+ # Add a user-defined field, X-MY-OWN:.
7
+ #
8
+ # This can be done both to encode custom fields, or to add support for
9
+ # fields that Vcard::Maker doesn't support. In the latter case, please
10
+ # submit your methods so I can add them to vPim.
11
+ def add_my_own(value)
12
+ @card << Vpim::DirectoryInfo::Field.create( 'X-MY-OWN', value.to_str );
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+
19
+ card = Vpim::Vcard.create # ... or load from somewhere
20
+
21
+ Vpim::Vcard::Maker.make2(card) do |m|
22
+ m.add_name do |n|
23
+ n.given = 'Given'
24
+ end
25
+ m.add_my_own 'my value'
26
+ end
27
+
28
+ puts card
29
+
@@ -0,0 +1,210 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Calendars are in ~/Library/Calendars/
4
+
5
+ $-w = true
6
+ $:.unshift File.dirname($0) + '/../lib'
7
+
8
+ require 'getoptlong'
9
+ require 'pp'
10
+ require 'open-uri'
11
+
12
+ require 'vpim/icalendar'
13
+ require 'vpim/duration'
14
+
15
+ include Vpim
16
+
17
+ HELP =<<EOF
18
+ Usage: #{$0} <vcard>...
19
+
20
+ Options
21
+ -h,--help Print this helpful message.
22
+ -n,--node Dump as nodes.
23
+ -d,--debug Print debug information.
24
+ -m,--metro Convert metro.
25
+
26
+ Examples:
27
+ EOF
28
+
29
+ opt_debug = nil
30
+ opt_node = false
31
+
32
+ opts = GetoptLong.new(
33
+ [ "--help", "-h", GetoptLong::NO_ARGUMENT ],
34
+ [ "--node", "-n", GetoptLong::NO_ARGUMENT ],
35
+ [ "--debug", "-d", GetoptLong::NO_ARGUMENT ]
36
+ )
37
+
38
+ opts.each do |opt, arg|
39
+ case opt
40
+ when "--help" then
41
+ puts HELP
42
+ exit 0
43
+
44
+ when "--node" then
45
+ opt_node = true
46
+
47
+ when "--debug" then
48
+ opt_debug = true
49
+ end
50
+ end
51
+
52
+ if ARGV.length < 1
53
+ puts "no input files specified, try -h!\n"
54
+ exit 1
55
+ end
56
+
57
+ if opt_node
58
+
59
+ ARGV.each do |file|
60
+ tree = Vpim.expand(Vpim.decode(File.open(file).read(nil)))
61
+ pp tree
62
+ end
63
+
64
+ exit 0
65
+ end
66
+
67
+ def v2s(v)
68
+ case v
69
+ when Vpim::Icalendar::Attachment
70
+ if v.binary
71
+ "#{v.format.inspect} binary #{v.binary.inspect}"
72
+ else
73
+ s = "#{v.format.inspect} uri #{v.uri.inspect}"
74
+ begin
75
+ s << " #{v.value.gets.inspect}..."
76
+ rescue
77
+ s << " (#{$!.class})"
78
+ end
79
+ end
80
+ else
81
+ v #.inspect
82
+ end
83
+ end
84
+
85
+
86
+ def puts_properties(c)
87
+ [
88
+ :access_class,
89
+ :attachments,
90
+ :categories,
91
+ :comments,
92
+ :completed,
93
+ :contacts,
94
+ :created,
95
+ :description,
96
+ :dtend,
97
+ :dtstamp,
98
+ :dtstart,
99
+ :due,
100
+ :geo,
101
+ :location,
102
+ :organizer,
103
+ :percent_complete,
104
+ :priority,
105
+ :sequence,
106
+ :status,
107
+ :summary,
108
+ :transparency,
109
+ :uid,
110
+ :url,
111
+ ].each do |m|
112
+ if c.respond_to? m
113
+ v = c.send(m)
114
+ case v
115
+ when Array
116
+ v.each_with_index do |v,i|
117
+ puts " #{m}[#{i}]=<#{v2s v}>"
118
+ end
119
+ else
120
+ if v
121
+ puts " #{m}=<#{v2s v}>"
122
+ end
123
+ end
124
+ end
125
+ end
126
+
127
+ begin
128
+ if c.duration; puts " duration=#{Duration.secs(c.duration).to_s}"; end
129
+ rescue NoMethodError
130
+ end
131
+
132
+ c.attendees.each_with_index do |a,i|
133
+ puts " attendee[#{i}]=#{a.to_s}"
134
+ puts " role=#{a.role.upcase} participation-status=#{a.partstat.upcase} rsvp?=#{a.rsvp ? 'yes' : 'no'}"
135
+ end
136
+
137
+ [
138
+ 'RRULE',
139
+ 'RDATE',
140
+ 'EXRULE',
141
+ 'EXDATE',
142
+ ].each do |m|
143
+ c.propvaluearray(m).each_with_index do |v,i|
144
+ puts " #{m}[#{i}]=<#{v.to_s}>"
145
+
146
+ case
147
+ when i == 1 && m != 'RRULE'
148
+ # Anything that isn't an RRULE isn't supported at all.
149
+ puts " ==> #{m} is unsupported!"
150
+ when i == 2 && m == 'RRULE'
151
+ # If there was more than 1 RRULE, its not supported.
152
+ puts " ==> More than one RRULE is unsupported!"
153
+ end
154
+ end
155
+ end
156
+
157
+ begin
158
+ c.occurrences.each_with_index do |t, i|
159
+ if(i < 10)
160
+ puts " #{i+1} -> #{t}"
161
+ else
162
+ puts " ..."
163
+ break;
164
+ end
165
+ end
166
+ rescue ArgumentError
167
+ # No occurrences.
168
+ end
169
+
170
+ end
171
+
172
+ ARGV.each do |file|
173
+ puts "===> ", file
174
+ cals = Vpim::Icalendar.decode(
175
+ (file == "-") ? $stdin : open(file)
176
+ )
177
+
178
+ cals.each_with_index do |cal, i|
179
+ if i > 0
180
+ puts
181
+ end
182
+
183
+ puts "Icalendar[#{i}]:"
184
+ puts " version=#{cal.version/10.0}"
185
+ puts " producer=#{cal.producer}"
186
+
187
+ if cal.protocol; puts " protocol=#{cal.protocol}"; end
188
+
189
+ events = cal.events
190
+
191
+ cal.components.each_with_index do |c, i|
192
+ puts " #{c.class.to_s.sub(/.*::/,'')}[#{i}]:"
193
+
194
+ begin
195
+ puts_properties(c)
196
+ rescue => e
197
+ cb = e.backtrace
198
+ pp e
199
+ print cb.shift, ":", e.message, " (", e.class, ")\n"
200
+ cb.each{|c| print "\tfrom ", c, "\n"}
201
+ exit 1
202
+ end
203
+ end
204
+
205
+ if opt_debug
206
+ pp cals
207
+ end
208
+ end
209
+ end
210
+
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Call with --print to print RSS to stdout, otherwise it runs as a WEBrick
4
+ # servelet on port 8080.
5
+ #
6
+ # This comes from an idea of Dave Thomas' that he described here:
7
+ #
8
+ # http://pragprog.com/pragdave/Tech/Blog/ToDos.rdoc
9
+ #
10
+ # He generously sent me his code, and I reimplemented it with vPim and rss/maker.
11
+ #
12
+ # RSS Content-Types:
13
+ #
14
+ # RSS 1.0 -> application/rdf+xml
15
+ # RSS 2.0 -> text/xml
16
+ # RSS 0.9 -> text/xml
17
+ # ATOM -> application/xml
18
+
19
+ require 'rss/maker'
20
+ require 'vpim/icalendar'
21
+
22
+ class IcalToRss
23
+ def initialize(calendars, title, link, language = 'en-us')
24
+ @rss = RSS::Maker.make("0.9") do |maker|
25
+ maker.channel.title = title
26
+ maker.channel.link = link
27
+ maker.channel.description = title
28
+ maker.channel.language = language
29
+
30
+ # These are required, or RSS::Maker silently returns nil!
31
+ maker.image.url = "maker.image.url"
32
+ maker.image.title = "maker.image.title"
33
+
34
+ calendars.each do |file|
35
+ Vpim::Icalendar.decode(File.open(file)).each do |cal|
36
+ cal.todos.each do |todo|
37
+ if !todo.status || todo.status.upcase != "COMPLETED"
38
+ item = maker.items.new_item
39
+ item.title = todo.summary
40
+ item.link = todo.properties['url'] || link
41
+ item.description = todo.description || todo.summary
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ def to_rss
50
+ @rss.to_s
51
+ end
52
+ end
53
+
54
+ TITLE = "Sam's ToDo List"
55
+ LINK = "http://ensemble.local/~sam"
56
+
57
+ if ARGV[0] == "--print"
58
+
59
+ puts IcalToRss.new( Dir[ "/Users/sam/Library/Calendars/*.ics" ], TITLE, LINK ).to_rss
60
+
61
+ else
62
+
63
+ require 'webrick'
64
+
65
+ class IcalRssTodoServlet < WEBrick::HTTPServlet::AbstractServlet
66
+ def do_GET(req, resp)
67
+ resp.body = IcalToRss.new( Dir[ "/Users/sam/Library/Calendars/*.ics" ], TITLE, LINK ).to_rss
68
+ resp['content-type'] = 'text/xml'
69
+ raise WEBrick::HTTPStatus::OK
70
+ end
71
+ end
72
+
73
+ server = WEBrick::HTTPServer.new( :Port => 8080 )
74
+
75
+ server.mount( '/', IcalRssTodoServlet )
76
+
77
+ ['INT', 'TERM'].each { |signal|
78
+ trap(signal) { server.shutdown }
79
+ }
80
+
81
+ server.start
82
+
83
+ end
84
+
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $-w = true
4
+ $:.unshift File.dirname($0) + '/../lib'
5
+
6
+ require 'vpim/vcard'
7
+
8
+ ARGV.each do |file|
9
+
10
+ File.open(file).each do |line|
11
+
12
+ if line =~ /\s*alias\s+(\w+)\s+(.*)/
13
+ nick = $1
14
+ rhs = $2
15
+ email = nil
16
+ name = nil
17
+
18
+ case rhs
19
+ when /(.*)<(.*)>/
20
+ email = $2
21
+ name = $1
22
+ else
23
+ email = rhs
24
+ name = nick
25
+ nick = nil
26
+ end
27
+
28
+ card = Vpim::Vcard::Maker.make2 do |maker|
29
+ # don't have the broken-down name, we'll have to leave it blank
30
+ maker.name { |n| n.fullname = name }
31
+
32
+ # Set preferred, its the only one...
33
+ maker.add_email( email ) { |e| e.preferred = true }
34
+
35
+ maker.nickname = nick if nick
36
+
37
+ # mark as auto-generated, it makes it easier to see them
38
+ maker.add_field( Vpim::DirectoryInfo::Field.create('note', "auto-generated-from-mutt-aliases") )
39
+ end
40
+
41
+ puts card.to_s
42
+ end
43
+ end
44
+ end
45
+
@@ -0,0 +1,86 @@
1
+ # OSX wrapper methods.
2
+ #
3
+ # The OSX classes are mirrored fairly directly into ruby by ruby/cocoa. Too
4
+ # directly for ease, this is a start at convenient ruby APIs on top of the low-level cocoa
5
+ # methods.
6
+
7
+ =begin
8
+
9
+ Ideas for things to add:
10
+
11
+ + each for the addressbook
12
+
13
+ + ABRecord#[] <- valueForProperty
14
+
15
+ + [] and each for NSCFArray (which is actually an instance of OCObject)
16
+
17
+ + [] and each for NSCFDictionary (which is actually an instance of OCObject)
18
+
19
+ + Can I add methods to OCObject, and have them implement themselves based on the the 'class'?
20
+
21
+ + ABMultiValue#[index]
22
+
23
+ if index
24
+
25
+ is a :token, then its the identifier,
26
+ is a string, its a label
27
+ is a number, its an array index
28
+
29
+ return a Struct, so you can do
30
+
31
+ mvalue["work"].value
32
+
33
+ =end
34
+
35
+ require 'osx/addressbook'
36
+
37
+ # put into osx/ocobject?
38
+ module OSX
39
+ # When an NSData object is returned by an objective/c API (such as
40
+ # ABPerson.vCardRepresentation, I actually get a OCObject back, who's class
41
+ # instance points to either a NSData, or a related class.
42
+ #
43
+ # This is a convenience method to get the NSData data back as a ruby string.
44
+ # Is it the right place to put this?
45
+ class OCObject
46
+ def bytes
47
+ s = ' ' * length
48
+ getBytes(s)
49
+ s
50
+ end
51
+ end
52
+ end
53
+
54
+ # put into osx/abperson?
55
+ module OSX
56
+ class ABPerson
57
+ def vCard
58
+ card = self.vCardRepresentation.bytes
59
+
60
+ # The card representation appears to be either ASCII, or UCS-2. If its
61
+ # UCS-2, then the first byte will be 0, so check for this, and convert
62
+ # if necessary.
63
+ #
64
+ # We know it's 0, because the first character in a vCard must be the 'B'
65
+ # of "BEGIN:VCARD", and in UCS-2 all ascii are encoded as a 0 byte
66
+ # followed by the ASCII byte, UNICODE is great.
67
+ if card[0] == 0
68
+ nsstring = OSX::NSString.alloc.initWithCharacters(card, :length, card.size/2)
69
+ card = nsstring.UTF8String
70
+
71
+ # TODO: is nsstring.UTF8String == nsstring.to_s ?
72
+ end
73
+ card
74
+ end
75
+ end
76
+ end
77
+
78
+ # put into osx/group?
79
+ module OSX
80
+ class ABGroup
81
+ def name
82
+ self.valueForProperty(OSX::kABGroupNameProperty).to_s
83
+ end
84
+ end
85
+ end
86
+