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.
data/CHANGES CHANGED
@@ -1,3 +1,9 @@
1
+ 0.695 - 2009-03-01
2
+
3
+ - Alpha versions of vPim agent (http://agent.octetcloud.com)
4
+ - Fixed: reminder app had drifted out of sync with API change
5
+
6
+
1
7
  0.657 - 2008-08-14
2
8
 
3
9
  - Date#to_time renamed to Date#vpim_to_time. Apparently Rails also patches the core.
@@ -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
data/bin/rrule CHANGED
File without changes
@@ -0,0 +1,194 @@
1
+ =begin
2
+ Copyright (C) 2009 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 'sinatra'
10
+ require 'vpim/agent/atomize'
11
+ require 'vpim/repo'
12
+ require 'vpim/view'
13
+
14
+ require 'cgi'
15
+
16
+ configure do
17
+ server = Sinatra::Application.server
18
+ set :server, Proc.new {
19
+ if ENV.include?("PHP_FCGI_CHILDREN")
20
+ break "fastcgi" # Must NOT be the correct class name!
21
+ elsif ENV.include?("REQUEST_METHOD")
22
+ break "cgi" # Must NOT be the correct class name!
23
+ else
24
+ # Fall back on whatever it was going to be.
25
+ server
26
+ end
27
+ }
28
+ end
29
+
30
+ # I could wrap the Repo/Calendar/Atomize in a small class that would memoize
31
+ # ical data and atom output. Maybe even do an HTTP head for fast detection of
32
+ # change? Does a calendar have updated information? Can we memoize atom when
33
+ # ics doesn't change?
34
+
35
+ module Vpim
36
+ module Agent
37
+ module App
38
+ def self.atomize(caluri, feeduri)
39
+ repo = Vpim::Repo::Uri.new(caluri)
40
+ cal = repo.find{true}
41
+ cal = View.week(cal)
42
+ feed = Agent::Atomize.calendar(cal, feeduri, caluri, cal.name)
43
+ return feed.to_xml, Agent::Atomize::MIME
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ get '/ics' do
50
+ from = env['QUERY_STRING']
51
+
52
+ url = URI.parse(request.url)
53
+ url.query = nil
54
+ url_base = url.to_s
55
+ url_atom = nil
56
+
57
+ @url_ics = from # ics from here
58
+ @url_atom = nil
59
+
60
+ if not from.empty?
61
+ # Error out if we can't atomize the feed
62
+ Vpim::Agent::App.atomize(from, "http://example.com")
63
+
64
+ url = URI.parse(request.url)
65
+ url.path << "/atom"
66
+ url_atom = url.to_s
67
+ end
68
+
69
+ @url_base = url_base # clean input form
70
+ @url_atom = url_atom # atomized ics from here
71
+
72
+ haml :"ics.haml"
73
+ end
74
+
75
+ post '/ics' do
76
+ from = params[:url]
77
+ url = URI.parse(request.url)
78
+ url.query = from
79
+ redirect url.to_s
80
+ end
81
+
82
+ # When we support other forms..
83
+ #get '/ics/:form' do
84
+ # form = params[:form]
85
+ get '/ics/atom' do
86
+ from = env['QUERY_STRING']
87
+ port = env["SERVER_PORT"].to_i
88
+ here = request.url
89
+
90
+ if from.empty?
91
+ url = URI.parse(here)
92
+ url.path.sub(/atom$/, "")
93
+ redirect here.to_s
94
+ end
95
+
96
+ xml, xmltype = Vpim::Agent::App.atomize(from, here)
97
+
98
+ content_type xmltype
99
+ body xml
100
+ end
101
+
102
+ get '/ics/style.css' do
103
+ content_type 'text/css'
104
+ sass :"ics.sass"
105
+ end
106
+
107
+ error do
108
+ @url_error = CGI.escapeHTML(env['sinatra.error'].inspect)
109
+ haml :"ics.haml"
110
+ end
111
+
112
+ use_in_file_templates!
113
+
114
+ # FIXME - hard-coded :action paths below, bad!
115
+
116
+ __END__
117
+ @@ics.sass
118
+ body
119
+ :background-color gray
120
+ :font-size medium
121
+ a
122
+ :color black
123
+ :font-style italic
124
+ a:hover
125
+ :color darkred
126
+
127
+ #header
128
+ :border-bottom 3px solid darkred
129
+ #title
130
+ :color black
131
+ :font-size large
132
+
133
+ .text
134
+ :width 80%
135
+ -#:color yellow
136
+
137
+ #submit
138
+ :margin-top 30px
139
+ :margin-left 5%
140
+ #form
141
+ :padding
142
+ :top 10px
143
+ :bottom 10px
144
+ :left 10px
145
+ :right 10px
146
+ :text-align left
147
+ #url
148
+ :width 80%
149
+ #button
150
+ :font-weight bold
151
+ :text-align center
152
+
153
+ #subscribe
154
+ :margin-left 5%
155
+ -#.feed
156
+ -# :margin-left 10%
157
+
158
+ #footer
159
+ :border-top 3px solid darkred
160
+ :margin-top 20px
161
+ @@ics.haml
162
+ %html
163
+ %head
164
+ %title Subscribe to calendar feeds as atom feeds
165
+ %link{:href => '/ics/style.css', :media => 'screen', :type => 'text/css'}
166
+ %body
167
+ #header
168
+ %span#title Subscribe to calendar feeds as atom feeds
169
+ #submit
170
+ .text Calendar feeds are great, but sometimes all you want is an atom feed of what's coming up in the next week.
171
+ .text Paste the URL of the calendar below, submit it, and subscribe.
172
+ %form#form{:method => 'POST', :action => '/ics'}
173
+ %input#url{:name => 'url', :value => params[:url]}
174
+ %input#button{:type => 'submit', :value => 'Submit'}
175
+ - if @url_atom
176
+ #subscribe
177
+ .text
178
+ Subscribe to
179
+ %a{:href => @url_ics}= @url_ics
180
+ as:
181
+ %ul.feed
182
+ %a{:href => @url_atom}= @url_atom
183
+ (atom feed)
184
+ - if @url_error
185
+ #error.text
186
+ #preamble Sorry, trying to access:
187
+ #source= @url_ics
188
+ #transition resulted in:
189
+ #destination= @url_error
190
+ #footer
191
+ .text
192
+ :textile
193
+ Coming from the "Octet Cloud":http://octetcloud.com/ using "vPim":http://vpim.rubyforge.org/, piloted by cloud monkey "Sam Roberts":mailto:vieuxtech@gmail.com
194
+
@@ -0,0 +1,101 @@
1
+ =begin
2
+ Copyright (C) 2009 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 "atom"
10
+
11
+ module Vpim
12
+ module Agent
13
+
14
+ module Atomize
15
+ MIME = "application/atom+xml"
16
+
17
+ # +ical+, an icalendar, or at least a Repo calendar's subset of an Icalendar
18
+ # +feeduri+, the atom xml should know the URI of where the feed is available from.
19
+ # +caluri+, optionally, the URI of the calendar its converted from.
20
+ #
21
+ # TODO - and the URI of an alternative/html representation of this feed?
22
+ def self.calendar(ical, feeduri, caluri = nil, calname = nil)
23
+ mime = MIME
24
+
25
+ feeduri = feeduri.to_str
26
+ caluri = caluri
27
+ calname = (calname or caluri or "Unknown").to_str
28
+
29
+ f = Atom::Feed.new
30
+ # Mandatory attributes:
31
+ # For ID, we should use http://.../ics/atom?....., or just the URL of the ics?
32
+ # I think it can be a full URI... or maybe a sha-1 of our full URI?
33
+ # or like gmail, no id for feed,
34
+ # <id>tag:gmail.google.com,2004:1295062805013769502</id>
35
+ #
36
+ f.id = feeduri
37
+ f.title = calname
38
+ f.updated = Time.now
39
+ f.authors << Atom::Person.new(:name => (caluri or calname))
40
+ f.generator = Atom::Generator.new do |g|
41
+ g.name = Vpim::PRODID
42
+ g.uri = "http://vpim.rubyforge.org"
43
+ g.version = Vpim::VERSION
44
+ end
45
+
46
+ f.links << Atom::Link.new do |l|
47
+ l.href = feeduri
48
+ l.type = mime
49
+ l.rel = :self
50
+ end
51
+
52
+ if caluri
53
+ # This is maybe better described as :via, but with :alternate being
54
+ # an html view of this feed.
55
+ f.links << Atom::Link.new do |l|
56
+ l.href = caluri
57
+ l.type = "text/calendar"
58
+ l.rel = :alternate
59
+ end
60
+ end
61
+
62
+ # .icon = uri to the vAgent icon
63
+ entry_id = 0
64
+ ical.events do |ve|
65
+ # TODO - infinite?
66
+ ve.occurrences do |t|
67
+ f.entries << Atom::Entry.new do |e|
68
+ # iCalendar -> atom
69
+ # -----------------
70
+ # summary -> title
71
+ # description -> text/content
72
+ # uid -> id
73
+ # created -> published?
74
+ # organizer -> author?
75
+ # contact -> author?
76
+ # last-mod -> semantically, this is updated, but atom doesn't
77
+ # have the notion that an entry has a relationship to a time,
78
+ # other than the time the entry itself was published, and when
79
+ # the entry gets updated. We'll abuse updated for the event's time.
80
+ # categories -> where do "tags" go in atom, if anywhere?
81
+ # attachment -> into a link?
82
+ e.title = ve.summary if ve.summary
83
+ e.content = Atom::Content::Text.new(ve.description) if ve.description
84
+ e.updated = t
85
+
86
+ # Use "tag:", as defined by RFC4151, and use event UID if possible. Otherwise,
87
+ # construct something. Maybe I should mix something in to make it unique for
88
+ # each time a feed is generated for the calendar?
89
+ entry_id += 1
90
+ tag = ve.uid || "#{entry_id}@#{feeduri}"
91
+ e.id = "tag:vpim.rubyforge.org,2009:#{tag}"
92
+ end
93
+ end
94
+ end
95
+ return f
96
+ end
97
+ end # Atomize
98
+
99
+ end # Agent
100
+ end # Vpim
101
+
@@ -0,0 +1,173 @@
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 "cgi"
10
+ require "uri"
11
+
12
+ require "vpim/repo"
13
+ require "vpim/agent/atomize"
14
+
15
+ module Vpim
16
+ module Agent
17
+ # On failure, raise this with an error message. text/plain for now,
18
+ # text/html later. Will convert to a 404 and a message.
19
+ class NotFound < Exception
20
+ def initialize(name, path)
21
+ super %{Resource "#{name}" under "#{path.prefix}" was not found!}
22
+ end
23
+ end
24
+
25
+ class Path
26
+ def self.split_path(path)
27
+ begin
28
+ path = path.to_ary
29
+ rescue NameError
30
+ path = path.split("/")
31
+ end
32
+ path.map{|w| CGI.unescape(w)}
33
+ end
34
+
35
+ # URI is the uri being queried, base is where this path is mounted under?
36
+ def initialize(uri, base = "")
37
+ @uri = URI.parse(uri.to_s)
38
+ #pp [uri, base, @uri]
39
+ if @uri.path.size == 0
40
+ @uri.path = "/"
41
+ end
42
+ @path = Path.split_path(@uri.path)
43
+ @base = base.to_str
44
+ @mark = 0
45
+
46
+ @base.split.size.times{ shift }
47
+ end
48
+
49
+ def uri
50
+ @uri.to_s
51
+ end
52
+
53
+ def to_path
54
+ self
55
+ end
56
+
57
+ # TODO - call this #next
58
+ def shift
59
+ if @path[@mark]
60
+ @path[@mark += 1]
61
+ end
62
+ end
63
+
64
+ def append(name, scheme = nil)
65
+ uri = @uri.dup
66
+ uri.path += "/" + CGI.escape(name)
67
+ if scheme
68
+ uri.scheme = scheme
69
+ end
70
+ uri
71
+ end
72
+
73
+ def prefix(len = nil)
74
+ len ||= @mark
75
+ @path[0, len].map{|p| CGI.escape(p)}.join("/") + "/"
76
+ end
77
+
78
+ end
79
+
80
+ module Form
81
+ ATOM = Atomize::MIME
82
+ HTML = "text/html"
83
+ ICS = "text/calendar"
84
+ PLAIN = "text/plain"
85
+ VCF = "text/directory"
86
+ end
87
+
88
+ # Return an HTML description of a list of resources accessible under this
89
+ # path.
90
+ class ResourceList
91
+ def initialize(description, items)
92
+ @description = description
93
+ @items = items
94
+ end
95
+
96
+ def get(path)
97
+ return <<__, Form::HTML
98
+ <html><body>
99
+ #{@description}
100
+ <ul>
101
+ #{
102
+ @items.map do |name,description,scheme|
103
+ "<li><a href=\"#{path.append(name,scheme)}\">#{description || name}</a></li>\n"
104
+ end
105
+ }
106
+ </ul>
107
+ </body></html>
108
+ __
109
+ end
110
+ end
111
+
112
+ # Return calendar information based on RESTful (lovein' the jargon...)
113
+ # paths. Input is a Vpim::Repo.
114
+ #
115
+ # .../coding/month/atom
116
+ # .../coding/events/month/ics <- next month?
117
+ # .../coding/events/month/2008-04/ics <- a specified month?
118
+ # .../coding/week/atom
119
+ # .../year/atom
120
+ class Calendars
121
+ def initialize(repo)
122
+ @repo = repo
123
+ end
124
+
125
+ class Calendar
126
+ def initialize(cal)
127
+ @cal = cal
128
+ @list = ResourceList.new(
129
+ "Calendar #{@cal.name.inspect}:",
130
+ [
131
+ ["calendar", "download"],
132
+ ["calendar", "subscription", "webcal"],
133
+ ["atom", "syndication"],
134
+ ]
135
+ )
136
+ end
137
+
138
+ def get(path)
139
+ form = path.shift
140
+
141
+ # TODO should redirect to an object, so that extra paths can be
142
+ # handled more gracefully.
143
+ case form
144
+ when nil
145
+ return @list.get(path)
146
+ when "calendar"
147
+ return @cal.encode, Form::ICS
148
+ when "atom"
149
+ return Atomize.calendar(@cal, path.uri, nil, @cal.name).to_xml, Form::ATOM
150
+ else
151
+ raise NotFound.new(form, path)
152
+ end
153
+ end
154
+ end
155
+
156
+ # Get object at this path. Return value is a tuple of data and mime content type.
157
+ def get(path)
158
+ case name = path.to_path.shift
159
+ when nil
160
+ list = ResourceList.new("Calendars:", @repo.map{|c| c.name})
161
+ return list.get(path)
162
+ else
163
+ if cal = @repo.find{|c| c.name == name}
164
+ return Calendar.new(cal).get(path)
165
+ else
166
+ raise NotFound.new(name, path)
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
173
+