vpim 0.658 → 0.695

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.
@@ -7,6 +7,7 @@
7
7
  =end
8
8
 
9
9
  require 'enumerator'
10
+ require "net/http"
10
11
 
11
12
  require 'plist'
12
13
 
@@ -19,6 +20,7 @@ module Vpim
19
20
  # Currently supported repository types are:
20
21
  # - Repo::Apple3, an Apple iCal3 repository.
21
22
  # - Repo::Directory, a directory hierarchy containing .ics files
23
+ # - Repo::Uri, a URI that identifies a single iCalendar
22
24
  #
23
25
  # All repository types support at least the methods of Repo, and all
24
26
  # repositories return calendars that support at least the methods of
@@ -79,7 +81,9 @@ module Vpim
79
81
  return Enumerable::Enumerator.new(self, :each, klass)
80
82
  end
81
83
 
82
- cals = Vpim::Icalendar.decode(File.open(file))
84
+ cals = open(file) do |io|
85
+ Vpim::Icalendar.decode(io)
86
+ end
83
87
 
84
88
  cals.each do |cal|
85
89
  cal.each(klass, &block)
@@ -176,6 +180,82 @@ module Vpim
176
180
  self
177
181
  end
178
182
  end
183
+
184
+ class Uri < Repo
185
+ def self.uri_check(uri)
186
+ uri = case uri
187
+ when URI
188
+ uri
189
+ else
190
+ begin
191
+ URI.parse(uri)
192
+ rescue URI::InvalidURIError => e
193
+ raise ArgumentError, "Invalid URI for #{uri.inspect} - #{e.to_s}"
194
+ end
195
+ end
196
+ unless uri.scheme == "http"
197
+ raise ArgumentError, "Unsupported URI scheme for #{uri.inspect}"
198
+ end
199
+ uri
200
+ end
201
+
202
+ class Calendar < Repo::Calendar
203
+ def body
204
+ end
205
+
206
+ def initialize(uri) #:nodoc:
207
+ @uri = Uri.uri_check(uri)
208
+ end
209
+
210
+ def name #:nodoc:
211
+ @uri.to_s
212
+ end
213
+
214
+ def displayed #:nodoc:
215
+ true
216
+ end
217
+
218
+ def each(klass, &block) #:nodoc:
219
+ unless iterator?
220
+ return Enumerable::Enumerator.new(self, :each, klass)
221
+ end
222
+
223
+ cals = Vpim::Icalendar.decode(encode)
224
+
225
+ cals.each do |cal|
226
+ cal.each(klass, &block)
227
+ end
228
+ self
229
+ end
230
+
231
+ def encode #:nodoc:
232
+ Net::HTTP.get_response(@uri) do |result|
233
+ accum = ""
234
+ =begin
235
+ better to let this pass up as an invalid encoding error
236
+ if result.code != "200"
237
+ raise StandardError,
238
+ "HTTP GET of #{@uri.to_s.inspect} failed with #{result.code} #{result.error_type}"
239
+ end
240
+ =end
241
+ result.read_body do |chunk|
242
+ accum << chunk
243
+ end
244
+ return accum
245
+ end
246
+ end
247
+
248
+ end
249
+
250
+ def initialize(where)
251
+ @where = Uri.uri_check(where)
252
+ end
253
+
254
+ def each #:nodoc:
255
+ yield Calendar.new(@where)
256
+ self
257
+ end
258
+ end
179
259
  end
180
260
  end
181
261
 
File without changes
@@ -7,9 +7,9 @@
7
7
  =end
8
8
 
9
9
  module Vpim
10
- PRODID = '-//Ensemble Independent//vPim 0.658//EN'
10
+ PRODID = '-//Ensemble Independent//vPim 0.695//EN'
11
11
 
12
- VERSION = '0.658'
12
+ VERSION = '0.695'
13
13
 
14
14
  # Return the API version as a string.
15
15
  def Vpim.version
File without changes
File without changes
File without changes
File without changes
@@ -18,8 +18,8 @@ Usage: #{$0} [where]
18
18
 
19
19
  Shows events and todos occuring soon.
20
20
 
21
- By default, the Apple iCal v3 calendars are used, but if a location where
22
- .ics files is specified, any calendars found there will be used.
21
+ By default, the Apple iCal v3 calendars are used, but if a location where .ics
22
+ files can be found is specified, any calendars found there will be used.
23
23
 
24
24
  Options
25
25
  -h,--help Print this helpful message.
@@ -29,6 +29,7 @@ Options
29
29
  EOF
30
30
 
31
31
  opt_debug = nil
32
+ opt_dump = nil
32
33
  opt_verbose = nil
33
34
  opt_days = 7
34
35
 
@@ -53,6 +54,9 @@ opts.each do |opt, arg|
53
54
 
54
55
  when "--debug" then
55
56
  opt_verbose = true
57
+ if opt_debug
58
+ opt_dump = true
59
+ end
56
60
  opt_debug = true
57
61
  end
58
62
  end
@@ -64,12 +68,12 @@ if ARGV.length > 0
64
68
  calendars << cal
65
69
  end
66
70
  else
67
- Vpim::Repo::Ical3.each() do |cal|
71
+ Vpim::Repo::Apple3.new.each() do |cal|
68
72
  calendars << cal
69
73
  end
70
74
  end
71
75
 
72
- if opt_debug
76
+ if opt_dump
73
77
  pp ARGV
74
78
  pp calendars
75
79
  end
@@ -81,7 +85,7 @@ t0[0] = t0[1] = t0[2] = 0 # sec,min,hour = 0
81
85
  t0 = Time.local(*t0)
82
86
  t1 = t0 + opt_days * SECSPERDAY
83
87
 
84
- if opt_debug
88
+ if opt_dump
85
89
  puts "to: #{t0}"
86
90
  puts "t1: #{t1}"
87
91
  end
@@ -95,12 +99,13 @@ all_events = []
95
99
  all_todos = []
96
100
 
97
101
  calendars.each do |cal|
98
- if opt_debug; puts cal.name; end
102
+ if opt_debug; puts "Calendar: #{cal.name}"; end
99
103
 
104
+ # TODO - mv collection algorithm to library
100
105
  begin
101
106
  cal.events.each do |e|
102
107
  begin
103
- if opt_debug; pp e; end
108
+ if opt_dump; pp e; end
104
109
  if e.occurs_in?(t0, t1)
105
110
  if e.summary
106
111
  all_events.push(e)
@@ -111,12 +116,13 @@ calendars.each do |cal|
111
116
  end
112
117
  end
113
118
 
114
- all_todos.concat(cal.todos)
119
+ all_todos.concat(cal.todos.to_a)
115
120
  end
116
121
  end
117
122
 
118
123
  puts
119
124
 
125
+ # TODO - mv sorting algorithm to library
120
126
  def start_of_first_occurrence(t0, t1, e)
121
127
  e.occurrences(t1) do |t|
122
128
  # An event might start before t0, but end after it..., in which case
@@ -137,7 +143,7 @@ all_events.each do |e|
137
143
 
138
144
  if opt_verbose
139
145
  if e.description; puts " description=#{e.description}"; end
140
- if e.comments; puts " comment=#{e.comments.first}"; end
146
+ if e.comments.any?; puts " comment=#{e.comments.first}"; end
141
147
  if e.location; puts " location=#{e.location}"; end
142
148
  if e.status; puts " status=#{e.status}"; end
143
149
  if e.dtstart; puts " dtstart=#{e.dtstart}"; end
@@ -145,15 +151,15 @@ all_events.each do |e|
145
151
  end
146
152
 
147
153
  i = 1
148
- e.occurrences.each_until(t1).each do |t|
154
+ e.occurrences(t1) do |t|
149
155
  # An event might start before t0, but end after it..., in which case
150
156
  # we are still interested.
151
157
  dstr = ''
152
158
  if e.duration
153
- d = e.duration
154
159
  dstr = " for #{Vpim::Duration.new(e.duration).to_s}"
155
160
  end
156
161
 
162
+ # TODO - mv to library, as variant of occurs_in?
157
163
  if (t + (e.duration || 0)) >= t0
158
164
  puts " ##{i} on #{t}#{dstr}"
159
165
  i += 1
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,74 @@
1
+ require 'test/common'
2
+ require 'sinatra/test/unit'
3
+
4
+ require 'vpim/agent/app'
5
+
6
+ class IcsAgent < Test::Unit::TestCase
7
+
8
+ def to_str
9
+ @caldata
10
+ end
11
+
12
+ def setup
13
+ @thrd = data_on_port(self, 9876)
14
+ end
15
+ def teardown
16
+ @thrd.kill
17
+ end
18
+
19
+ def test_ics_atom_query
20
+ @caldata = open('test/calendars/weather.calendar/Events/1205042405-0-0.ics').read
21
+
22
+ get '/ics/atom?http://127.0.0.1:9876'
23
+
24
+ #pp @response
25
+ assert(@response.body =~ /<\?xml/)
26
+ assert_equal(Vpim::Agent::Atomize::MIME, @response['Content-Type'])
27
+ assert_equal(200, @response.status)
28
+ assert(@response.body =~ Regexp.new(
29
+ Regexp.quote(
30
+ "<id>http://example.org/ics/atom?http://127.0.0.1:9876</id>"
31
+ )), @response.body)
32
+ end
33
+
34
+ def test_ics
35
+ get '/ics'
36
+
37
+ assert(@response.body =~ /<html/)
38
+ assert_equal('text/html', @response['Content-Type'])
39
+ assert_equal(200, @response.status)
40
+ assert(@response.body =~ Regexp.new(
41
+ Regexp.quote("<title>Subscribe")), @response.body)
42
+ end
43
+
44
+ def test_ics_query
45
+ @caldata = open('test/calendars/weather.calendar/Events/1205042405-0-0.ics').read
46
+
47
+ get '/ics?http://127.0.0.1:9876'
48
+
49
+ assert(@response.body =~ /<html/)
50
+ assert_equal('text/html', @response['Content-Type'])
51
+ assert_equal(200, @response.status)
52
+
53
+ assert(@response.body =~ Regexp.new(
54
+ Regexp.quote("Subscribe to")), @response.body)
55
+ end
56
+
57
+ def test_ics_atom
58
+ get '/ics/atom'
59
+ assert_equal(302, @response.status)
60
+ end
61
+
62
+ =begin
63
+
64
+ WTF? Sinatra doesn't run it's error catcher in unit test mode?
65
+ def test_ics_atom_query_bad
66
+ get '/ics/atom?http://example.com'
67
+ assert_equal(500, @response.status)
68
+ assert(@response.body =~ Regexp.new(
69
+ Regexp.quote("error")), @response.body)
70
+ end
71
+ =end
72
+
73
+ end
74
+
@@ -0,0 +1,84 @@
1
+ require 'test/common'
2
+
3
+ require 'vpim/agent/atomize'
4
+ require 'vpim/icalendar'
5
+ require 'vpim/view'
6
+
7
+ class TextAgentAtomize < Test::Unit::TestCase
8
+
9
+ def atomize(cal, feeduri, caluri, filter=nil)
10
+ ical = Vpim::Icalendar.decode(cal).first
11
+ if filter
12
+ ical = filter.call(ical)
13
+ end
14
+ feed = Vpim::Agent::Atomize.calendar(ical, feeduri, caluri)
15
+ return ical, feed
16
+ end
17
+
18
+ def test_minimal
19
+ ical, feed = atomize(<<'__', "http://example.com/feed", "http://example.com/calendar")
20
+ BEGIN:VCALENDAR
21
+ BEGIN:VEVENT
22
+ DTSTART:20090214T144503
23
+ END:VEVENT
24
+ END:VCALENDAR
25
+ __
26
+
27
+ assert_equal(feed.entries.size, 1)
28
+ assert_equal("http://example.com/feed", feed.id)
29
+ assert_equal("http://example.com/calendar", feed.title)
30
+ assert(feed.to_xml.to_str)
31
+ assert_equal(nil, feed.entries.first.title)
32
+ assert_equal(nil, feed.entries.first.content)
33
+ #puts feed.to_xml
34
+ end
35
+
36
+ def test_small
37
+ ical, feed = atomize(<<'__', "http://example.com/feed", "http://example.com/calendar")
38
+ BEGIN:VCALENDAR
39
+ BEGIN:VEVENT
40
+ DTSTART:20090214T144503
41
+ SUMMARY:I am summarized
42
+ DESCRIPTION:And I am described
43
+ UID:very, very, unique
44
+ END:VEVENT
45
+ END:VCALENDAR
46
+ __
47
+
48
+ assert_equal(feed.entries.size, 1)
49
+ assert_equal("http://example.com/feed", feed.id)
50
+ assert_equal("http://example.com/calendar", feed.title)
51
+ assert_equal("I am summarized", feed.entries.first.title)
52
+ assert_equal("And I am described", feed.entries.first.content)
53
+ assert(feed.to_xml.to_str)
54
+ #puts feed.to_xml
55
+ end
56
+
57
+ def test_recurring
58
+ filter = proc do |cal|
59
+ Vpim::View.week(cal)
60
+ end
61
+ ical, feed = atomize(<<'__', "http://example.com/feed", "http://example.com/calendar", filter)
62
+ BEGIN:VCALENDAR
63
+ BEGIN:VEVENT
64
+ DTSTART:20090214T144503
65
+ RRULE:FREQ=weekly
66
+ SUMMARY:I am summarized
67
+ DESCRIPTION:And I am described
68
+ UID:very, very, unique
69
+ END:VEVENT
70
+ END:VCALENDAR
71
+ __
72
+
73
+ puts feed.to_xml
74
+ assert_equal(1, feed.entries.size)
75
+ assert_equal("http://example.com/feed", feed.id)
76
+ assert_equal("http://example.com/calendar", feed.title)
77
+ assert_equal("I am summarized", feed.entries.first.title)
78
+ assert_equal("And I am described", feed.entries.first.content)
79
+ assert(feed.to_xml.to_str)
80
+ end
81
+
82
+ end
83
+
84
+
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'vpim/repo'
4
+ require 'vpim/agent/calendars'
5
+ require 'test/common'
6
+
7
+ class TestAgentCalendars < Test::Unit::TestCase
8
+ Apple3 = Vpim::Repo::Apple3
9
+ Directory = Vpim::Repo::Directory
10
+ Uri = Vpim::Repo::Uri
11
+ Agent = Vpim::Agent
12
+ Path = Agent::Path
13
+ NotFound = Agent::NotFound
14
+
15
+ def setup
16
+ @testdir = Dir.getwd + "/test" #File.dirname($0) doesn't work with rcov :-(
17
+ @caldir = @testdir + "/calendars"
18
+ @eventsz = Dir[@caldir + "/**/*.ics"].size
19
+ assert(@testdir)
20
+ assert(test(?d, @caldir), "no caldir "+@caldir)
21
+ end
22
+
23
+ def assert_is_text_calendar(text)
24
+ lines = text.split("\n")
25
+ lines = lines.first, lines.last
26
+ assert_equal("BEGIN:VCALENDAR", lines.first.upcase, lines)
27
+ assert_equal("END:VCALENDAR", lines.last.upcase, lines)
28
+ end
29
+
30
+ def test_agent_calendars
31
+ repo = Apple3.new(@caldir)
32
+ rest = Agent::Calendars.new(repo)
33
+
34
+ out1, form = rest.get(Path.new("http://host/here", "/here"))
35
+ assert_equal("text/html", form)
36
+ #puts(out1)
37
+
38
+ out1, form = rest.get(Path.new("http://host/here/weather%2fLeavenworth", "/here"))
39
+ assert_equal("text/html", form)
40
+ #puts(out1)
41
+
42
+ out2, form = rest.get(Path.new("http://host/here/weather%2fLeavenworth/calendar", "/here"))
43
+ assert_equal("text/calendar", form)
44
+ assert_is_text_calendar(out2)
45
+
46
+ #assert_equal(out1, out2)
47
+
48
+ assert_raise(Vpim::Agent::NotFound) do
49
+ rest.get(Path.new("http://host/here/weather%2fLeavenworth/an_unknown_protocol", "/here"))
50
+ end
51
+ assert_raise(Vpim::Agent::NotFound) do
52
+ rest.get(Path.new("http://host/here/no_such_calendar", "/here"))
53
+ end
54
+
55
+ assert_equal(["","/","/"], Vpim::Agent::Path.split_path("/%2F/%2F"))
56
+ assert_equal(["/","/"], Vpim::Agent::Path.split_path("%2F/%2F"))
57
+ assert_equal(["calendars", "weather/Leavenworth"],
58
+ Vpim::Agent::Path.split_path("calendars/weather%2FLeavenworth"))
59
+ end
60
+
61
+ def test_agent_calendar_atom
62
+ repo = Apple3.new(@caldir)
63
+ rest = Agent::Calendars.new(repo)
64
+
65
+ out, form = rest.get(Path.new("http://host/here/weather%2fLeavenworth/atom", "/here"))
66
+ assert_equal("application/atom+xml", form)
67
+ #pp out
68
+ #assert_is_atom(out)
69
+ end
70
+
71
+ def _test_path_shift(url, shifts)
72
+ # last shift should be a nil
73
+ shifts << nil
74
+
75
+ # presence or absence of a trailing / should not affect shifting
76
+ ["", "/"].each do |trailer|
77
+ path = Path.new(url + trailer)
78
+ shifts.each do |_|
79
+ assert_equal(_, path.shift)
80
+ end
81
+ end
82
+ end
83
+
84
+ def test_path_shift
85
+ _test_path_shift("http://host.ex", [])
86
+ _test_path_shift("http://host.ex/a", ["a"])
87
+ _test_path_shift("http://host.ex/a/b", ["a", "b"])
88
+ _test_path_shift("http://host.ex/a/b/c", ["a", "b", "c"])
89
+ end
90
+
91
+ def _test_path_prefix(base, parts, shifts, prefix)
92
+ path = Path.new(base+parts.join("/"))
93
+ shifts.times{ path.shift }
94
+ assert_equal(prefix, path.prefix)
95
+ end
96
+
97
+ def test_path_prefix
98
+ _test_path_prefix("http://host.ex/", [], 0, "/")
99
+ _test_path_prefix("http://host.ex/", ["a"], 0, "/")
100
+ _test_path_prefix("http://host.ex/", ["a"], 1, "/")
101
+ _test_path_prefix("http://host.ex/", ["a"], 2, "/a/")
102
+ _test_path_prefix("http://host.ex/", ["a"], 3, "/a/")
103
+ _test_path_prefix("http://host.ex/", ["a", "b"], 0, "/")
104
+ _test_path_prefix("http://host.ex/", ["a", "b"], 1, "/")
105
+ _test_path_prefix("http://host.ex/", ["a", "b"], 2, "/a/")
106
+ _test_path_prefix("http://host.ex/", ["a", "b"], 3, "/a/b/")
107
+ end
108
+
109
+ =begin
110
+ def test_atomize
111
+ repo = Apple3.new(@caldir)
112
+ cal = repo.find{true}
113
+ a = Vpim::Agent::Atomize.new(cal)
114
+ assert( a.get(Path.new("http://example.com/path")))
115
+ end
116
+
117
+ def x_test_uri_query
118
+ uri = "http://example.com/ics/atom?http://localhost:9876"
119
+
120
+ repo = Uri.new("http://localhost:9876")
121
+ rest = Agent::Calendars.new(repo)
122
+ out1, form = rest.get(Path.new("http://example.com/ics", "/ics/atom"))
123
+ p [out1, form]
124
+ end
125
+ =end
126
+
127
+ end
128
+