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,24 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright (C) 2008 Sam Roberts
|
3
|
+
|
4
|
+
This library is free software; you can redistribute it and/or modify it
|
5
|
+
under the same terms as the ruby language itself, see the file COPYING for
|
6
|
+
details.
|
7
|
+
=end
|
8
|
+
|
9
|
+
module Vpim
|
10
|
+
class Icalendar
|
11
|
+
module Property
|
12
|
+
|
13
|
+
module Resources
|
14
|
+
|
15
|
+
def resources
|
16
|
+
proptextlistarray 'RESOURCES'
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
|
data/lib/vpim/repo.rb
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright (C) 2008 Sam Roberts
|
3
|
+
|
4
|
+
This library is free software; you can redistribute it and/or modify it
|
5
|
+
under the same terms as the ruby language itself, see the file COPYING for
|
6
|
+
details.
|
7
|
+
=end
|
8
|
+
|
9
|
+
require 'enumerator'
|
10
|
+
|
11
|
+
require 'plist'
|
12
|
+
|
13
|
+
require 'vpim/icalendar'
|
14
|
+
require 'vpim/duration'
|
15
|
+
|
16
|
+
module Vpim
|
17
|
+
# A Repo is a representation of a calendar repository.
|
18
|
+
#
|
19
|
+
# Currently supported repository types are:
|
20
|
+
# - Repo::Apple3, an Apple iCal3 repository.
|
21
|
+
# - Repo::Directory, a directory hierarchy containing .ics files
|
22
|
+
#
|
23
|
+
# All repository types support at least the methods of Repo, and all
|
24
|
+
# repositories return calendars that support at least the methods of
|
25
|
+
# Repo::Calendar.
|
26
|
+
class Repo
|
27
|
+
include Enumerable
|
28
|
+
|
29
|
+
# Open a repository at location +where+.
|
30
|
+
def initialize(where)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Enumerate the calendars in the repository.
|
34
|
+
def each #:yield: calendar
|
35
|
+
end
|
36
|
+
|
37
|
+
# A calendar abstraction. It models a calendar in a calendar repository
|
38
|
+
# that may not be an iCalendar.
|
39
|
+
#
|
40
|
+
# It has methods that behave identically to Icalendar, but it also has
|
41
|
+
# methods like name and displayed that are not present in an iCalendar.
|
42
|
+
class Calendar
|
43
|
+
include Enumerable
|
44
|
+
|
45
|
+
# The calendar name.
|
46
|
+
def name
|
47
|
+
end
|
48
|
+
|
49
|
+
# Whether a calendar should be displayed.
|
50
|
+
#
|
51
|
+
# TODO - should be #displayed?
|
52
|
+
def displayed
|
53
|
+
end
|
54
|
+
|
55
|
+
# Encode into iCalendar format.
|
56
|
+
def encode
|
57
|
+
end
|
58
|
+
|
59
|
+
# Enumerate the components in the calendar, both todos and events, or
|
60
|
+
# the specified klass. Like Icalendar#each()
|
61
|
+
def each(klass=nil, &block) #:yield: component
|
62
|
+
end
|
63
|
+
|
64
|
+
# Enumerate the events in the calendar.
|
65
|
+
def events(&block) #:yield: Vevent
|
66
|
+
each(Vpim::Icalendar::Vevent, &block)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Enumerate the todos in the calendar.
|
70
|
+
def todos(&block) #:yield: Vtodo
|
71
|
+
each(Vpim::Icalendar::Vtodo, &block)
|
72
|
+
end
|
73
|
+
|
74
|
+
# The method definitions are just to fool rdoc, not to be used.
|
75
|
+
%w{each name displayed encode}.each{|m| remove_method m}
|
76
|
+
|
77
|
+
def file_each(file, klass, &block) #:nodoc:
|
78
|
+
unless iterator?
|
79
|
+
return Enumerable::Enumerator.new(self, :each, klass)
|
80
|
+
end
|
81
|
+
|
82
|
+
cals = Vpim::Icalendar.decode(File.open(file))
|
83
|
+
|
84
|
+
cals.each do |cal|
|
85
|
+
cal.each(klass, &block)
|
86
|
+
end
|
87
|
+
self
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class Repo
|
93
|
+
include Enumerable
|
94
|
+
|
95
|
+
# An Apple iCal version 3 repository.
|
96
|
+
class Apple3 < Repo
|
97
|
+
def initialize(where = "~/Library/Calendars")
|
98
|
+
@where = where.to_str
|
99
|
+
end
|
100
|
+
|
101
|
+
def each #:nodoc:
|
102
|
+
Dir[ File.expand_path(@where + "/**/*.calendar") ].each do |dir|
|
103
|
+
yield Calendar.new(dir)
|
104
|
+
end
|
105
|
+
self
|
106
|
+
end
|
107
|
+
|
108
|
+
class Calendar < Repo::Calendar
|
109
|
+
def initialize(dir) # :nodoc:
|
110
|
+
@dir = dir
|
111
|
+
end
|
112
|
+
|
113
|
+
def plist(key) #:nodoc:
|
114
|
+
Plist::parse_xml( @dir + "/Info.plist")[key]
|
115
|
+
end
|
116
|
+
|
117
|
+
def name #:nodoc:
|
118
|
+
plist "Title"
|
119
|
+
end
|
120
|
+
|
121
|
+
def displayed #:nodoc:
|
122
|
+
1 == plist("Checked")
|
123
|
+
end
|
124
|
+
|
125
|
+
def each(klass=nil, &block) #:nodoc:
|
126
|
+
unless iterator?
|
127
|
+
return Enumerable::Enumerator.new(self, :each, klass)
|
128
|
+
end
|
129
|
+
Dir[ @dir + "/Events/*.ics" ].map do |ics|
|
130
|
+
file_each(ics, klass, &block)
|
131
|
+
end
|
132
|
+
self
|
133
|
+
end
|
134
|
+
|
135
|
+
def encode #:nodoc:
|
136
|
+
Icalendar.create2 do |cal|
|
137
|
+
each{|c| cal << c}
|
138
|
+
end.encode
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
class Directory < Repo
|
145
|
+
class Calendar < Repo::Calendar
|
146
|
+
def initialize(file) #:nodoc:
|
147
|
+
@file = file
|
148
|
+
end
|
149
|
+
|
150
|
+
def name #:nodoc:
|
151
|
+
File.basename(@file)
|
152
|
+
end
|
153
|
+
|
154
|
+
def displayed #:nodoc:
|
155
|
+
true
|
156
|
+
end
|
157
|
+
|
158
|
+
def each(klass, &block) #:nodoc:
|
159
|
+
file_each(@file, klass, &block)
|
160
|
+
end
|
161
|
+
|
162
|
+
def encode #:nodoc:
|
163
|
+
open(@file, "r"){|f| f.read}
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
def initialize(where = ".")
|
169
|
+
@where = where.to_str
|
170
|
+
end
|
171
|
+
|
172
|
+
def each #:nodoc:
|
173
|
+
Dir[ File.expand_path(@where + "/**/*.ics") ].each do |file|
|
174
|
+
yield Calendar.new(file)
|
175
|
+
end
|
176
|
+
self
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
data/lib/vpim/rfc2425.rb
ADDED
@@ -0,0 +1,367 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright (C) 2008 Sam Roberts
|
3
|
+
|
4
|
+
This library is free software; you can redistribute it and/or modify it
|
5
|
+
under the same terms as the ruby language itself, see the file COPYING for
|
6
|
+
details.
|
7
|
+
=end
|
8
|
+
|
9
|
+
require 'vpim/vpim'
|
10
|
+
|
11
|
+
module Vpim
|
12
|
+
# Contains regular expression strings for the EBNF of RFC 2425.
|
13
|
+
module Bnf #:nodoc:
|
14
|
+
|
15
|
+
# 1*(ALPHA / DIGIT / "-")
|
16
|
+
# Note: I think I can add A-Z here, and get rid of the "i" matches elsewhere.
|
17
|
+
# Note: added '_' to allowed because its produced by Notes (X-LOTUS-CHILD_UID:)
|
18
|
+
# Note: added '/' to allowed because its produced by KAddressBook (X-messaging/xmpp-All:)
|
19
|
+
# Note: added ' ' to allowed because its produced by highrisehq.com (X-GOOGLE TALK:)
|
20
|
+
NAME = '[-a-z0-9_/][-a-z0-9_/ ]*'
|
21
|
+
|
22
|
+
# <"> <Any character except CTLs, DQUOTE> <">
|
23
|
+
QSTR = '"([^"]*)"'
|
24
|
+
|
25
|
+
# *<Any character except CTLs, DQUOTE, ";", ":", ",">
|
26
|
+
PTEXT = '([^";:,]+)'
|
27
|
+
|
28
|
+
# param-value = ptext / quoted-string
|
29
|
+
PVALUE = "(?:#{QSTR}|#{PTEXT})"
|
30
|
+
|
31
|
+
# param = name "=" param-value *("," param-value)
|
32
|
+
# Note: v2.1 allows a type or encoding param-value to appear without the type=
|
33
|
+
# or the encoding=. This is hideous, but we try and support it, if there
|
34
|
+
# is no "=", then $2 will be "", and we will treat it as a v2.1 param.
|
35
|
+
PARAM = ";(#{NAME})(=?)((?:#{PVALUE})?(?:,#{PVALUE})*)"
|
36
|
+
|
37
|
+
# V3.0: contentline = [group "."] name *(";" param) ":" value
|
38
|
+
# V2.1: contentline = *( group "." ) name *(";" param) ":" value
|
39
|
+
#
|
40
|
+
# We accept the V2.1 syntax for backwards compatibility.
|
41
|
+
#LINE = "((?:#{NAME}\\.)*)?(#{NAME})([^:]*)\:(.*)"
|
42
|
+
LINE = "^((?:#{NAME}\\.)*)?(#{NAME})((?:#{PARAM})*):(.*)$"
|
43
|
+
|
44
|
+
# date = date-fullyear ["-"] date-month ["-"] date-mday
|
45
|
+
# date-fullyear = 4 DIGIT
|
46
|
+
# date-month = 2 DIGIT
|
47
|
+
# date-mday = 2 DIGIT
|
48
|
+
DATE = '(\d\d\d\d)-?(\d\d)-?(\d\d)'
|
49
|
+
|
50
|
+
# time = time-hour [":"] time-minute [":"] time-second [time-secfrac] [time-zone]
|
51
|
+
# time-hour = 2 DIGIT
|
52
|
+
# time-minute = 2 DIGIT
|
53
|
+
# time-second = 2 DIGIT
|
54
|
+
# time-secfrac = "," 1*DIGIT
|
55
|
+
# time-zone = "Z" / time-numzone
|
56
|
+
# time-numzone = sign time-hour [":"] time-minute
|
57
|
+
TIME = '(\d\d):?(\d\d):?(\d\d)(\.\d+)?(Z|[-+]\d\d:?\d\d)?'
|
58
|
+
|
59
|
+
# integer = (["+"] / "-") 1*DIGIT
|
60
|
+
INTEGER = '[-+]?\d+'
|
61
|
+
|
62
|
+
# QSAFE-CHAR = WSP / %x21 / %x23-7E / NON-US-ASCII
|
63
|
+
# ; Any character except CTLs and DQUOTE
|
64
|
+
QSAFECHAR = '[ \t\x21\x23-\x7e\x80-\xff]'
|
65
|
+
|
66
|
+
# SAFE-CHAR = WSP / %x21 / %x23-2B / %x2D-39 / %x3C-7E / NON-US-ASCII
|
67
|
+
# ; Any character except CTLs, DQUOTE, ";", ":", ","
|
68
|
+
SAFECHAR = '[ \t\x21\x23-\x2b\x2d-\x39\x3c-\x7e\x80-\xff]'
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
module Vpim
|
73
|
+
# Split on \r\n or \n to get the lines, unfold continued lines (they
|
74
|
+
# start with ' ' or \t), and return the array of unfolded lines.
|
75
|
+
#
|
76
|
+
# This also supports the (invalid) encoding convention of allowing empty
|
77
|
+
# lines to be inserted for readability - it does this by dropping zero-length
|
78
|
+
# lines.
|
79
|
+
def Vpim.unfold(card) #:nodoc:
|
80
|
+
unfolded = []
|
81
|
+
|
82
|
+
card.each do |line|
|
83
|
+
line.chomp!
|
84
|
+
# If it's a continuation line, add it to the last.
|
85
|
+
# If it's an empty line, drop it from the input.
|
86
|
+
if( line =~ /^[ \t]/ )
|
87
|
+
unfolded[-1] << line[1, line.size-1]
|
88
|
+
elsif( line =~ /^$/ )
|
89
|
+
else
|
90
|
+
unfolded << line
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
unfolded
|
95
|
+
end
|
96
|
+
|
97
|
+
# Convert a +sep+-seperated list of values into an array of values.
|
98
|
+
def Vpim.decode_list(value, sep = ',') # :nodoc:
|
99
|
+
list = []
|
100
|
+
|
101
|
+
value.each(sep) do |item|
|
102
|
+
item.chomp!(sep)
|
103
|
+
list << yield(item)
|
104
|
+
end
|
105
|
+
list
|
106
|
+
end
|
107
|
+
|
108
|
+
# Convert a RFC 2425 date into an array of [year, month, day].
|
109
|
+
def Vpim.decode_date(v) # :nodoc:
|
110
|
+
unless v =~ %r{^\s*#{Bnf::DATE}\s*$}
|
111
|
+
raise Vpim::InvalidEncodingError, "date not valid (#{v})"
|
112
|
+
end
|
113
|
+
[$1.to_i, $2.to_i, $3.to_i]
|
114
|
+
end
|
115
|
+
|
116
|
+
# Convert a RFC 2425 date into a Date object.
|
117
|
+
def self.decode_date_to_date(v)
|
118
|
+
Date.new(*decode_date(v))
|
119
|
+
end
|
120
|
+
|
121
|
+
# Note in the following the RFC2425 allows yyyy-mm-ddThh:mm:ss, but RFC2445
|
122
|
+
# does not. I choose to encode to the subset that is valid for both.
|
123
|
+
|
124
|
+
# Encode a Date object as "yyyymmdd".
|
125
|
+
def Vpim.encode_date(d) # :nodoc:
|
126
|
+
"%0.4d%0.2d%0.2d" % [ d.year, d.mon, d.day ]
|
127
|
+
end
|
128
|
+
|
129
|
+
# Encode a Date object as "yyyymmdd".
|
130
|
+
def Vpim.encode_time(d) # :nodoc:
|
131
|
+
"%0.4d%0.2d%0.2d" % [ d.year, d.mon, d.day ]
|
132
|
+
end
|
133
|
+
|
134
|
+
# Encode a Time or DateTime object as "yyyymmddThhmmss"
|
135
|
+
def Vpim.encode_date_time(d) # :nodoc:
|
136
|
+
"%0.4d%0.2d%0.2dT%0.2d%0.2d%0.2d" % [ d.year, d.mon, d.day, d.hour, d.min, d.sec ]
|
137
|
+
end
|
138
|
+
|
139
|
+
# Convert a RFC 2425 time into an array of [hour,min,sec,secfrac,timezone]
|
140
|
+
def Vpim.decode_time(v) # :nodoc:
|
141
|
+
unless match = %r{^\s*#{Bnf::TIME}\s*$}.match(v)
|
142
|
+
raise Vpim::InvalidEncodingError, "time '#{v}' not valid"
|
143
|
+
end
|
144
|
+
hour, min, sec, secfrac, tz = match.to_a[1..5]
|
145
|
+
|
146
|
+
[hour.to_i, min.to_i, sec.to_i, secfrac ? secfrac.to_f : 0, tz]
|
147
|
+
end
|
148
|
+
|
149
|
+
def self.array_datetime_to_time(dtarray) #:nodoc:
|
150
|
+
# We get [ year, month, day, hour, min, sec, usec, tz ]
|
151
|
+
begin
|
152
|
+
tz = (dtarray.pop == "Z") ? :gm : :local
|
153
|
+
Time.send(tz, *dtarray)
|
154
|
+
rescue ArgumentError => e
|
155
|
+
raise Vpim::InvalidEncodingError, "#{tz} #{e} (#{dtarray.join(', ')})"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# Convert a RFC 2425 time into an array of Time objects.
|
160
|
+
def Vpim.decode_time_to_time(v) # :nodoc:
|
161
|
+
array_datetime_to_time(decode_date_time(v))
|
162
|
+
end
|
163
|
+
|
164
|
+
# Convert a RFC 2425 date-time into an array of [year,mon,day,hour,min,sec,secfrac,timezone]
|
165
|
+
def Vpim.decode_date_time(v) # :nodoc:
|
166
|
+
unless match = %r{^\s*#{Bnf::DATE}T#{Bnf::TIME}\s*$}.match(v)
|
167
|
+
raise Vpim::InvalidEncodingError, "date-time '#{v}' not valid"
|
168
|
+
end
|
169
|
+
year, month, day, hour, min, sec, secfrac, tz = match.to_a[1..8]
|
170
|
+
|
171
|
+
[
|
172
|
+
# date
|
173
|
+
year.to_i, month.to_i, day.to_i,
|
174
|
+
# time
|
175
|
+
hour.to_i, min.to_i, sec.to_i, secfrac ? secfrac.to_f : 0, tz
|
176
|
+
]
|
177
|
+
end
|
178
|
+
|
179
|
+
def Vpim.decode_date_time_to_datetime(v) #:nodoc:
|
180
|
+
year, month, day, hour, min, sec, secfrac, tz = Vpim.decode_date_time(v)
|
181
|
+
# TODO - DateTime understands timezones, so we could decode tz and use it.
|
182
|
+
DateTime.civil(year, month, day, hour, min, sec, 0)
|
183
|
+
end
|
184
|
+
|
185
|
+
# Vpim.decode_boolean
|
186
|
+
#
|
187
|
+
# float
|
188
|
+
#
|
189
|
+
# float_list
|
190
|
+
=begin
|
191
|
+
=end
|
192
|
+
|
193
|
+
# Convert an RFC2425 INTEGER value into an Integer
|
194
|
+
def Vpim.decode_integer(v) # :nodoc:
|
195
|
+
unless match = %r{\s*#{Bnf::INTEGER}\s*}.match(v)
|
196
|
+
raise Vpim::InvalidEncodingError, "integer not valid (#{v})"
|
197
|
+
end
|
198
|
+
v.to_i
|
199
|
+
end
|
200
|
+
|
201
|
+
#
|
202
|
+
# integer_list
|
203
|
+
|
204
|
+
# Convert a RFC2425 date-list into an array of dates.
|
205
|
+
def Vpim.decode_date_list(v) # :nodoc:
|
206
|
+
Vpim.decode_list(v) do |date|
|
207
|
+
date.strip!
|
208
|
+
if date.length > 0
|
209
|
+
Vpim.decode_date(date)
|
210
|
+
end
|
211
|
+
end.compact
|
212
|
+
end
|
213
|
+
|
214
|
+
# Convert a RFC 2425 time-list into an array of times.
|
215
|
+
def Vpim.decode_time_list(v) # :nodoc:
|
216
|
+
Vpim.decode_list(v) do |time|
|
217
|
+
time.strip!
|
218
|
+
if time.length > 0
|
219
|
+
Vpim.decode_time(time)
|
220
|
+
end
|
221
|
+
end.compact
|
222
|
+
end
|
223
|
+
|
224
|
+
# Convert a RFC 2425 date-time-list into an array of date-times.
|
225
|
+
def Vpim.decode_date_time_list(v) # :nodoc:
|
226
|
+
Vpim.decode_list(v) do |datetime|
|
227
|
+
datetime.strip!
|
228
|
+
if datetime.length > 0
|
229
|
+
Vpim.decode_date_time(datetime)
|
230
|
+
end
|
231
|
+
end.compact
|
232
|
+
end
|
233
|
+
|
234
|
+
# Convert RFC 2425 text into a String.
|
235
|
+
# \\ -> \
|
236
|
+
# \n -> NL
|
237
|
+
# \N -> NL
|
238
|
+
# \, -> ,
|
239
|
+
# \; -> ;
|
240
|
+
#
|
241
|
+
# I've seen double-quote escaped by iCal.app. Hmm. Ok, if you aren't supposed
|
242
|
+
# to escape anything but the above, everything else is ambiguous, so I'll
|
243
|
+
# just support it.
|
244
|
+
def Vpim.decode_text(v) # :nodoc:
|
245
|
+
# FIXME - I think this should trim leading and trailing space
|
246
|
+
v.gsub(/\\(.)/) do
|
247
|
+
case $1
|
248
|
+
when 'n', 'N'
|
249
|
+
"\n"
|
250
|
+
else
|
251
|
+
$1
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def Vpim.encode_text(v) #:nodoc:
|
257
|
+
v.to_str.gsub(/([\\,;\n])/) { $1 == "\n" ? "\\n" : "\\"+$1 }
|
258
|
+
end
|
259
|
+
|
260
|
+
# v is an Array of String, or just a single String
|
261
|
+
def Vpim.encode_text_list(v, sep = ",") #:nodoc:
|
262
|
+
begin
|
263
|
+
v.to_ary.map{ |t| Vpim.encode_text(t) }.join(sep)
|
264
|
+
rescue
|
265
|
+
Vpim.encode_text(v)
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
# Convert a +sep+-seperated list of TEXT values into an array of values.
|
270
|
+
def Vpim.decode_text_list(value, sep = ',') # :nodoc:
|
271
|
+
# Need to do in two stages, as best I can find.
|
272
|
+
list = value.scan(/([^#{sep}\\]*(?:\\.[^#{sep}\\]*)*)#{sep}/).map do |v|
|
273
|
+
Vpim.decode_text(v.first)
|
274
|
+
end
|
275
|
+
if value.match(/([^#{sep}\\]*(?:\\.[^#{sep}\\]*)*)$/)
|
276
|
+
list << $1
|
277
|
+
end
|
278
|
+
list
|
279
|
+
end
|
280
|
+
|
281
|
+
# param-value = paramtext / quoted-string
|
282
|
+
# paramtext = *SAFE-CHAR
|
283
|
+
# quoted-string = DQUOTE *QSAFE-CHAR DQUOTE
|
284
|
+
def Vpim.encode_paramtext(value)
|
285
|
+
case value
|
286
|
+
when %r{\A#{Bnf::SAFECHAR}*\z}
|
287
|
+
value
|
288
|
+
else
|
289
|
+
raise Vpim::Unencodable, "paramtext #{value.inspect}"
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
def Vpim.encode_paramvalue(value)
|
294
|
+
case value
|
295
|
+
when %r{\A#{Bnf::SAFECHAR}*\z}
|
296
|
+
value
|
297
|
+
when %r{\A#{Bnf::QSAFECHAR}*\z}
|
298
|
+
'"' + value + '"'
|
299
|
+
else
|
300
|
+
raise Vpim::Unencodable, "param-value #{value.inspect}"
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
|
305
|
+
# Unfold the lines in +card+, then return an array of one Field object per
|
306
|
+
# line.
|
307
|
+
def Vpim.decode(card) #:nodoc:
|
308
|
+
content = Vpim.unfold(card).collect { |line| DirectoryInfo::Field.decode(line) }
|
309
|
+
end
|
310
|
+
|
311
|
+
|
312
|
+
# Expand an array of fields into its syntactic entities. Each entity is a sequence
|
313
|
+
# of fields where the sequences is delimited by a BEGIN/END field. Since
|
314
|
+
# BEGIN/END delimited entities can be nested, we build a tree. Each entry in
|
315
|
+
# the array is either a Field or an array of entries (where each entry is
|
316
|
+
# either a Field, or an array of entries...).
|
317
|
+
def Vpim.expand(src) #:nodoc:
|
318
|
+
# output array to expand the src to
|
319
|
+
dst = []
|
320
|
+
# stack used to track our nesting level, as we see begin/end we start a
|
321
|
+
# new/finish the current entity, and push/pop that entity from the stack
|
322
|
+
current = [ dst ]
|
323
|
+
|
324
|
+
for f in src
|
325
|
+
if f.name? 'BEGIN'
|
326
|
+
e = [ f ]
|
327
|
+
|
328
|
+
current.last.push(e)
|
329
|
+
current.push(e)
|
330
|
+
|
331
|
+
elsif f.name? 'END'
|
332
|
+
current.last.push(f)
|
333
|
+
|
334
|
+
unless current.last.first.value? current.last.last.value
|
335
|
+
raise "BEGIN/END mismatch (#{current.last.first.value} != #{current.last.last.value})"
|
336
|
+
end
|
337
|
+
|
338
|
+
current.pop
|
339
|
+
|
340
|
+
else
|
341
|
+
current.last.push(f)
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
dst
|
346
|
+
end
|
347
|
+
|
348
|
+
# Split an array into an array of all the fields at the outer level, and
|
349
|
+
# an array of all the inner arrays of fields. Return the array [outer,
|
350
|
+
# inner].
|
351
|
+
def Vpim.outer_inner(fields) #:nodoc:
|
352
|
+
# TODO - use Enumerable#partition
|
353
|
+
# seperate into the outer-level fields, and the arrays of component
|
354
|
+
# fields
|
355
|
+
outer = []
|
356
|
+
inner = []
|
357
|
+
fields.each do |line|
|
358
|
+
case line
|
359
|
+
when Array; inner << line
|
360
|
+
else; outer << line
|
361
|
+
end
|
362
|
+
end
|
363
|
+
return outer, inner
|
364
|
+
end
|
365
|
+
|
366
|
+
end
|
367
|
+
|