Skip to content

Commit

Permalink
Prepared for limited release
Browse files Browse the repository at this point in the history
Lots of Doc

Refactored validations out of recurrence rule
  • Loading branch information
rubyredrick committed Feb 26, 2009
1 parent fd5e548 commit 0d986be
Show file tree
Hide file tree
Showing 59 changed files with 1,227 additions and 339 deletions.
109 changes: 107 additions & 2 deletions README.txt
Expand Up @@ -18,15 +18,120 @@ This is a clean-slate implementation of RFC2445.

== SYNOPSIS:

FIXME (code sample of usage)
=== Parsing

RiCal can parse icalendar data from either a string or a Ruby io object.

The data may consist of one or more icalendar calendars, or one or more icalendar compoentns (e.g. one or more
VEVENT, or VTODO objects.)

In either case the result will be an array of components.
==== From a string
RiCal.parse_string <<ENDCAL
BEGIN:VCALENDAR
X-WR-TIMEZONE:America/New_York
PRODID:-//Apple Inc.//iCal 3.0//EN
CALSCALE:GREGORIAN
X-WR-CALNAME:test
VERSION:2.0
X-WR-RELCALID:1884C7F8-BC8E-457F-94AC-297871967D5E
X-APPLE-CALENDAR-COLOR:#2CA10B
BEGIN:VTIMEZONE
TZID:US/Eastern
BEGIN:DAYLIGHT
TZOFFSETFROM:-0500
TZOFFSETTO:-0400
DTSTART:20070311T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
TZNAME:EDT
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:-0400
TZOFFSETTO:-0500
DTSTART:20071104T020000
RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
TZNAME:EST
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
SEQUENCE:5
TRANSP:OPAQUE
UID:00481E53-9258-4EA7-9F8D-947D3041A3F2
DTSTART;TZID=US/Eastern:20090224T090000
DTSTAMP:20090225T000908Z
SUMMARY:Test Event
CREATED:20090225T000839Z
DTEND;TZID=US/Eastern:20090224T100000
RRULE:FREQ=DAILY;INTERVAL=1;UNTIL=20090228T045959Z
END:VEVENT
END:VCALENDAR
ENDCAL

*bold*:: Beware of the initial whitespace in the above example which is for rdoc formatting. The parser does not strip initial whitespace from lines in the file and will fail.

As already stated the string argument may be a full icalendar format calendar, or just one or more subcomponents, e.g.

RiCal.parse("BEGIN:VEVENT\nDTSTART;TZID=US/Eastern:20090224T090000\nSUMMARY:Test Event\nDTEND;TZID=US/Eastern:20090224T100000\nRRULE:FREQ=DAILY;INTERVAL=1;UNTIL=20090228T045959Z\nEND:VEVENT")

==== From an Io
File.open("path/to/file", "r") do |file|
components = RiCal.parse(file)
end

=== Occurrence Enumeration

Event, Journal, and Todo components can have recurrences which are defined following the RFC 2445 specification.
A component with recurrences can enumerate those occurrences.

These components have common methods for enumeration which are defined in the RiCal::OccurrenceEnumerator module.

==== Obtaining an array of occurrences

To get an array of occurrences, Use the RiCal::OccurrenceEnumerator#occurrences method:

event.occurrences

This method may fail with an argument error, if the component has an unbounded recurrence definition. This happens
when one or more of its RRULES don't have a COUNT, or UNTIL part. This may be tested by using the RiCal::OccurrenceEnumerator#bounded? method.

In the case of unbounded components, you must either use the :count, or :before options of the RiCal::OccurrenceEnumerator#occurrences method:

event.occurrences(:count => 10)

or

event.occurrences(:before => Date.today >> 1)

Alternately, you can use the RiCal::OccurrenceEnumerator#each method,
or another Enumerable method (RiCal::OccurrenceEnumerator includes Enumerable), and terminate when you wish by breaking out of the block.

event.each do |event|
break if some_termination_condition
#....
end

== REQUIREMENTS:

* FIXME (list of requirements)

== INSTALL:

* FIXME (sudo gem install, anything else)
This project was built using the bones gem.

For this preview release, I'm assuming that you received the code from my private git repository.

You can validate it by running
rake
which will run all of the specs.

If you would like to use it as a gem you can use the bones rake tasks:

rake gem
rake gem:install

The command
rake -T
will show additional tasks.

== LICENSE:

Expand Down
23 changes: 10 additions & 13 deletions lib/ri_cal.rb
@@ -1,8 +1,10 @@
module RiCal

autoload :Component, 'lib/ri_cal/component.rb'
autoload :TimezonePeriod, 'lib/ri_cal/timezone_period.rb'
autoload :OccurrenceEnumerator, 'lib/ri_cal/occurrence_enumerator.rb'
my_dir = File.dirname(__FILE__)

autoload :Component, "#{my_dir}/ri_cal/component.rb"
autoload :TimezonePeriod, "#{my_dir}/ri_cal/properties/timezone_period.rb"
autoload :OccurrenceEnumerator, "#{my_dir}/ri_cal/occurrence_enumerator.rb"


# :stopdoc:
Expand Down Expand Up @@ -38,15 +40,10 @@ def self.path( *args )
# the _filename_ does not have to be equivalent to the directory.
#
def self.require_all_libs_relative_to( fname, dir = nil )
# propdir ::File.basename(fname, 'ri_cal', "properties")
# search_me = ::File.expand_path(
# ::File.join(::File.dirname(fname), propdir, '*.rb'))
# Dir.glob(search_me).sort.each {|rb| require rb}
dir ||= ::File.basename(fname, '.*')
search_me = ::File.expand_path(
::File.join(::File.dirname(fname), dir, '**', '*.rb'))
Dir.glob(search_me).grep(%r{/properties/}).each {|rb| require rb}
Dir.glob(search_me).sort.each {|rb| require rb}
search_me = ::File.expand_path(::File.join(::File.dirname(fname), dir, '**', '*.rb'))
Dir.glob(search_me).sort.each {|rb|
require rb}
end

# :startdoc:
Expand All @@ -66,8 +63,8 @@ def self.parse_string(string)

end # module RiCal

require File.join(File.dirname(__FILE__), *%w[ri_cal property_value])
require File.join(File.dirname(__FILE__), *%w[ri_cal component])
# require File.join(File.dirname(__FILE__), *%w[ri_cal property_value])
# require File.join(File.dirname(__FILE__), *%w[ri_cal component])
RiCal.require_all_libs_relative_to(__FILE__)

# EOF
34 changes: 20 additions & 14 deletions lib/ri_cal/component.rb
@@ -1,13 +1,13 @@
module RiCal
class Component

autoload :Timezone, 'lib/ri_cal/component/timezone.rb'
autoload :Timezone, "#{File.dirname(__FILE__)}/component/timezone.rb"

def initialize(parent)
@parent = parent
end

def self.from_parser(parser, parent)
def self.from_parser(parser, parent) # :nodoc:
entity = self.new(parent)
line = parser.next_separated_line
while parser.still_in(entity_name, line)
Expand All @@ -17,15 +17,15 @@ def self.from_parser(parser, parent)
entity
end

def self.parse(io)
def self.parse(io) # :nodoc:
Parser.new(io).parse
end

def self.parse_string(string)
def self.parse_string(string) # :nodoc:
parse(StringIO.new(string))
end

def subcomponents
def subcomponents # :nodoc:
@subcomponents ||= Hash.new {|h, k| h[k] = []}
end

Expand All @@ -35,11 +35,11 @@ def alarms
subcomponents["VALARM"]
end

def add_subcomponent(parser, line)
def add_subcomponent(parser, line) # :nodoc:
subcomponents[line[:value]] << parser.parse_one(line, self)
end

def process_line(parser, line)
def process_line(parser, line) # :nodoc:
if line[:name] == "BEGIN"
add_subcomponent(parser, line)
else
Expand All @@ -52,31 +52,37 @@ def process_line(parser, line)
end
end



# return a hash of any extended properties, (i.e. those with a property name starting with "X-"
# representing an extension to the RFC 2445 specification)
def x_properties
@x_properties ||= {}
end

def add_x_property(prop, name)
def add_x_property(prop, name) # :nodoc:
x_properties[name] = prop
end

# Predicate to determine if the component is valid according to RFC 2445
def valid?
!mutual_exclusion_violation
end

def recurrence(occurrence)
result = self.copy
def initialize_copy(original) # :nodoc:
end

def initialize_copy(original)
def prop_string(prop_name, *properties) # :nodoc:
properties = properties.flatten.compact
if properties && !properties.empty?
properties.map {|prop| "#{prop_name}#{prop.to_s}"}.join("\n")
else
nil
end
end

end
end

Dir[File.dirname(__FILE__) + "/component/*.rb"].sort.each do |path|
filename = File.basename(path)
require "lib/ri_cal/component/#{filename}"
require path
end
2 changes: 2 additions & 0 deletions lib/ri_cal/component/alarm.rb
Expand Up @@ -4,6 +4,8 @@ module RiCal
# An Alarm component groups properties defining a reminder or alarm associated with an event or to-do
# TODO: The Alarm component has complex cardinality restrictions depending on the value of the action property
# i.e. audio, display, email, and proc alarms, this is currently not checked or enforced
#
# to see the property accessing methods for this class see the RiCal::Properties::Alarm module
class Component
class Alarm < Component
include RiCal::Properties::Alarm
Expand Down
2 changes: 2 additions & 0 deletions lib/ri_cal/component/calendar.rb
Expand Up @@ -2,6 +2,8 @@

module RiCal
class Component
#
# to see the property accessing methods for this class see the RiCal::Properties::Calendar module
class Calendar < Component
include RiCal::Properties::Calendar

Expand Down
4 changes: 3 additions & 1 deletion lib/ri_cal/component/event.rb
Expand Up @@ -6,7 +6,9 @@ class Component
# Events may have multiple occurrences
#
# Events may also contain one or more ALARM subcomponents
# TODO: implement alarm subcomponents
#
# to see the property accessing methods for this class see the RiCal::Properties::Event module
# to see the methods for enumerating occurrences of recurring events see the RiCal::OccurrenceEnumerator module
class Event < Component
include OccurrenceEnumerator

Expand Down
1 change: 1 addition & 0 deletions lib/ri_cal/component/freebusy.rb
Expand Up @@ -4,6 +4,7 @@ module RiCal
class Component
# A Freebusy (VFREEBUSY) calendar component groups properties describing either a request for free/busy time,
# a response to a request for free/busy time, or a published set of busy time.
# to see the property accessing methods for this class see the RiCal::Properties::Freebusy module
class Freebusy < Component
include RiCal::Properties::Freebusy

Expand Down
3 changes: 3 additions & 0 deletions lib/ri_cal/component/journal.rb
Expand Up @@ -4,8 +4,11 @@ module RiCal
class Component
# A Journal (VJOURNAL) calendar component groups properties describing a journal entry.
# Journals may have multiple occurrences
# to see the property accessing methods for this class see the RiCal::Properties::Journal module
# to see the methods for enumerating occurrences of recurring journals see the RiCal::OccurrenceEnumerator module
class Journal < Component
include RiCal::Properties::Journal
include RiCal::OccurrenceEnumerator

def self.entity_name #:nodoc:
"VJOURNAL"
Expand Down
7 changes: 3 additions & 4 deletions lib/ri_cal/component/t_z_info_timezone.rb
@@ -1,6 +1,5 @@
# A wrapper class for a Timezone implemented by the TZInfo Gem
# (or Rails)

# (or by Rails)
class RiCal::Component::TZInfoTimezone < RiCal::Component::Timezone
attr_reader :tzinfo_timezone

Expand Down Expand Up @@ -29,7 +28,7 @@ def to_rfc2445_string(utc_start, utc_end)
result.join("\n")
end

def add_period(result, this_period, prev_period)
def add_period(result, this_period, prev_period) # :nodoc:
if this_period.dst?
which = 'DAYLIGHT'
offset_from = this_period.utc_offset
Expand All @@ -47,7 +46,7 @@ def add_period(result, this_period, prev_period)
result << "END:#{which}"
end

def format_rfc2445_offset(seconds)
def format_rfc2445_offset(seconds) # :nodoc:
abs_seconds = seconds.abs
h = (abs_seconds/3600).floor
m = (abs_seconds - (h * 3600))/60
Expand Down
4 changes: 2 additions & 2 deletions lib/ri_cal/component/timezone.rb
Expand Up @@ -10,6 +10,6 @@ def self.entity_name #:nodoc:
end


%w[timezone_period.rb daylight_period.rb standard_period.rb].each do |filename|
require "lib/ri_cal/component/timezone/#{filename}"
%w[timezone_period.rb daylight_period.rb standard_period.rb].each do |filename|
require "#{File.dirname(__FILE__)}/timezone/#{filename}"
end
2 changes: 2 additions & 0 deletions lib/ri_cal/component/todo.rb
Expand Up @@ -6,6 +6,8 @@ class Component
# Todos may have multiple occurrences
#
# Todos may also contain one or more ALARM subcomponents
# to see the property accessing methods for this class see the RiCal::Properties::Todo module
# to see the methods for enumerating occurrences of recurring to-dos see the RiCal::OccurrenceEnumerator module
class Todo < Component
include Properties::Todo

Expand Down
3 changes: 1 addition & 2 deletions lib/ri_cal/core_extensions.rb
Expand Up @@ -3,6 +3,5 @@
# end
# end
Dir[File.dirname(__FILE__) + "/core_extensions/*.rb"].sort.each do |path|
filename = File.basename(path)
require "lib/ri_cal/core_extensions/#{filename}"
require path
end
2 changes: 1 addition & 1 deletion lib/ri_cal/core_extensions/array.rb
@@ -1,4 +1,4 @@
require 'lib/ri_cal/core_extensions/array/conversions'
require "#{File.dirname(__FILE__)}/array/conversions.rb"
class Array
include RiCal::CoreExtensions::Array::Conversions
end
3 changes: 2 additions & 1 deletion lib/ri_cal/core_extensions/array/conversions.rb
Expand Up @@ -2,7 +2,8 @@ module RiCal
module CoreExtensions
module Array
module Conversions
def to_rfc2445_string
# return the concatenation of the elements representation in rfc 2445 format
def to_rfc2445_string # :doc:
join(",")
end
end
Expand Down
6 changes: 3 additions & 3 deletions lib/ri_cal/core_extensions/date.rb
@@ -1,6 +1,6 @@
require 'lib/ri_cal/core_extensions/date/conversions'
require 'lib/ri_cal/core_extensions/time/week_day_predicates'
require 'lib/ri_cal/core_extensions/time/calculations'
require "#{File.dirname(__FILE__)}/date/conversions.rb"
require "#{File.dirname(__FILE__)}/time/week_day_predicates.rb"
require "#{File.dirname(__FILE__)}/time/calculations.rb"
require 'date'
class Date
include RiCal::CoreExtensions::Time::WeekDayPredicates
Expand Down
1 change: 1 addition & 0 deletions lib/ri_cal/core_extensions/date/conversions.rb
Expand Up @@ -2,6 +2,7 @@ module RiCal
module CoreExtensions
module Date
module Conversions
# Return an RiCal::PropertyValue::DateTime representing the receiver
def to_ri_cal_date_time_value
RiCal::PropertyValue::Date.from_date(self)
end
Expand Down

0 comments on commit 0d986be

Please sign in to comment.