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.
- checksums.yaml +7 -0
- data/CHANGES +504 -0
- data/COPYING +58 -0
- data/README +182 -0
- data/lib/atom.rb +728 -0
- data/lib/plist.rb +22 -0
- data/lib/vpim.rb +13 -0
- data/lib/vpim/address.rb +219 -0
- data/lib/vpim/attachment.rb +102 -0
- data/lib/vpim/date.rb +222 -0
- data/lib/vpim/dirinfo.rb +277 -0
- data/lib/vpim/duration.rb +119 -0
- data/lib/vpim/enumerator.rb +32 -0
- data/lib/vpim/field.rb +614 -0
- data/lib/vpim/icalendar.rb +381 -0
- data/lib/vpim/maker/vcard.rb +16 -0
- data/lib/vpim/property/base.rb +193 -0
- data/lib/vpim/property/common.rb +315 -0
- data/lib/vpim/property/location.rb +38 -0
- data/lib/vpim/property/priority.rb +43 -0
- data/lib/vpim/property/recurrence.rb +69 -0
- data/lib/vpim/property/resources.rb +24 -0
- data/lib/vpim/repo.rb +181 -0
- data/lib/vpim/rfc2425.rb +367 -0
- data/lib/vpim/rrule.rb +591 -0
- data/lib/vpim/vcard.rb +1430 -0
- data/lib/vpim/version.rb +18 -0
- data/lib/vpim/vevent.rb +187 -0
- data/lib/vpim/view.rb +90 -0
- data/lib/vpim/vjournal.rb +58 -0
- data/lib/vpim/vpim.rb +65 -0
- data/lib/vpim/vtodo.rb +103 -0
- data/samples/README.mutt +93 -0
- data/samples/ab-query.rb +57 -0
- data/samples/cmd-itip.rb +156 -0
- data/samples/ex_cpvcard.rb +55 -0
- data/samples/ex_get_vcard_photo.rb +22 -0
- data/samples/ex_mkv21vcard.rb +34 -0
- data/samples/ex_mkvcard.rb +64 -0
- data/samples/ex_mkyourown.rb +29 -0
- data/samples/ics-dump.rb +210 -0
- data/samples/ics-to-rss.rb +84 -0
- data/samples/mutt-aliases-to-vcf.rb +45 -0
- data/samples/osx-wrappers.rb +86 -0
- data/samples/reminder.rb +203 -0
- data/samples/rrule.rb +71 -0
- data/samples/tabbed-file-to-vcf.rb +390 -0
- data/samples/vcf-dump.rb +86 -0
- data/samples/vcf-lines.rb +61 -0
- data/samples/vcf-to-ics.rb +22 -0
- data/samples/vcf-to-mutt.rb +121 -0
- data/test/test_all.rb +17 -0
- data/test/test_date.rb +120 -0
- data/test/test_dur.rb +41 -0
- data/test/test_field.rb +156 -0
- data/test/test_ical.rb +415 -0
- data/test/test_repo.rb +158 -0
- data/test/test_rrule.rb +1030 -0
- data/test/test_vcard.rb +973 -0
- data/test/test_view.rb +79 -0
- 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
|
+
|
data/samples/ics-dump.rb
ADDED
@@ -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
|
+
|