vpim 0.597 → 0.602
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +420 -0
- data/COPYING +58 -0
- data/README +175 -0
- data/bin/reminder +203 -0
- data/bin/rrule +71 -0
- data/lib/vpim/property/common.rb +1 -1
- data/lib/vpim/repo.rb +116 -0
- data/lib/vpim/time.rb +1 -1
- data/lib/vpim/version.rb +2 -2
- 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 +14 -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 +201 -0
- data/test/test_rrule.rb +979 -0
- data/test/test_vcard.rb +733 -0
- metadata +61 -11
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.occurences.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.occurences.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 occurences.
|
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
|
+
|