<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>app/models/source_parser/upcoming.rb</filename>
    </added>
    <added>
      <filename>spec/models/source_parser_upcoming_spec.rb</filename>
    </added>
    <added>
      <filename>spec/samples/upcoming_ip7.xml</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff>@@ -8,30 +8,26 @@ class SourceParser
   # * :url - URL string to read as parser input.
   # * :content - String to read as parser input.
   def self.to_abstract_events(opts)
-    # Upcoming consistently breaks their hCalendar content and I can't keep fixing the parser. The following horrible hack rewrites Upcoming's hCalendar URLs into iCalendar URLs in hopes that they're paying more attention to iCalendar's validity and so that we've only got one single set of Upcoming parser hacks. Here's what the two types of URLs look like:
-    # hCalendar: http://upcoming.yahoo.com/event/1366250/
-    # iCalendar: webcal://upcoming.yahoo.com/calendar/v2/event/1366250
-    if matcher = opts[:url].ergo.match(%r{http://upcoming.yahoo.com/event/(\w+)})
-      opts[:url] = &quot;http://upcoming.yahoo.com/calendar/v2/event/#{matcher[1]}&quot;
-    end
-
+    # Cache the content
     content = self.content_for(opts)
 
-    returning([]) do |events|
-      parsers.each do |parser|
-        begin
-          events.concat(parser.to_abstract_events(opts.merge(:content =&gt; content)))
-        rescue Exception =&gt; e
-          RAILS_DEFAULT_LOGGER.info(&quot;SourceParser.to_abstract_events : Can't parse with #{parser.name} because -- #{e}&quot;)
-          :ignore # Leave this line for rcov's code coverage
-        end
+    # Return events from the first parser that suceeds.
+    parsers.each do |parser|
+      begin
+        events = parser.to_abstract_events(opts.merge(:content =&gt; content))
+        return events if not events.blank?
+      rescue Exception =&gt; e
+        # Ignore
       end
     end
+
+    # Return empty set if no matches
+    return []
   end
 
   # Returns an Array of parser classes for the various formats
   def self.parsers
-    $SourceParserImplementations.map{|parser| parser}.uniq
+    $SourceParserImplementations
   end
 
   # Return content for the arguments
@@ -53,9 +49,7 @@ class SourceParser
       # Add class-wide ::_label accessor to subclasses.
       subclass.meta_eval {attr_accessor :_label}
 
-      # Use global because it's the only data structure that survives a Rails #reload!
-      $SourceParserImplementations ||= Set.new
-      $SourceParserImplementations &lt;&lt; subclass
+      $SourceParserImplementations &lt;&lt; subclass unless $SourceParserImplementations.include?(subclass)
     end
 
     # Gets or sets the human-readable label for this parser.
@@ -115,8 +109,8 @@ class SourceParser
   end
 end
 
-# Load all the format-specific drivers in the &quot;source_parser&quot; directory
-source_parser_driver_path = File.join(File.dirname(__FILE__), &quot;source_parser&quot;)
-for entry in Dir.entries(source_parser_driver_path).select{|t| t.match(/.+\.rb$/)}
-  require File.join(source_parser_driver_path, entry)
-end
+# Load format-specific drivers in the following order:
+$SourceParserImplementations = []
+SourceParser::Upcoming
+SourceParser::Ical
+SourceParser::Hcal</diff>
      <filename>app/models/source_parser.rb</filename>
    </modified>
    <modified>
      <diff>@@ -39,10 +39,21 @@ describe SourceParser, &quot;when subclassing&quot; do
 end
 
 describe SourceParser, &quot;when parsing events&quot; do
-  it &quot;should skip past parsing errors&quot; do
+  it &quot;should have expected parsers plus FakeParser&quot; do
+    SourceParser.parsers.should == [
+      SourceParser::Upcoming,
+      SourceParser::Ical,
+      SourceParser::Hcal,
+      SourceParser::FakeParser,
+    ]
+  end
+
+  it &quot;should use first successful parser's results&quot; do
     events = [mock_model(SourceParser::AbstractEvent)]
-    SourceParser::FakeParser.should_receive(:to_abstract_events).and_raise(NotImplementedError)
+    SourceParser::Upcoming.should_receive(:to_abstract_events).and_return(false)
+    SourceParser::Ical.should_receive(:to_abstract_events).and_raise(NotImplementedError)
     SourceParser::Hcal.should_receive(:to_abstract_events).and_return(events)
+    SourceParser::FakeParser.should_not_receive(:to_abstract_events)
     SourceParser::Base.should_receive(:content_for).and_return(&quot;fake content&quot;)
 
     SourceParser.to_abstract_events(:fake =&gt; :argument).should == events</diff>
      <filename>spec/models/source_parser_spec.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>31b518c6f1dec33ceffd4b08fa66ac9c8a923db4</id>
    </parent>
  </parents>
  <author>
    <name>Igal Koshevoy</name>
    <email>igal@pragmaticraft.com</email>
  </author>
  <url>http://github.com/calagator/calagator/commit/31518d7b9b5854f78ebe57f0005f4555e1f6104d</url>
  <id>31518d7b9b5854f78ebe57f0005f4555e1f6104d</id>
  <committed-date>2009-10-19T16:25:01-07:00</committed-date>
  <authored-date>2009-10-19T16:25:01-07:00</authored-date>
  <message>Implemented SourceParser::Upcoming to import events using the Upcoming API, wrote spec and added sample data. Improved SourceParser to load drivers in specific order and return results from the first to suceeed, updated specs.</message>
  <tree>b6f7eb449f57939e7f9dc080a9d51b0ab7cfee45</tree>
  <committer>
    <name>Igal Koshevoy</name>
    <email>igal@pragmaticraft.com</email>
  </committer>
</commit>
