kali 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,6 @@
1
+ module Kali
2
+ module Utils
3
+ autoload :Text, "kali/utils/text"
4
+ autoload :Named, "kali/utils/named"
5
+ end
6
+ end
@@ -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
@@ -0,0 +1,3 @@
1
+ module Kali
2
+ VERSION = "0.0.1"
3
+ end
@@ -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