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,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
|