<commit_before>require "calagator/vcalendar"

# == Source::Parser::Ical
#
# Reads iCalendar events.
#
# Example:
#   events = Source::Parser::Ical.to_events('http://appendix.23ae.com/calendars/AlternateHolidays.ics')
#
# Sample sources:
#   webcal://appendix.23ae.com/calendars/AlternateHolidays.ics
#   http://appendix.23ae.com/calendars/AlternateHolidays.ics
module Calagator

class Source::Parser::Ical < Source::Parser
  self.label = :iCalendar

  # Override Source::Parser.read_url to handle "webcal" scheme addresses.
  def self.read_url(url)
    url.gsub!(/^webcal:/, 'http:')
    super
  end

  def to_events
    return false unless vcalendars
    current_vevents = vcalendars.flat_map(&:vevents).reject(&:old?)
    current_events = current_vevents.map { |vevent| to_event(vevent) }
    dedup(current_events)
  end

  private

  def vcalendars
    @vcalendars ||= VCalendar.parse(raw_ical)
  end

  def raw_ical
    self.class.read_url(url)
  end

  def to_event(vevent)
    event = EventParser.new(vevent).to_event
    event.venue = VenueParser.new(vevent.vvenue, vevent.location).to_venue
    event.source = source
    event
  end

  def dedup(events)
    events.map do |event|
      event = event_or_duplicate(event)
      event.venue = venue_or_duplicate(event.venue) if event.venue
      event
    end.uniq
  end

  # Converts a VEvent instance into an Event
  class EventParser < Struct.new(:vevent)
    def to_event
      Event.new({
        title:       vevent.summary,
        description: vevent.description,
        url:         vevent.url,
        start_time:  vevent.start_time,
        end_time:    vevent.end_time,
      })
    end
  end

  # Converts a VVenue instance into a Venue
  class VenueParser < Struct.new(:vvenue, :fallback)
    def to_venue
      from_vvenue or from_fallback or return
    end

    private

    def from_vvenue
      return unless vvenue
      location = vvenue.geo.split(/;/).map(&:to_f)
      Venue.new({
        title:          vvenue.name,
        street_address: vvenue.address,
        locality:       vvenue.city,
        region:         vvenue.region,
        postal_code:    vvenue.postalcode,
        country:        vvenue.country,
        latitude:       location.first,
        longitude:      location.last,
      }) do |venue|
        venue.geocode!
      end
    end

    def from_fallback
      return unless fallback.present?
      Venue.new(title: fallback)
    end
  end
end

end
<commit_msg>these are now mappers, not parsers.<commit_after>require "calagator/vcalendar"

# == Source::Parser::Ical
#
# Reads iCalendar events.
#
# Example:
#   events = Source::Parser::Ical.to_events('http://appendix.23ae.com/calendars/AlternateHolidays.ics')
#
# Sample sources:
#   webcal://appendix.23ae.com/calendars/AlternateHolidays.ics
#   http://appendix.23ae.com/calendars/AlternateHolidays.ics
module Calagator

class Source::Parser::Ical < Source::Parser
  self.label = :iCalendar

  # Override Source::Parser.read_url to handle "webcal" scheme addresses.
  def self.read_url(url)
    url.gsub!(/^webcal:/, 'http:')
    super
  end

  def to_events
    return false unless vcalendars
    current_vevents = vcalendars.flat_map(&:vevents).reject(&:old?)
    current_events = current_vevents.map { |vevent| to_event(vevent) }
    dedup(current_events)
  end

  private

  def vcalendars
    @vcalendars ||= VCalendar.parse(raw_ical)
  end

  def raw_ical
    self.class.read_url(url)
  end

  def to_event(vevent)
    event = EventMapper.new(vevent).to_event
    event.venue = VenueMapper.new(vevent.vvenue, vevent.location).to_venue
    event.source = source
    event
  end

  def dedup(events)
    events.map do |event|
      event = event_or_duplicate(event)
      event.venue = venue_or_duplicate(event.venue) if event.venue
      event
    end.uniq
  end

  # Converts a VEvent instance into an Event
  class EventMapper < Struct.new(:vevent)
    def to_event
      Event.new({
        title:       vevent.summary,
        description: vevent.description,
        url:         vevent.url,
        start_time:  vevent.start_time,
        end_time:    vevent.end_time,
      })
    end
  end

  # Converts a VVenue instance into a Venue
  class VenueMapper < Struct.new(:vvenue, :fallback)
    def to_venue
      from_vvenue or from_fallback or return
    end

    private

    def from_vvenue
      return unless vvenue
      location = vvenue.geo.split(/;/).map(&:to_f)
      Venue.new({
        title:          vvenue.name,
        street_address: vvenue.address,
        locality:       vvenue.city,
        region:         vvenue.region,
        postal_code:    vvenue.postalcode,
        country:        vvenue.country,
        latitude:       location.first,
        longitude:      location.last,
      }) do |venue|
        venue.geocode!
      end
    end

    def from_fallback
      return unless fallback.present?
      Venue.new(title: fallback)
    end
  end
end

end

{'fraction_non_alphanumeric': 0.09858585858585858, 'fraction_numerical': 0.0024242424242424242, 'mean_word_length': 2.616508400292184, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4543187', 'n_tokens_mistral': 1699, 'n_tokens_neox': 1649, 'n_words': 339}