kali 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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,16 @@
|
|
1
|
+
require "date"
|
2
|
+
|
3
|
+
module Kali
|
4
|
+
# Used to represent calendar dates.
|
5
|
+
#
|
6
|
+
# See http://tools.ietf.org/html/rfc5545#section-3.3.4
|
7
|
+
class Type::Date < Type::DateTime
|
8
|
+
def encode!(*)
|
9
|
+
super.split("T").first
|
10
|
+
end
|
11
|
+
|
12
|
+
def decode!(string)
|
13
|
+
::Date.parse(string)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require "date"
|
2
|
+
|
3
|
+
module Kali
|
4
|
+
# Used to represent a precise calendar date and time of day.
|
5
|
+
#
|
6
|
+
# See http://tools.ietf.org/html/rfc5545#section-3.3.5
|
7
|
+
class Type::DateTime < Type
|
8
|
+
def parameters
|
9
|
+
{ Parameter::TimeZoneIdentifier => :tzid }
|
10
|
+
end
|
11
|
+
|
12
|
+
def encode!(object)
|
13
|
+
date_time = object.to_datetime
|
14
|
+
date_time.strftime("%Y%m%dT%H%M%S#{detect_timezone(date_time)}")
|
15
|
+
end
|
16
|
+
|
17
|
+
def decode!(string)
|
18
|
+
::DateTime.parse(string)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def detect_timezone(date_time)
|
24
|
+
offset = date_time.to_datetime.zone
|
25
|
+
sign, hour, minute = offset.scan(/(\+|-)(\d{2}):(\d{2})/).flatten
|
26
|
+
offset = Integer("#{sign}1") * (60 * Integer(minute) + 3600 * Integer(hour))
|
27
|
+
|
28
|
+
if offset.zero?
|
29
|
+
"Z"
|
30
|
+
else
|
31
|
+
""
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Kali
|
2
|
+
# Used to represent a duration of time, which can be nominal (days or weeks)
|
3
|
+
# or accurate (hours, days, minutes, or seconds).
|
4
|
+
#
|
5
|
+
# See http://tools.ietf.org/html/rfc5545#section-3.3.6
|
6
|
+
class Type::Duration < Type
|
7
|
+
LABELS = { week: "W", day: "D", hour: "H", minute: "M", second: "S" }
|
8
|
+
|
9
|
+
def encode!(obj)
|
10
|
+
negative = obj.delete(:negative) { false }
|
11
|
+
duration = obj.sort_by { |label, _| LABELS.key(label) }
|
12
|
+
|
13
|
+
nominal = true
|
14
|
+
duration = obj.each_with_object("") do |(label, amount), acc|
|
15
|
+
suffix = LABELS.fetch(label) do
|
16
|
+
raise ArgumentError, "#{label.inspect} is not a valid duration period. It must be one of #{labels.keys.inspect}"
|
17
|
+
end
|
18
|
+
|
19
|
+
acc << "T" if nominal && !(nominal = nominal?(label))
|
20
|
+
acc << "#{amount}#{suffix}"
|
21
|
+
end
|
22
|
+
|
23
|
+
"#{"-" if negative}P#{duration}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def decode!(string)
|
27
|
+
duration = {}
|
28
|
+
duration[:negative] = true if string.start_with?("-")
|
29
|
+
matches = string.gsub(/\-\+PT/, "").scan(/((\d+)([WDHMS]))/)
|
30
|
+
matches.each.with_object(duration) do |(_, n, suffix), duration|
|
31
|
+
duration[LABELS.key(suffix)] = Integer(n)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def nominal?(key)
|
38
|
+
[:week, :day].include?(key)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Kali
|
2
|
+
# Used to represent floating point numbers.
|
3
|
+
#
|
4
|
+
# See http://tools.ietf.org/html/rfc5545#section-3.3.7
|
5
|
+
class Type::Float < Type
|
6
|
+
def encode!(object)
|
7
|
+
Float(object).to_s
|
8
|
+
end
|
9
|
+
|
10
|
+
def decode!(string)
|
11
|
+
Float(string)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Kali
|
2
|
+
# Meta type (not defined in the RFC) for parsing and serializing
|
3
|
+
# GeographicPosition properties.
|
4
|
+
class Type::Geo < Type
|
5
|
+
def initialize(rule = nil)
|
6
|
+
super(->(o) { Array === o && o.size == 2 && (rule.nil? || rule === o) })
|
7
|
+
@latitude = Type::Float.new(-90..90)
|
8
|
+
@longitude = Type::Float.new(-180..180)
|
9
|
+
end
|
10
|
+
|
11
|
+
def encode!(object)
|
12
|
+
[@latitude.encode(object.at(0)),
|
13
|
+
@longitude.encode(object.at(1))].join(";")
|
14
|
+
end
|
15
|
+
|
16
|
+
def decode!(string)
|
17
|
+
lat, lng = String(string).split(";")
|
18
|
+
[@latitude.decode(lat), @longitude.decode(lng)]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Kali
|
2
|
+
# Used to represent integer numbers.
|
3
|
+
#
|
4
|
+
# See http://tools.ietf.org/html/rfc5545#section-3.3.8
|
5
|
+
class Type::Integer < Type
|
6
|
+
def encode!(object)
|
7
|
+
Integer(object).to_s
|
8
|
+
end
|
9
|
+
|
10
|
+
def decode!(string)
|
11
|
+
Integer(string)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Kali
|
2
|
+
# Meta type (not explicitly defined in the RFC as a property type) to parse
|
3
|
+
# and serialize lists of other values. Lists are strongly typed, with elements
|
4
|
+
# of only one type occurring inside a list.
|
5
|
+
#
|
6
|
+
# See http://tools.ietf.org/html/rfc5545#section-3.1.1
|
7
|
+
class Type::List < Type
|
8
|
+
def initialize(subtype, restriction = nil)
|
9
|
+
super(restriction)
|
10
|
+
@subtype = subtype
|
11
|
+
end
|
12
|
+
|
13
|
+
def encode!(object)
|
14
|
+
object.map { |el| @subtype.encode(el) }.join(",")
|
15
|
+
end
|
16
|
+
|
17
|
+
def decode!(string)
|
18
|
+
tentative_parts = string.split(",")
|
19
|
+
parts = []
|
20
|
+
in_group = false
|
21
|
+
group_size = 1
|
22
|
+
tentative_parts.each.with_index do |part, index|
|
23
|
+
if part[-1] == "\\"
|
24
|
+
group_size += 1
|
25
|
+
in_group = true
|
26
|
+
elsif in_group
|
27
|
+
parts << tentative_parts[index - group_size + 1, group_size].join(",")
|
28
|
+
group_size = 1
|
29
|
+
in_group = false
|
30
|
+
else
|
31
|
+
parts << part
|
32
|
+
end
|
33
|
+
end
|
34
|
+
parts.map { |part| @subtype.decode(part) }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Kali
|
2
|
+
# Meta-type that just wraps a different type in double quote characters when
|
3
|
+
# encoding it, or takes out the quotes when decoding it.
|
4
|
+
class Type::Quoted < Type
|
5
|
+
def initialize(type, restriction = nil)
|
6
|
+
super(restriction)
|
7
|
+
@type = type
|
8
|
+
end
|
9
|
+
|
10
|
+
def encode!(object)
|
11
|
+
%Q("#{@type.encode(object)}")
|
12
|
+
end
|
13
|
+
|
14
|
+
def decode!(string)
|
15
|
+
@type.decode(string.gsub(/^"|"$/, ""))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Kali
|
2
|
+
# Used to represent strings of text.
|
3
|
+
#
|
4
|
+
# See http://tools.ietf.org/html/rfc5545#section-3.3.11
|
5
|
+
class Type::Text < Type
|
6
|
+
def parameters
|
7
|
+
{
|
8
|
+
Parameter::Language => :language,
|
9
|
+
Parameter::AlternateRepresentation => :altrep
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
def encode!(object)
|
14
|
+
str = object.to_s.dup
|
15
|
+
str.gsub! "\\", "\\\\"
|
16
|
+
str.gsub! "\n", "\\n"
|
17
|
+
str.gsub! ";", "\\;"
|
18
|
+
str.gsub! ",", "\\,"
|
19
|
+
str
|
20
|
+
end
|
21
|
+
|
22
|
+
def decode!(string)
|
23
|
+
str = string.to_s.dup
|
24
|
+
str.gsub! "\\,", ","
|
25
|
+
str.gsub! "\\;", ";"
|
26
|
+
str.gsub! "\\n", "\n"
|
27
|
+
str.gsub! "\\\\", "\\"
|
28
|
+
str
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "time"
|
2
|
+
|
3
|
+
module Kali
|
4
|
+
# Used to represent precise times of the day.
|
5
|
+
#
|
6
|
+
# See http://tools.ietf.org/html/rfc5545#section-3.3.12
|
7
|
+
class Type::Time < Type::DateTime
|
8
|
+
def encode!(*)
|
9
|
+
super.split("T").last
|
10
|
+
end
|
11
|
+
|
12
|
+
def decode!(string)
|
13
|
+
_, hour, minute, second, utc = *string.match(/(\d{2})(\d{2})(\d{2})?(Z)?/)
|
14
|
+
::Time.parse([hour, minute, second || "00"].join(":") + utc.to_s)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "uri"
|
2
|
+
|
3
|
+
module Kali
|
4
|
+
# Used to represent URIs in properties.
|
5
|
+
#
|
6
|
+
# See http://tools.ietf.org/html/rfc5545#section-3.3.13
|
7
|
+
class Type::URI < Type
|
8
|
+
def encode!(uri)
|
9
|
+
uri.to_s
|
10
|
+
end
|
11
|
+
|
12
|
+
def decode!(string)
|
13
|
+
::URI.parse(string)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Kali
|
2
|
+
# Public: List of objects that have a specific type in them. Useful for lists
|
3
|
+
# of properties (like ATTENDEE or COMMENT, that can have multiple instances of
|
4
|
+
# the same property in a component) or sub-components.
|
5
|
+
class TypedList
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
# Internal: Initialize the List.
|
9
|
+
#
|
10
|
+
# type - The type of objects that this list contains.
|
11
|
+
def initialize(type)
|
12
|
+
@type = type
|
13
|
+
@items = []
|
14
|
+
end
|
15
|
+
|
16
|
+
# Public: Add a new item to the list.
|
17
|
+
#
|
18
|
+
# value - The item to be added. If it's an instance of the type of the list,
|
19
|
+
# then it adds it directly, otherwise it passes this to the
|
20
|
+
# constructor of that type. If it's `nil` it's ignored and just an
|
21
|
+
# empty new instance of the correct type is added to the list.
|
22
|
+
#
|
23
|
+
# Returns the list.
|
24
|
+
# Yields the Property if a block is passed.
|
25
|
+
def add(value = nil)
|
26
|
+
item = case value
|
27
|
+
when @type; value
|
28
|
+
when nil; @type.new
|
29
|
+
else @type.new(value)
|
30
|
+
end
|
31
|
+
|
32
|
+
yield item if block_given?
|
33
|
+
@items << item
|
34
|
+
self
|
35
|
+
end
|
36
|
+
alias_method :<<, :add
|
37
|
+
|
38
|
+
# Public: Get an iCalendar representation of this list of properties.
|
39
|
+
#
|
40
|
+
# Returns a String.
|
41
|
+
def to_ics
|
42
|
+
@items.map(&:to_ics).join("\n")
|
43
|
+
end
|
44
|
+
|
45
|
+
# Internal: Iterate over the properties in this list.
|
46
|
+
#
|
47
|
+
# Yields each property in turn.
|
48
|
+
def each(&block)
|
49
|
+
@items.each(&block)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/kali/utils.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
module Kali
|
2
|
+
module Utils
|
3
|
+
module Named
|
4
|
+
# Public: Get/Set the name of the property implemented by this class.
|
5
|
+
def name(name = nil)
|
6
|
+
@name = name if name
|
7
|
+
@name
|
8
|
+
end
|
9
|
+
|
10
|
+
# Public: Get a default name for the method used as an accessor for this
|
11
|
+
# property, based on the name of the property.
|
12
|
+
def method_name
|
13
|
+
@method_name ||= name.to_s.downcase.gsub("-", "_") unless name.nil?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Kali
|
2
|
+
module Utils
|
3
|
+
module Text
|
4
|
+
module_function
|
5
|
+
|
6
|
+
# Public: Fold lines as indicated by the Content Lines section of
|
7
|
+
# [RFC5545](http://tools.ietf.org/html/rfc5545#section-3.1).
|
8
|
+
#
|
9
|
+
# FIXME: This is splitting into 75 *character* long lines, and not 75
|
10
|
+
# *octet* long lines. We should deal with UTF-8 properly.
|
11
|
+
#
|
12
|
+
# line - A String.
|
13
|
+
#
|
14
|
+
# Returns a multi-line String.
|
15
|
+
def fold_line(line)
|
16
|
+
head, tail = line[0...75], line[75..-1]
|
17
|
+
tail &&= tail.scan(/.{0,74}/)
|
18
|
+
.reject { |fragment| fragment.nil? || fragment.empty? }
|
19
|
+
.map { |fragment| " #{fragment}" }
|
20
|
+
[head, *tail].join("\r\n")
|
21
|
+
end
|
22
|
+
|
23
|
+
# Public: Collapse a folded line into a single line without line breaks. See
|
24
|
+
# `TextUtils.fold_line`, and [RFC5545][rfc].
|
25
|
+
#
|
26
|
+
# [rfc]: http://tools.ietf.org/html/rfc5545#section-3.1
|
27
|
+
#
|
28
|
+
# FIXME: This isn't taking into account that the line could have been folded
|
29
|
+
# in the middle of a multi-byte character.
|
30
|
+
#
|
31
|
+
# lines - A multi-line String that follows the folding format specified by
|
32
|
+
# the RFC.
|
33
|
+
#
|
34
|
+
# Returns a String.
|
35
|
+
def unfold_line(lines)
|
36
|
+
head, *tail = lines.split("\r\n")
|
37
|
+
tail.map! { |line| line[1..-1] }
|
38
|
+
[head, *tail].join("")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/kali/version.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class CalendarTest < MiniTest::Unit::TestCase
|
4
|
+
def test_calendar_defaults
|
5
|
+
cal = Kali::Calendar.new
|
6
|
+
assert_equal Kali::Property::ProductIdentifier.default, cal.prodid.to_s
|
7
|
+
assert_equal Kali::Property::Version.default, cal.version.to_s
|
8
|
+
assert_equal Kali::Property::CalendarScale.default, cal.calscale.to_s
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_calendar_overwrites_default_properties
|
12
|
+
cal = Kali::Calendar.new do |calendar|
|
13
|
+
calendar.prodid = "Product Id"
|
14
|
+
calendar.method = "REQUEST"
|
15
|
+
end
|
16
|
+
|
17
|
+
assert_equal "Product Id", cal.prodid.value
|
18
|
+
assert_equal "REQUEST", cal.method.value
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class EventTest < MiniTest::Unit::TestCase
|
4
|
+
def test_event_properties
|
5
|
+
event = Kali::Event.new do |e|
|
6
|
+
e.sequence = 0
|
7
|
+
e.uid = 1
|
8
|
+
e.description = "Some description about the event"
|
9
|
+
e.geo = [-54.458594, -34.123310]
|
10
|
+
e.categories = ["CATEGORY A", "CATEGORY B"]
|
11
|
+
e.comments.add do |comment|
|
12
|
+
comment.value = "This is a comment --John"
|
13
|
+
end
|
14
|
+
e.comments.add "This is another comment --Jane"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|