kali 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.
- data/README.md +42 -0
- data/examples/calendar.rb +48 -0
- data/lib/kali.rb +16 -0
- data/lib/kali/component.rb +128 -0
- data/lib/kali/component/calendar.rb +23 -0
- data/lib/kali/component/event.rb +33 -0
- data/lib/kali/key_value_pair.rb +62 -0
- data/lib/kali/parameter.rb +14 -0
- data/lib/kali/parameters.rb +146 -0
- data/lib/kali/properties.rb +207 -0
- data/lib/kali/property.rb +93 -0
- data/lib/kali/type.rb +118 -0
- data/lib/kali/type/boolean.rb +18 -0
- data/lib/kali/type/cal_address.rb +28 -0
- data/lib/kali/type/date.rb +16 -0
- data/lib/kali/type/date_time.rb +35 -0
- data/lib/kali/type/duration.rb +41 -0
- data/lib/kali/type/float.rb +14 -0
- data/lib/kali/type/geo.rb +21 -0
- data/lib/kali/type/integer.rb +14 -0
- data/lib/kali/type/list.rb +37 -0
- data/lib/kali/type/quoted.rb +18 -0
- data/lib/kali/type/text.rb +31 -0
- data/lib/kali/type/time.rb +17 -0
- data/lib/kali/type/uri.rb +16 -0
- data/lib/kali/typed_list.rb +52 -0
- data/lib/kali/utils.rb +6 -0
- data/lib/kali/utils/named.rb +17 -0
- data/lib/kali/utils/text.rb +42 -0
- data/lib/kali/version.rb +3 -0
- data/test/component/calendar_test.rb +20 -0
- data/test/component/event_test.rb +17 -0
- data/test/property/calendar_properties_test.rb +43 -0
- data/test/property/calendar_property_test.rb +45 -0
- data/test/property/component_properties_test.rb +31 -0
- data/test/test_helper.rb +2 -0
- data/test/type_test.rb +120 -0
- data/test/utils/text_test.rb +42 -0
- metadata +83 -0
@@ -0,0 +1,207 @@
|
|
1
|
+
module Kali
|
2
|
+
# Identifier for the product that created this iCalendar object.
|
3
|
+
#
|
4
|
+
# As per http://tools.ietf.org/html/rfc5545#section-3.7.3
|
5
|
+
class Property::ProductIdentifier < Property
|
6
|
+
name "PRODID"
|
7
|
+
type Type::Text.new
|
8
|
+
default "Kali/#{Kali::VERSION}"
|
9
|
+
end
|
10
|
+
|
11
|
+
# Version of the iCalendar protocol implemented in this iCalendar object.
|
12
|
+
#
|
13
|
+
# As per http://tools.ietf.org/html/rfc5545#section-3.7.4
|
14
|
+
class Property::Version < Property
|
15
|
+
name "VERSION"
|
16
|
+
type Type::Text.new("2.0")
|
17
|
+
default "2.0"
|
18
|
+
end
|
19
|
+
|
20
|
+
# Calendar scale used in this iCalendar object.
|
21
|
+
#
|
22
|
+
# As per http://tools.ietf.org/html/rfc5545#section-3.7.1
|
23
|
+
class Property::CalendarScale < Property
|
24
|
+
name "CALSCALE"
|
25
|
+
type Type::Text.new("GREGORIAN")
|
26
|
+
default "GREGORIAN"
|
27
|
+
end
|
28
|
+
|
29
|
+
# The iCalendar object method associated with this iCalendar object.
|
30
|
+
#
|
31
|
+
# As per http://tools.ietf.org/html/rfc5545#section-3.7.2
|
32
|
+
class Property::Method < Property
|
33
|
+
name "METHOD"
|
34
|
+
type Type::Text.new(
|
35
|
+
Enum["PUBLISH", "REQUEST", "REPLY", "ADD", "CANCEL", "REFRESH", "COUNTER",
|
36
|
+
"DECLINECOUNTER"]
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Globally unique identifier for this component.
|
41
|
+
#
|
42
|
+
# As per http://tools.ietf.org/html/rfc5545#section-3.8.4.7
|
43
|
+
class Property::UniqueIdentifier < Property
|
44
|
+
name "UID"
|
45
|
+
type Type::Text.new
|
46
|
+
end
|
47
|
+
|
48
|
+
# The "current version" of the component, according to the organizer's
|
49
|
+
# judgement.
|
50
|
+
#
|
51
|
+
# As per http://tools.ietf.org/html/rfc5545#section-3.8.7.4
|
52
|
+
class Property::SequenceNumber < Property
|
53
|
+
name "SEQUENCE"
|
54
|
+
type Type::Integer.new(->(i) { i >= 0 })
|
55
|
+
default 0
|
56
|
+
end
|
57
|
+
|
58
|
+
# Short summary of the current component.
|
59
|
+
#
|
60
|
+
# As per http://tools.ietf.org/html/rfc5545#section-3.8.1.12
|
61
|
+
class Property::Summary < Property
|
62
|
+
name "SUMMARY"
|
63
|
+
type Type::Text.new
|
64
|
+
end
|
65
|
+
|
66
|
+
# Longer description of the current component.
|
67
|
+
#
|
68
|
+
# As per http://tools.ietf.org/html/rfc5545#section-3.8.1.5
|
69
|
+
class Property::Description < Property
|
70
|
+
name "DESCRIPTION"
|
71
|
+
type Type::Text.new
|
72
|
+
end
|
73
|
+
|
74
|
+
# Lat/Lng pair indicating where this component takes place.
|
75
|
+
#
|
76
|
+
# As per http://tools.ietf.org/html/rfc5545#section-3.8.1.6
|
77
|
+
class Property::GeographicPosition < Property
|
78
|
+
name "GEO"
|
79
|
+
type Type::Geo.new
|
80
|
+
end
|
81
|
+
|
82
|
+
# Name/description of the venue where this component takes place.
|
83
|
+
#
|
84
|
+
# As per http://tools.ietf.org/html/rfc5545#section-3.8.1.7
|
85
|
+
class Property::Location < Property
|
86
|
+
name "LOCATION"
|
87
|
+
type Type::Text.new
|
88
|
+
end
|
89
|
+
|
90
|
+
# How important is this component compared to others. Can be 0 (no special
|
91
|
+
# priority), or range from 1 (high) to 9 (low).
|
92
|
+
#
|
93
|
+
# As per http://tools.ietf.org/html/rfc5545#section-3.8.1.9
|
94
|
+
class Property::Priority < Property
|
95
|
+
name "PRIORITY"
|
96
|
+
type Type::Integer.new(0..9)
|
97
|
+
default 0
|
98
|
+
end
|
99
|
+
|
100
|
+
# List of categories describing this component.
|
101
|
+
#
|
102
|
+
# As per http://tools.ietf.org/html/rfc5545#section-3.8.1.2
|
103
|
+
class Property::Categories < Property
|
104
|
+
name "CATEGORIES"
|
105
|
+
type Type::List.new(Type::Text.new)
|
106
|
+
end
|
107
|
+
|
108
|
+
# List of resources needed to be able to fulfill this component. For example
|
109
|
+
# ["PROJECTOR", "EASEL", "WHITEBOARD"].
|
110
|
+
#
|
111
|
+
# As per http://tools.ietf.org/html/rfc5545#section-3.8.1.10
|
112
|
+
class Property::Resources < Property
|
113
|
+
name "RESOURCES"
|
114
|
+
type Type::List.new(Type::Text.new)
|
115
|
+
end
|
116
|
+
|
117
|
+
# User provided comments about the current component. Can be specified
|
118
|
+
# multiple times for a single component.
|
119
|
+
#
|
120
|
+
# As per http://tools.ietf.org/html/rfc5545#section-3.8.1.4
|
121
|
+
class Property::Comment < Property
|
122
|
+
name "COMMENT"
|
123
|
+
type Type::Text.new
|
124
|
+
end
|
125
|
+
|
126
|
+
# Status representing whether the event is taking place or not.
|
127
|
+
#
|
128
|
+
# As per http://tools.ietf.org/html/rfc5545#section-3.8.1.11
|
129
|
+
class Property::EventStatus < Property
|
130
|
+
name "STATUS"
|
131
|
+
type Type::Text.new(Enum["TENTATIVE", "CONFIRMED", "CANCELLED"])
|
132
|
+
end
|
133
|
+
|
134
|
+
# End date of a component. This is required, unless there's a Duration, in
|
135
|
+
# which case this can't appear.
|
136
|
+
#
|
137
|
+
# TODO: This can be a DateTime or a Date. We should take that into
|
138
|
+
# consideration.
|
139
|
+
#
|
140
|
+
# As per http://tools.ietf.org/html/rfc5545#section-3.8.2.2
|
141
|
+
class Property::EndDateTime < Property
|
142
|
+
name "DTEND"
|
143
|
+
type Type::DateTime.new
|
144
|
+
end
|
145
|
+
|
146
|
+
# Start date of a component.
|
147
|
+
#
|
148
|
+
# TODO: This can be a DateTime or a Date. We should take that into
|
149
|
+
# consideration.
|
150
|
+
#
|
151
|
+
# As per http://tools.ietf.org/html/rfc5545#section-3.8.2.4
|
152
|
+
class Property::StartDateTime < Property
|
153
|
+
name "DTSTART"
|
154
|
+
type Type::DateTime.new
|
155
|
+
end
|
156
|
+
|
157
|
+
# Duration of the component.
|
158
|
+
#
|
159
|
+
# As per http://tools.ietf.org/html/rfc5545#section-3.8.2.5
|
160
|
+
class Property::Duration < Property
|
161
|
+
name "DURATION"
|
162
|
+
type Type::Duration.new
|
163
|
+
end
|
164
|
+
|
165
|
+
# Does this event affect your free/busy schedule? Transparent events are the
|
166
|
+
# ones that don't, while opaque events do.
|
167
|
+
#
|
168
|
+
# As per http://tools.ietf.org/html/rfc5545#section-3.8.2.7
|
169
|
+
class Property::TimeTransparency < Property
|
170
|
+
name "TRANSP"
|
171
|
+
type Type::Text.new(Enum["OPAQUE", "TRANSPARENT"])
|
172
|
+
default "OPAQUE"
|
173
|
+
end
|
174
|
+
|
175
|
+
# Represent a person involved with this event.
|
176
|
+
#
|
177
|
+
# As per http://tools.ietf.org/html/rfc5545#section-3.8.4.1
|
178
|
+
class Property::Attendee < Property
|
179
|
+
name "ATTENDEE"
|
180
|
+
type Type::CalAddress.new
|
181
|
+
end
|
182
|
+
|
183
|
+
# Represent the party organizing the event.
|
184
|
+
#
|
185
|
+
# As per http://tools.ietf.org/html/rfc5545#section-3.8.4.3
|
186
|
+
class Property::Organizer < Property
|
187
|
+
name "ORGANIZER"
|
188
|
+
type Type::CalAddress.new
|
189
|
+
end
|
190
|
+
|
191
|
+
# This component is used to represent contact information for someone
|
192
|
+
# associated with the Event's venue.
|
193
|
+
#
|
194
|
+
# As per http://tools.ietf.org/html/rfc5545#section-3.8.4.3
|
195
|
+
class Property::Contact < Property
|
196
|
+
name "CONTACT"
|
197
|
+
type Type::Text.new
|
198
|
+
end
|
199
|
+
|
200
|
+
# External URL associated with this calendar component.
|
201
|
+
#
|
202
|
+
# As per http://tools.ietf.org/html/rfc5545#section-3.8.4.6
|
203
|
+
class Property::URL < Property
|
204
|
+
name "URL"
|
205
|
+
type Type::URI.new
|
206
|
+
end
|
207
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module Kali
|
2
|
+
# Public: Properties are the definition of an attribute describing a
|
3
|
+
# component.
|
4
|
+
class Property < KeyValuePair
|
5
|
+
# Public: Add a new parameter to this Property.
|
6
|
+
#
|
7
|
+
# type - A subclass of Kali::Parameter.
|
8
|
+
#
|
9
|
+
# Returns nothing.
|
10
|
+
def self.parameter(type, method = type.method_name)
|
11
|
+
default_parameter_types[type.name] = type
|
12
|
+
|
13
|
+
define_method method do
|
14
|
+
parameters[type.name] ||= type.new
|
15
|
+
end
|
16
|
+
|
17
|
+
define_method "#{method}=" do |value|
|
18
|
+
parameters[type.name] = type.wrap(value)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Public: Register the type of this property, and all the parameters
|
23
|
+
# associated with that type, or get the current type of the object.
|
24
|
+
#
|
25
|
+
# See `KeyValuePair.type`.
|
26
|
+
def self.type(type = nil)
|
27
|
+
if type
|
28
|
+
type.parameters.each do |param, method|
|
29
|
+
parameter(param, method)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
super
|
33
|
+
end
|
34
|
+
|
35
|
+
# Internal: List of parameter types added to the class explicitly. Any
|
36
|
+
# parameter added that is not in this store will be assumed to be a simple
|
37
|
+
# Type::Text parameter. See `Property#[]=`.
|
38
|
+
def self.default_parameter_types
|
39
|
+
@parameter_types ||= {}
|
40
|
+
end
|
41
|
+
|
42
|
+
def initialize(*) # :nodoc:
|
43
|
+
super
|
44
|
+
end
|
45
|
+
|
46
|
+
# Internal: Storage of parameters associated with this instance of the
|
47
|
+
# property.
|
48
|
+
#
|
49
|
+
# Returns a Hash.
|
50
|
+
def parameters
|
51
|
+
@parameters ||= {}
|
52
|
+
end
|
53
|
+
|
54
|
+
# Public: Get a parameter by iCalendar name.
|
55
|
+
#
|
56
|
+
# name - The name of the parameter.
|
57
|
+
#
|
58
|
+
# Returns the passed in value.
|
59
|
+
def [](name)
|
60
|
+
parameters[name]
|
61
|
+
end
|
62
|
+
|
63
|
+
# Public: Set a parameter manually by explicitly passing the iCalendar name
|
64
|
+
# (so, for example, "DELEGATED-TO" instead of calling `#delegated_to=`).
|
65
|
+
#
|
66
|
+
# name - A String with the name of the parameter. If there's a typed
|
67
|
+
# parameter for this name already, this will be equivalent to
|
68
|
+
# setting that parameter via the setter. Otherwise it will assume
|
69
|
+
# this is a Text parameter and set it. Useful for setting
|
70
|
+
# experimental ("X-*") params, such as "X-GUEST-COUNT".
|
71
|
+
# value - The value of the param.
|
72
|
+
#
|
73
|
+
# Returns the passed in value.
|
74
|
+
def []=(name, value)
|
75
|
+
param = if type = self.class.default_parameter_types.fetch(name, false)
|
76
|
+
type.wrap(value)
|
77
|
+
else
|
78
|
+
Parameter::Default.new(name, value)
|
79
|
+
end
|
80
|
+
|
81
|
+
parameters[name] = param
|
82
|
+
end
|
83
|
+
|
84
|
+
# Public: Generate an iCalendar representation of this property.
|
85
|
+
#
|
86
|
+
# Returns a String.
|
87
|
+
def to_ics
|
88
|
+
params = parameters.map { |_, value| value.to_ics }.join("")
|
89
|
+
encoded_value = self.class.type.encode(value)
|
90
|
+
Utils::Text.fold_line "#{self.class.name}#{params}:#{encoded_value}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
data/lib/kali/type.rb
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
require "set"
|
2
|
+
|
3
|
+
module Kali
|
4
|
+
# Public: Type classes are used to convert ruby objects from and to their
|
5
|
+
# iCalendar representations.
|
6
|
+
#
|
7
|
+
# Examples:
|
8
|
+
# type = Kali::Type::Date.new
|
9
|
+
# type.encode(Date.today) #=> "20130712"
|
10
|
+
# type.decode("20130712") #=> Date.new(2013, 7, 12)
|
11
|
+
#
|
12
|
+
# type = Kali::Type::Integer.new(0..9)
|
13
|
+
# type.encode(3) #=> "3"
|
14
|
+
# type.encode(15) #=> ArgumentError
|
15
|
+
# type.decode("7") #=> 7
|
16
|
+
#
|
17
|
+
# type = Kali::Type::Text.new(->(s) { s.size > 3 })
|
18
|
+
# type.encode("x") #=> ArgumentError
|
19
|
+
# type.encode("long") #=> "long"
|
20
|
+
#
|
21
|
+
# type = Kali::Type::Text.new(Enum["ACTIVE", "INACTIVE"])
|
22
|
+
# type.encode("ACTIVE") #=> "ACTIVE"
|
23
|
+
# type.encode("nope") #=> ArgumentError
|
24
|
+
class Type
|
25
|
+
# Public: Initialize the Type.
|
26
|
+
#
|
27
|
+
# restriction - An object that implenents the #=== operator. When we
|
28
|
+
# validate that a certain value is valid for this type, we
|
29
|
+
# will check the value against `restriction.===`. If it's
|
30
|
+
# `nil` then we don't restrict the value.
|
31
|
+
def initialize(restriction = nil)
|
32
|
+
@restriction = restriction
|
33
|
+
end
|
34
|
+
|
35
|
+
# Public: Parameters added by deafult to properties that are of this type.
|
36
|
+
#
|
37
|
+
# Returns an Array where each entry is a Hash keyed by a subclass of
|
38
|
+
# Kali::Parameter, and the value is a Symbol with the name of the method for
|
39
|
+
# that property.
|
40
|
+
def parameters
|
41
|
+
[]
|
42
|
+
end
|
43
|
+
|
44
|
+
# Public: Encode an object into an iCalendar-compatible String using the
|
45
|
+
# format specific to this Type.
|
46
|
+
#
|
47
|
+
# Requires a subclass to implement #encode!
|
48
|
+
#
|
49
|
+
# obj - An Object that matches this Type.
|
50
|
+
#
|
51
|
+
# Returns a String that matches the iCalendar representation of this Type.
|
52
|
+
def encode(obj)
|
53
|
+
encode!(validate(obj))
|
54
|
+
end
|
55
|
+
|
56
|
+
# Public: Decode an iCalendar-compatible String into an Object that matches
|
57
|
+
# this Type.
|
58
|
+
#
|
59
|
+
# Requires a subclass to implement #decode!
|
60
|
+
#
|
61
|
+
# obj - A String that matches the iCalendar representation for this Type..
|
62
|
+
#
|
63
|
+
# Returns an Object that matches this Type.
|
64
|
+
def decode(obj)
|
65
|
+
validate(decode!(obj))
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
# Internal: Check that an Object meets the restrictions of this particular
|
71
|
+
# Type.
|
72
|
+
#
|
73
|
+
# value - An Object.
|
74
|
+
#
|
75
|
+
# Returns the value if it satisfies the restriction.
|
76
|
+
# Raises ArgumentError if the restriction is not satisfied.
|
77
|
+
def validate(value)
|
78
|
+
if @restriction.nil? || @restriction === value
|
79
|
+
value
|
80
|
+
else
|
81
|
+
raise ArgumentError, "#{value.inspect} does not satisfy the type restriction for #{self.class} (#{@restriction.inspect})"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
autoload :Text, "kali/type/text"
|
86
|
+
autoload :Integer, "kali/type/integer"
|
87
|
+
autoload :Float, "kali/type/float"
|
88
|
+
autoload :Geo, "kali/type/geo"
|
89
|
+
autoload :List, "kali/type/list"
|
90
|
+
autoload :DateTime, "kali/type/date_time"
|
91
|
+
autoload :Date, "kali/type/date"
|
92
|
+
autoload :Time, "kali/type/time"
|
93
|
+
autoload :Duration, "kali/type/duration"
|
94
|
+
autoload :URI, "kali/type/uri"
|
95
|
+
autoload :CalAddress, "kali/type/cal_address"
|
96
|
+
autoload :Quoted, "kali/type/quoted"
|
97
|
+
autoload :Boolean, "kali/type/boolean"
|
98
|
+
end
|
99
|
+
|
100
|
+
# Public: Helper class that allows us to easily restrict a value from a set of
|
101
|
+
# predetermined values. It's just a Set that implements the case equality
|
102
|
+
# operator to check for Set inclusion.
|
103
|
+
#
|
104
|
+
# This is meant to be used as a Type restriction. See Type for examples.
|
105
|
+
#
|
106
|
+
# Examples:
|
107
|
+
# Enum[1, 2, 3] === 1 #=> true
|
108
|
+
# Enum[1, 2, 3] === 5 #=> false
|
109
|
+
class Enum < Set
|
110
|
+
def self.[](*values)
|
111
|
+
new(values).freeze
|
112
|
+
end
|
113
|
+
|
114
|
+
def ===(element)
|
115
|
+
include?(element)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Kali
|
2
|
+
class Type::Boolean < Type
|
3
|
+
def initialize(requirement = nil)
|
4
|
+
super(->(o) { (o == true || o == false) && (requirement.nil? || requirement === o) })
|
5
|
+
end
|
6
|
+
|
7
|
+
def encode!(flag)
|
8
|
+
flag ? "TRUE" : "FALSE"
|
9
|
+
end
|
10
|
+
|
11
|
+
def decode!(string)
|
12
|
+
case string
|
13
|
+
when "TRUE"; true
|
14
|
+
when "FALSE"; false
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require "uri"
|
2
|
+
|
3
|
+
module Kali
|
4
|
+
# The CAL-ADDRESS type represents a person involved with an event. It must be
|
5
|
+
# an email URI.
|
6
|
+
#
|
7
|
+
# See http://tools.ietf.org/html/rfc5545#section-3.3.3
|
8
|
+
class Type::CalAddress < Type::URI
|
9
|
+
def initialize
|
10
|
+
super(::URI::MailTo)
|
11
|
+
end
|
12
|
+
|
13
|
+
def parameters
|
14
|
+
{
|
15
|
+
Parameter::CommonName => :cn,
|
16
|
+
Parameter::CalendarUserType => :cutype,
|
17
|
+
Parameter::Delegators => :delegators,
|
18
|
+
Parameter::Delegatees => :delegatees,
|
19
|
+
Parameter::DirectoryEntry => :dir,
|
20
|
+
Parameter::GroupMemberships => :members,
|
21
|
+
Parameter::EventParticipationStatus => :partstat,
|
22
|
+
Parameter::ParticipationRole => :role,
|
23
|
+
Parameter::RSVPExpectation => :rsvp,
|
24
|
+
Parameter::SentBy => :sent_by
|
25
|
+
}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|