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.
@@ -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