File: component.rb

package info (click to toggle)
ruby-icalendar 2.8.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 492 kB
  • sloc: ruby: 2,868; makefile: 5
file content (115 lines) | stat: -rw-r--r-- 3,311 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
require 'securerandom'

module Icalendar

  class Component
    include HasProperties
    include HasComponents

    attr_reader :name
    attr_reader :ical_name
    attr_accessor :parent

    def self.parse(source)
      _parse source
    rescue ArgumentError
      source.rewind if source.respond_to?(:rewind)
      _parse Parser.clean_bad_wrapping(source)
    end

    def initialize(name, ical_name = nil)
      @name = name
      @ical_name = ical_name || "V#{name.upcase}"
      super()
    end

    def new_uid
      SecureRandom.uuid
    end

    def to_ical
      [
        "BEGIN:#{ical_name}",
        ical_properties,
        ical_components,
        "END:#{ical_name}\r\n"
      ].compact.join "\r\n"
    end

    private

    def ical_properties
      (self.class.properties + custom_properties.keys).map do |prop|
        value = property prop
        unless value.nil?
          if value.is_a? ::Array
            value.map do |part|
              ical_fold "#{ical_prop_name prop}#{part.to_ical self.class.default_property_types[prop]}"
            end.join "\r\n" unless value.empty?
          else
            ical_fold "#{ical_prop_name prop}#{value.to_ical self.class.default_property_types[prop]}"
          end
        end
      end.compact.join "\r\n"
    end

    def ical_prop_name(prop_name)
      prop_name.gsub(/\Aip_/, '').gsub('_', '-').upcase
    end

    def ical_fold(long_line, indent = "\x20")
      # rfc2445 says:
      # Lines of text SHOULD NOT be longer than 75 octets, excluding the line
      # break. Long content lines SHOULD be split into a multiple line
      # representations using a line "folding" technique. That is, a long
      # line can be split between any two characters by inserting a CRLF
      # immediately followed by a single linear white space character (i.e.,
      # SPACE, US-ASCII decimal 32 or HTAB, US-ASCII decimal 9). Any sequence
      # of CRLF followed immediately by a single linear white space character
      # is ignored (i.e., removed) when processing the content type.
      #
      # Note the useage of "octets" and "characters": a line should not be longer
      # than 75 octets, but you need to split between characters, not bytes.
      # This is challanging with Unicode composing accents, for example.

      return long_line if long_line.bytesize <= Icalendar::MAX_LINE_LENGTH

      chars = long_line.scan(/\P{M}\p{M}*/u) # split in graphenes
      folded = ['']
      bytes = 0
      while chars.count > 0
        c = chars.shift
        cb = c.bytes.count
        if bytes + cb > Icalendar::MAX_LINE_LENGTH
          # Split here
          folded.push "#{indent}"
          bytes = indent.bytes.count
        end
        folded[-1] += c
        bytes += cb
      end

      folded.join("\r\n")
    end

    def ical_components
      collection = []
      (self.class.components + custom_components.keys).each do |component_name|
        components = send component_name
        components.each do |component|
          collection << component.to_ical
        end
      end
      collection.empty? ? nil : collection.join.chomp("\r\n")
    end

    class << self
      private def _parse(source)
        parser = Parser.new(source)
        parser.component_class = self
        parser.parse
      end
    end
  end

end