mumboe-vpim 0.7
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/CHANGES +510 -0
- data/COPYING +58 -0
- data/README +185 -0
- data/lib/vpim/address.rb +219 -0
- data/lib/vpim/agent/atomize.rb +104 -0
- data/lib/vpim/agent/base.rb +73 -0
- data/lib/vpim/agent/calendars.rb +173 -0
- data/lib/vpim/agent/handler.rb +26 -0
- data/lib/vpim/agent/ics.rb +161 -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 +384 -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 +261 -0
- data/lib/vpim/rfc2425.rb +367 -0
- data/lib/vpim/rrule.rb +591 -0
- data/lib/vpim/time.rb +40 -0
- data/lib/vpim/vcard.rb +1456 -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/lib/vpim.rb +13 -0
- data/samples/README.mutt +93 -0
- data/samples/ab-query.rb +57 -0
- data/samples/agent.ru +10 -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 +209 -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_agent_atomize.rb +84 -0
- data/test/test_agent_calendars.rb +128 -0
- data/test/test_agent_ics.rb +96 -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 +437 -0
- data/test/test_misc.rb +13 -0
- data/test/test_repo.rb +129 -0
- data/test/test_rrule.rb +1030 -0
- data/test/test_vcard.rb +973 -0
- data/test/test_view.rb +79 -0
- metadata +140 -0
data/samples/cmd-itip.rb
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$-w = true
|
4
|
+
$:.unshift File.dirname($0) + '/../lib'
|
5
|
+
|
6
|
+
require 'getoptlong'
|
7
|
+
|
8
|
+
require 'vpim/icalendar'
|
9
|
+
require 'vpim/duration'
|
10
|
+
|
11
|
+
include Vpim
|
12
|
+
|
13
|
+
# TODO - $0 is the full path, fix it.
|
14
|
+
HELP =<<EOF
|
15
|
+
Usage: #{$0} <invitation.ics>
|
16
|
+
|
17
|
+
Options
|
18
|
+
-h,--help Print this helpful message.
|
19
|
+
-d,--debug Print debug information.
|
20
|
+
|
21
|
+
-m,--my-addrs My email addresses, a REGEX.
|
22
|
+
Examples:
|
23
|
+
EOF
|
24
|
+
|
25
|
+
opt_debug = nil
|
26
|
+
opt_print = true
|
27
|
+
|
28
|
+
# Ways to get this:
|
29
|
+
# Use a --mutt option, and steal it from muttrc,
|
30
|
+
# from $USER, $LOGNAME,, from /etc/passwd...
|
31
|
+
opt_myaddrs = nil
|
32
|
+
|
33
|
+
opts = GetoptLong.new(
|
34
|
+
[ "--help", "-h", GetoptLong::NO_ARGUMENT ],
|
35
|
+
|
36
|
+
[ "--myaddrs", "-m", GetoptLong::REQUIRED_ARGUMENT ],
|
37
|
+
|
38
|
+
[ "--accept", "-a", GetoptLong::REQUIRED_ARGUMENT ],
|
39
|
+
[ "--reject", "-r", GetoptLong::REQUIRED_ARGUMENT ],
|
40
|
+
[ "--debug", "-d", GetoptLong::NO_ARGUMENT ]
|
41
|
+
)
|
42
|
+
|
43
|
+
opts.each do |opt, arg|
|
44
|
+
case opt
|
45
|
+
when "--help" then
|
46
|
+
puts HELP
|
47
|
+
exit 0
|
48
|
+
|
49
|
+
when "--debug" then
|
50
|
+
require 'pp'
|
51
|
+
opt_debug = true
|
52
|
+
|
53
|
+
when "--myaddrs" then
|
54
|
+
opt_myaddrs = Regexp.new(arg, 'i')
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
if ARGV.length < 1
|
59
|
+
puts "no input files specified, try -h!\n"
|
60
|
+
exit 1
|
61
|
+
end
|
62
|
+
|
63
|
+
ARGV.each do |file|
|
64
|
+
cals = Vpim::Icalendar.decode(File.open(file))
|
65
|
+
|
66
|
+
cals.each do |cal|
|
67
|
+
if opt_debug
|
68
|
+
puts "vCalendar: version=#{cal.version/10.0} producer='#{cal.producer}'"
|
69
|
+
if cal.protocol; puts " protocol-method=#{cal.protocol}"; end
|
70
|
+
end
|
71
|
+
|
72
|
+
events = cal.events
|
73
|
+
|
74
|
+
if events.size != 1
|
75
|
+
raise "!! #{events.size} calendar events is more than 1!"
|
76
|
+
end
|
77
|
+
|
78
|
+
events.each do |e|
|
79
|
+
summary = e.summary || e.comment || ''
|
80
|
+
|
81
|
+
case cal.protocol.upcase
|
82
|
+
when 'PUBLISH'
|
83
|
+
puts "Notification of: #{summary}"
|
84
|
+
|
85
|
+
when 'REQUEST'
|
86
|
+
puts "Request for: #{summary}"
|
87
|
+
|
88
|
+
when 'REPLY'
|
89
|
+
|
90
|
+
else
|
91
|
+
raise "!! unhandled protocol type #{cal.protocol}!"
|
92
|
+
end
|
93
|
+
|
94
|
+
puts "Organized by: #{e.organizer.to_s}"
|
95
|
+
|
96
|
+
# TODO - spec as hours/mins/secs
|
97
|
+
e.occurrences.each_with_index do |t, i|
|
98
|
+
if(i < 1)
|
99
|
+
puts "At time: #{t}" +( e.duration ? " for #{Duration.secs(e.duration).to_s}" : '' )
|
100
|
+
else
|
101
|
+
puts "... and others"
|
102
|
+
break
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
if e.location; puts "Located at: #{e.location}"; end
|
107
|
+
|
108
|
+
if e.description
|
109
|
+
puts finish="-- Description --"
|
110
|
+
puts e.description
|
111
|
+
end
|
112
|
+
|
113
|
+
if e.comments
|
114
|
+
puts finish="-- Comment --"
|
115
|
+
puts " comment=#{e.comments}"
|
116
|
+
end
|
117
|
+
|
118
|
+
if e.attendees.first
|
119
|
+
|
120
|
+
puts finish="-- Attendees --"
|
121
|
+
|
122
|
+
e.attendees.each_with_index do |a,i|
|
123
|
+
puts "#{i} #{a.to_s}"
|
124
|
+
if !opt_myaddrs || a.uri =~ opt_myaddrs
|
125
|
+
puts " participation-status: #{a.partstat ? a.partstat.downcase : 'unknown'}"
|
126
|
+
puts " response-requested? #{a.rsvp ? 'yes' : 'no'}"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
if finish
|
132
|
+
puts '-' * finish.length
|
133
|
+
end
|
134
|
+
|
135
|
+
if opt_debug
|
136
|
+
if e.status; puts " status=#{e.status}"; end
|
137
|
+
puts " uid=#{e.uid}"
|
138
|
+
puts " dtstamp=#{e.dtstamp.to_s}"
|
139
|
+
puts " dtstart=#{e.dtstart.to_s}"
|
140
|
+
if e.dtend; puts " dtend=#{e.dtend.to_s}"; end
|
141
|
+
if e.rrule; puts " rrule=#{e.rrule}"; end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
todos = cal.todos
|
146
|
+
todos.each do |e|
|
147
|
+
s = e.status ? " (#{e.status})" : ''
|
148
|
+
puts "Todo#{s}: #{e.summary}"
|
149
|
+
end
|
150
|
+
|
151
|
+
if opt_debug
|
152
|
+
pp cals
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'vpim/vcard'
|
2
|
+
|
3
|
+
ORIGINAL =<<'---'
|
4
|
+
BEGIN:VCARD
|
5
|
+
VERSION:3.0
|
6
|
+
FN:Jimmy Death
|
7
|
+
N:Death;Jimmy;;Dr.;
|
8
|
+
TEL:+416 123 1111
|
9
|
+
TEL;type=home,pref:+416 123 2222
|
10
|
+
TEL;type=work,fax:+416+123+3333
|
11
|
+
EMAIL;type=work:drdeath@work.com
|
12
|
+
EMAIL;type=pref:drdeath@home.net
|
13
|
+
NOTE:Do not call.
|
14
|
+
END:VCARD
|
15
|
+
---
|
16
|
+
|
17
|
+
original = Vpim::Vcard.decode(ORIGINAL).first
|
18
|
+
|
19
|
+
puts original
|
20
|
+
|
21
|
+
modified = Vpim::Vcard::Maker.make2 do |maker|
|
22
|
+
# Set the fullname field to use family-given name order.
|
23
|
+
maker.name do |n|
|
24
|
+
n.fullname = "#{original.name.family} #{original.name.given}"
|
25
|
+
end
|
26
|
+
|
27
|
+
# Copy original fields, with some changes:
|
28
|
+
# - set only work email addresses and telephone numbers to be preferred.
|
29
|
+
# - don't copy notes
|
30
|
+
maker.copy(original) do |field|
|
31
|
+
if field.name? 'EMAIL'
|
32
|
+
field = field.copy
|
33
|
+
field.pref = field.type? 'work'
|
34
|
+
end
|
35
|
+
if field.name? 'TEL'
|
36
|
+
field = field.copy
|
37
|
+
field.pref = field.type? 'work'
|
38
|
+
end
|
39
|
+
if field.name? 'NOTE'
|
40
|
+
field = nil
|
41
|
+
end
|
42
|
+
field
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
puts '---'
|
47
|
+
puts modified
|
48
|
+
|
49
|
+
Vpim::Vcard::Maker.make2(modified) do |maker|
|
50
|
+
maker.nickname = "Your Last Friend"
|
51
|
+
end
|
52
|
+
|
53
|
+
puts '---'
|
54
|
+
puts modified
|
55
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
|
3
|
+
require 'vpim/vcard'
|
4
|
+
|
5
|
+
vcf = open(ARGV[0] || 'data/vcf/Sam Roberts.vcf')
|
6
|
+
|
7
|
+
card = Vpim::Vcard.decode(vcf).first
|
8
|
+
|
9
|
+
card.photos.each_with_index do |photo, i|
|
10
|
+
file = "_photo_#{i}."
|
11
|
+
|
12
|
+
if photo.format
|
13
|
+
file += photo.format.gsub('/', '_')
|
14
|
+
else
|
15
|
+
# You are your own if PHOTO doesn't include a format. AddressBook.app
|
16
|
+
# exports TIFF, for example, but doesn't specify that.
|
17
|
+
file += 'tiff'
|
18
|
+
end
|
19
|
+
|
20
|
+
open(file, 'w').write photo.to_s
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# Note that while most version 3.0 vCards should be valid 2.1 vCards, they
|
2
|
+
# aren't guaranteed to be. vCard 2.1 is reasonably well supported on decode,
|
3
|
+
# I'm not sure how well it works on encode.
|
4
|
+
#
|
5
|
+
# Most things should work, but you should test whether this works with your 2.1
|
6
|
+
# vCard decoder. Also, avoid base64 encoding, or do it manually.
|
7
|
+
require 'vpim/vcard'
|
8
|
+
|
9
|
+
# Create a new 2.1 vCard.
|
10
|
+
card21 = Vpim::DirectoryInfo.create(
|
11
|
+
[
|
12
|
+
Vpim::DirectoryInfo::Field.create('VERSION', '2.1')
|
13
|
+
], 'VCARD')
|
14
|
+
|
15
|
+
Vpim::Vcard::Maker.make2(card21) do |maker|
|
16
|
+
maker.name do |n|
|
17
|
+
n.prefix = 'Dr.'
|
18
|
+
n.given = 'Jimmy'
|
19
|
+
n.family = 'Death'
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
puts card21
|
25
|
+
|
26
|
+
# Copy and modify a 2.1 vCard, preserving it's version.
|
27
|
+
mod21 = Vpim::Vcard::Maker.make2(Vpim::DirectoryInfo.create([], 'VCARD')) do |maker|
|
28
|
+
maker.copy card21
|
29
|
+
maker.nickname = 'some name'
|
30
|
+
end
|
31
|
+
|
32
|
+
puts '---'
|
33
|
+
puts mod21
|
34
|
+
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'vpim/vcard'
|
2
|
+
|
3
|
+
card = Vpim::Vcard::Maker.make2 do |maker|
|
4
|
+
maker.add_name do |name|
|
5
|
+
name.prefix = 'Dr.'
|
6
|
+
name.given = 'Jimmy'
|
7
|
+
name.family = 'Death'
|
8
|
+
end
|
9
|
+
|
10
|
+
maker.add_addr do |addr|
|
11
|
+
addr.preferred = true
|
12
|
+
addr.location = 'work'
|
13
|
+
addr.street = '12 Last Row, 13th Section'
|
14
|
+
addr.locality = 'City of Lost Children'
|
15
|
+
addr.country = 'Cinema'
|
16
|
+
end
|
17
|
+
|
18
|
+
maker.add_addr do |addr|
|
19
|
+
addr.location = [ 'home', 'zoo' ]
|
20
|
+
addr.delivery = [ 'snail', 'stork', 'camel' ]
|
21
|
+
addr.street = '12 Last Row, 13th Section'
|
22
|
+
addr.locality = 'City of Lost Children'
|
23
|
+
addr.country = 'Cinema'
|
24
|
+
end
|
25
|
+
|
26
|
+
maker.nickname = "The Good Doctor"
|
27
|
+
|
28
|
+
maker.birthday = Date.today
|
29
|
+
|
30
|
+
maker.add_photo do |photo|
|
31
|
+
photo.link = 'http://example.com/image.png'
|
32
|
+
end
|
33
|
+
|
34
|
+
maker.add_photo do |photo|
|
35
|
+
photo.image = "File.open('drdeath.jpg').read # a fake string, real data is too large :-)"
|
36
|
+
photo.type = 'jpeg'
|
37
|
+
end
|
38
|
+
|
39
|
+
maker.add_tel('416 123 1111')
|
40
|
+
|
41
|
+
maker.add_tel('416 123 2222') { |t| t.location = 'home'; t.preferred = true }
|
42
|
+
|
43
|
+
maker.add_impp('joe') do |impp|
|
44
|
+
impp.preferred = 'yes'
|
45
|
+
impp.location = 'mobile'
|
46
|
+
end
|
47
|
+
|
48
|
+
maker.add_x_aim('example') do |xaim|
|
49
|
+
xaim.location = 'row12'
|
50
|
+
end
|
51
|
+
|
52
|
+
maker.add_tel('416-123-3333') do |tel|
|
53
|
+
tel.location = 'work'
|
54
|
+
tel.capability = 'fax'
|
55
|
+
end
|
56
|
+
|
57
|
+
maker.add_email('drdeath@work.com') { |e| e.location = 'work' }
|
58
|
+
|
59
|
+
maker.add_email('drdeath@home.net') { |e| e.preferred = 'yes' }
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
puts card
|
64
|
+
|
@@ -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
|
+
|