dash-bees 0.19 → 0.20

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,5 @@
1
+ 2010-09-20 v0.20 Added data source for Atom feeds
2
+
1
3
  2010-09-20 v0.19 Fixed sometimes missing identity for Github
2
4
 
3
5
  2010-09-16 v0.18 Now bees, have separate update and webhook methods
data/Gemfile CHANGED
@@ -8,7 +8,7 @@ end
8
8
 
9
9
  group :test do
10
10
  gem "awesome_print"
11
- gem "nokogiri"
11
+ gem "builder"
12
12
  gem "shoulda"
13
13
  gem "timecop"
14
14
  gem "vcr"
data/dash-bees.gemspec CHANGED
@@ -18,8 +18,9 @@ Gem::Specification.new do |spec|
18
18
 
19
19
  spec.required_ruby_version = '>= 1.8.7'
20
20
  spec.add_dependency "activesupport"
21
+ spec.add_dependency "i18n"
21
22
  spec.add_dependency "json"
22
23
  spec.add_dependency "nokogiri"
23
24
  spec.add_dependency "rack"
24
- spec.add_dependency "i18n"
25
+ spec.add_dependency "sanitize"
25
26
  end
data/lib/dash-fu/bee.rb CHANGED
@@ -1,8 +1,8 @@
1
- require "active_support"
2
1
  require "json"
3
2
  require "net/http"
4
- require "rack"
5
3
  require "uri"
4
+ require "open-uri"
5
+ require "rack"
6
6
 
7
7
  # See http://dash-fu.com
8
8
  module DashFu
@@ -10,7 +10,7 @@ module DashFu
10
10
  # The README covers it all.
11
11
  module Bee
12
12
 
13
- VERSION = "0.19"
13
+ VERSION = "0.20"
14
14
 
15
15
  class << self
16
16
  attr_accessor :logger
@@ -146,15 +146,15 @@ module DashFu
146
146
  # three arguments: status code, response body and response headers. The
147
147
  # block may be called asynchronoulsy.
148
148
  def get(path, headers = {}, &block)
149
- response = @http.request(get_request(path, headers))
150
- yield response.code, response.body, {}
149
+ response = @http.request(get_request(path, headers || {}))
150
+ yield response.code.to_i, response.body, {}
151
151
  end
152
152
 
153
153
  # Make a GET request and yield response to the block. If the response
154
154
  # status is 200 the second argument is the response JSON object. The block
155
155
  # may be called asynchronously.
156
156
  def get_json(path, headers = {}, &block)
157
- response = @http.request(get_request(path, headers))
157
+ response = @http.request(get_request(path, headers || {}))
158
158
  if Net::HTTPOK === response
159
159
  json = JSON.parse(response.body) rescue nil
160
160
  if json
@@ -0,0 +1,104 @@
1
+ require "sanitize"
2
+ require "nokogiri"
3
+
4
+ module DashFu::Bee
5
+ # Track Web feed (Atom or RSS).
6
+ class Feed
7
+ include DashFu::Bee
8
+
9
+ def setup(source, params)
10
+ source["url"] = params["url"].strip
11
+ source["source.name"] = "Web feed"
12
+ end
13
+
14
+ def validate(source)
15
+ uri = URI.parse(source["url"]) rescue nil
16
+ raise "Not a valid URL" unless uri && uri.absolute?
17
+ raise "Only HTTP/S URLs supported" unless uri.scheme == "http" || uri.scheme == "https"
18
+ begin
19
+ uri.open read_timeout: 3, redirect: true do |io|
20
+ code = io.status.first
21
+ raise "Cannot read this feed, got status code #{code}" unless code == "200"
22
+ feed = (Nokogiri::XML(io.read)>"feed").first
23
+ source["title"] = get_text(feed>"title").strip
24
+ source["source.name"] = source["title"] if source["title"].length > 2
25
+ permalink = (feed>"link[rel=alternate]").first
26
+ source["permalink"] = permalink["href"] if permalink
27
+ source["logo"] = (feed>"logo").text
28
+ end
29
+ rescue
30
+ raise "Cannot read this feed: is it down for you or just for us?"
31
+ end
32
+ end
33
+
34
+ def update(source, callback)
35
+ uri = URI.parse(source["url"])
36
+ session uri.host, uri.port do |http|
37
+ headers = { username: uri.user, password: uri.password } unless uri.user.blank? && uri.password.blank?
38
+ http.get uri.request_uri, headers do |status, body|
39
+ begin
40
+ case status
41
+ when 200
42
+ feed = (Nokogiri::XML(body)>"feed").first
43
+ updated = Time.iso8601((feed>"updated").text) rescue Time.now
44
+ if source["updated"].nil? || updated > source["updated"]
45
+ (feed>"entry").each do |entry|
46
+ published = Time.iso8601((entry>"published").text) rescue Time.now
47
+ break unless source["updated"].nil? || updated >= published
48
+ if author = (entry>"author").first
49
+ person = { fullname: (author>"name").text, email: (author>"email").text,
50
+ identities: (author>"uri").map(&:text) }
51
+ end
52
+ title = get_text(entry>"title")
53
+ url = (entry>"link[rel=alternate]").first["href"] rescue nil
54
+ if content = get_html(entry.css("summary,content"))
55
+ html = "posted <a href=\"#{h url}\">#{h title}</a>:\n<blockquote>#{content}</blockquote>"
56
+ else
57
+ html = "posted a link <a href=\"#{h url}\">#{h title}</a>"
58
+ end
59
+ callback.activity! uid: (entry>"id").text, url: url, html: html, timestamp: published, person: person
60
+ end
61
+ end
62
+ source["updated"] = updated
63
+ when 401
64
+ callback.error! "You are not authorized to access this feed"
65
+ when 404
66
+ callback.error! "Could not find the feed #{source["url"]}"
67
+ else
68
+ callback.error! "Last request didn't go as expected, trying again later"
69
+ end
70
+ rescue
71
+ logger.error $!
72
+ callback.error! "Last request didn't go as expected, trying again later"
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ def meta(source)
79
+ meta = []
80
+ meta << { title: "Source", text: source["title"], url: source["permalink"] }
81
+ meta
82
+ end
83
+
84
+ def get_text(elements)
85
+ element = elements.first
86
+ case element["type"]
87
+ when "html" ; Nokogiri::HTML(element.text).text.strip
88
+ when "xhtml", "text", nil ; element.text.strip
89
+ else ""
90
+ end
91
+ end
92
+
93
+ def get_html(elements)
94
+ element = elements.first
95
+ return unless element
96
+ case element["type"]
97
+ when "html" ; Sanitize.clean(element.text, Sanitize::Config::BASIC).strip
98
+ when "xhtml" ; Sanitize.clean(element.to_xhtml, Sanitize::Config::BASIC).strip
99
+ when "text"; element.text.strip
100
+ end
101
+ end
102
+
103
+ end
104
+ end
@@ -0,0 +1,7 @@
1
+ en:
2
+ description: "Gather activities from an Atom feed"
3
+ inputs: |-
4
+ <label>Feed URL <input type="text" name="source[url]" size="30"></label>
5
+ notes: |-
6
+ <ul>
7
+ </ul>
@@ -92,13 +92,5 @@ module DashFu::Bee
92
92
  meta
93
93
  end
94
94
 
95
- protected
96
-
97
- def http_request(http, source, path)
98
- get = Net::HTTP::Get.new(path.gsub(":repo", URI.escape(source["repo"])).gsub(":branch", URI.escape(source["branch"])))
99
- get.basic_auth "#{source["username"]}/token", source["api_token"] unless source["username"].blank? && source["api_token"].blank?
100
- http.request(get)
101
- end
102
-
103
95
  end
104
96
  end
@@ -78,14 +78,5 @@ closed <a href="#{url}">issue #{issue["number"]}</a> on #{source["repo"]}:
78
78
  [ { title: "On GitHub", url: "http://github.com/#{source["repo"]}/issues" } ]
79
79
  end
80
80
 
81
- protected
82
-
83
- def http_request(http, source, state)
84
- path = "/api/v2/json/issues/list/#{URI.escape source["repo"]}/#{state}"
85
- get = Net::HTTP::Get.new(path)
86
- get.basic_auth "#{source["username"]}/token", source["api_token"] unless source["username"].blank? && source["api_token"].blank?
87
- http.request(get)
88
- end
89
-
90
81
  end
91
82
  end
@@ -0,0 +1,83 @@
1
+ ---
2
+ - !ruby/struct:VCR::HTTPInteraction
3
+ request: !ruby/struct:VCR::Request
4
+ method: :get
5
+ uri: http://example.org:80/feed.xml
6
+ response: !ruby/struct:VCR::Response
7
+ status: !ruby/struct:VCR::ResponseStatus
8
+ code: 200
9
+ message: OK
10
+ body: |-
11
+ <?xml version="1.0" encoding="utf-8"?>
12
+ <feed xmlns="http://www.w3.org/2005/Atom">
13
+ <title type="text">dive into mark</title>
14
+ <subtitle type="html">
15
+ A &lt;em&gt;lot&lt;/em&gt; of effort
16
+ went into making this effortless
17
+ </subtitle>
18
+ <updated>2005-07-31T12:29:29Z</updated>
19
+ <id>tag:example.org,2003:3</id>
20
+ <link rel="alternate" type="text/html"
21
+ hreflang="en" href="http://example.org/"/>
22
+ <link rel="self" type="application/atom+xml"
23
+ href="http://example.org/feed.atom"/>
24
+ <rights>Copyright (c) 2003, Mark Pilgrim</rights>
25
+ <generator uri="http://www.example.com/" version="1.0">
26
+ Example Toolkit
27
+ </generator>
28
+ <entry>
29
+ <title>Atom draft-07 snapshot</title>
30
+ <link rel="alternate" type="text/html"
31
+ href="http://example.org/2005/04/02/atom"/>
32
+ <link rel="enclosure" type="audio/mpeg" length="1337"
33
+ href="http://example.org/audio/ph34r_my_podcast.mp3"/>
34
+ <id>tag:example.org,2003:3.2397</id>
35
+ <updated>2005-07-31T12:29:29Z</updated>
36
+ <published>2003-12-13T08:29:29-04:00</published>
37
+ <author>
38
+ <name>Mark Pilgrim</name>
39
+ <uri>http://example.org/</uri>
40
+ <email>f8dy@example.com</email>
41
+ </author>
42
+ <contributor>
43
+ <name>Sam Ruby</name>
44
+ </contributor>
45
+ <contributor>
46
+ <name>Joe Gregorio</name>
47
+ </contributor>
48
+ <content type="xhtml" xml:lang="en"
49
+ xml:base="http://diveintomark.org/">
50
+ <div xmlns="http://www.w3.org/1999/xhtml">
51
+ <p><i>[Update: The Atom draft is finished.]</i></p>
52
+ </div>
53
+ </content>
54
+ </entry>
55
+ </feed>
56
+ - !ruby/struct:VCR::HTTPInteraction
57
+ request: !ruby/struct:VCR::Request
58
+ method: :get
59
+ uri: http://example.org:80/summary.xml
60
+ response: !ruby/struct:VCR::Response
61
+ status: !ruby/struct:VCR::ResponseStatus
62
+ code: 200
63
+ message: OK
64
+ body: |-
65
+ <?xml version="1.0" encoding="utf-8"?>
66
+ <feed xmlns="http://www.w3.org/2005/Atom">
67
+ <title type="text">dive into mark</title>
68
+ <updated>2005-07-31T12:29:29Z</updated>
69
+ <entry>
70
+ <title>Atom draft-07 snapshot</title>
71
+ <id>tag:example.org,2003:3.2397</id>
72
+ <published>2003-12-13T08:29:29-04:00</published>
73
+ <link rel="alternate" type="text/html"
74
+ href="http://example.org/2005/04/02/atom"/>
75
+ <summary type="html">&lt;q&gt;Quickly stated&lt;/&gt</summary>
76
+ <content type="xhtml" xml:lang="en"
77
+ xml:base="http://diveintomark.org/">
78
+ <div xmlns="http://www.w3.org/1999/xhtml">
79
+ <p><i>[Update: The Atom draft is finished.]</i></p>
80
+ </div>
81
+ </content>
82
+ </entry>
83
+ </feed>
data/test/feed_test.rb ADDED
@@ -0,0 +1,132 @@
1
+ require_relative "setup"
2
+
3
+ test DashFu::Bee::Feed do
4
+ context "setup" do
5
+ subject { source.setup "url"=>"http://example.org/feed.xml" }
6
+ end
7
+
8
+ context "validation" do
9
+ should "fail if URL is invalid" do
10
+ assert_raises(RuntimeError) { source.setup("url"=>"/feed.xml") }
11
+ end
12
+
13
+ should "fail if URL is not HTTP" do
14
+ assert_raises(RuntimeError) { source.setup("url"=>"ftp://example.org/feed.xml") }
15
+ end
16
+
17
+ should "fail if URL is not accessible" do
18
+ stub_request(:get, "http://example.org/feed.xml").to_timeout
19
+ assert_raises(RuntimeError) { source.setup("url"=>"http://example.org/feed.xml") }
20
+ end
21
+
22
+ should "fail if status is not 200" do
23
+ stub_request(:get, "http://example.org/feed.xml").to_return status: 400
24
+ assert_raises(RuntimeError) { source.setup("url"=>"http://example.org/feed.xml") }
25
+ end
26
+
27
+ should "fail if document is not a feed" do
28
+ stub_request(:get, "http://example.org/feed.xml").to_return body: { not: "feed" }.to_json
29
+ assert_raises(RuntimeError) { source.setup("url"=>"http://example.org/feed.xml") }
30
+ end
31
+
32
+ should "set source name from feed title" do
33
+ stub_request(:get, "http://example.org/feed.xml").to_return body: { title: "The Awesome Feed" }.to_xml(root: "feed")
34
+ source.setup "url"=>"http://example.org/feed.xml"
35
+ assert_equal "The Awesome Feed", source.name
36
+ end
37
+ end
38
+
39
+ context "update" do
40
+ setup { source.setup "url"=>"http://example.org/feed.xml" }
41
+
42
+ should "handle 401" do
43
+ stub_request(:get, interactions.first.uri).to_return :status=>404
44
+ source.update
45
+ assert_equal "Could not find the feed http://example.org/feed.xml", source.last_error
46
+ end
47
+
48
+ should "handle 401" do
49
+ stub_request(:get, interactions.first.uri).to_return :status=>401
50
+ source.update
51
+ assert_equal "You are not authorized to access this feed", source.last_error
52
+ end
53
+
54
+ should "handle other error" do
55
+ stub_request(:get, interactions.first.uri).to_return :status=>500
56
+ source.update
57
+ assert_equal "Last request didn't go as expected, trying again later", source.last_error
58
+ end
59
+
60
+ should "handle invlid document entity" do
61
+ stub_request(:get, interactions.first.uri).to_return :body=>"Not a feed"
62
+ source.update
63
+ assert_equal "Last request didn't go as expected, trying again later", source.last_error
64
+ end
65
+
66
+ context "activity" do
67
+ setup { source.update }
68
+ subject { source.activity }
69
+
70
+ should "capture entry id" do
71
+ assert_equal "tag:example.org,2003:3.2397", subject.uid
72
+ end
73
+
74
+ should "capture published date" do
75
+ assert_equal Time.parse("2003-12-13T08:29:29-04:00"), subject.timestamp
76
+ end
77
+
78
+ should "capture URL" do
79
+ assert_equal "http://example.org/2005/04/02/atom", subject.url
80
+ end
81
+
82
+ should "capture title and content" do
83
+ assert_equal <<-HTML.strip, subject.html
84
+ posted <a href=\"http://example.org/2005/04/02/atom\">Atom draft-07 snapshot</a>:
85
+ <blockquote><p><i>[Update: The Atom draft is finished.]</i></p></blockquote>
86
+ HTML
87
+ end
88
+
89
+ context "person" do
90
+ subject { source.activity.person }
91
+
92
+ should "capture full name" do
93
+ assert_equal "Mark Pilgrim", subject.fullname
94
+ end
95
+
96
+ should "capture email" do
97
+ assert_equal "f8dy@example.com", subject.email
98
+ end
99
+
100
+ should "capture identity" do
101
+ assert_contains subject.identities, "http://example.org/"
102
+ end
103
+ end
104
+
105
+ end
106
+
107
+ context "with summary" do
108
+ setup do
109
+ source.setup "url"=>"http://example.org/summary.xml"
110
+ source.update
111
+ end
112
+ subject { source.activity }
113
+
114
+ should "capture title and summary" do
115
+ assert_equal <<-HTML.strip, subject.html
116
+ posted <a href=\"http://example.org/2005/04/02/atom\">Atom draft-07 snapshot</a>:
117
+ <blockquote><q>Quickly stated</q></blockquote>
118
+ HTML
119
+ end
120
+ end
121
+
122
+ end
123
+
124
+ context "meta" do
125
+ setup { source.setup "url"=>"http://example.org/feed.xml" }
126
+ subject { source.meta }
127
+
128
+ should "link to site" do
129
+ assert_contains subject, :title=>"Source", :text=>"dive into mark", :url=>"http://example.org/"
130
+ end
131
+ end
132
+ end
@@ -16,17 +16,17 @@ class Source
16
16
  # setup { source.setup "gem_name"=>"vanity" }
17
17
  def setup(params = {})
18
18
  bee.setup state, params
19
+ bee.validate state
19
20
  self.name = state.delete("source.name")
20
21
  @metric = Metric.new(:name=>name, :columns=>state.delete("metric.columns"), :totals=>!!state.delete("metric.totals")) if state["metric.columns"]
21
22
  validate
22
23
  end
23
24
 
24
25
  def validate
26
+ metric.validate if metric
25
27
  raise "Must be named" if name.blank?
26
28
  raise "Name must be 3 characters of longer" if name.length < 3
27
29
  raise "State fields can only use alphanumeric/underscore in their names" unless state.keys.all? { |k| k =~ /^\w+$/ }
28
- bee.validate state
29
- metric.validate if metric
30
30
  end
31
31
 
32
32
  def valid?
data/test/test.log CHANGED
@@ -932,3 +932,79 @@ RubyGems: 200
932
932
  RubyGems: 500
933
933
  RubyGems: 200
934
934
  RubyGems: 500
935
+ #<Timeout::Error: execution expired>
936
+ #<NameError: undefined local variable or method `body' for #<DashFu::Bee::Feed:0x00000101b4f670>>
937
+ #<OpenURI::HTTPError: 400 >
938
+ #<WebMock::NetConnectNotAllowedError: Real HTTP connections are disabled. Unregistered request: GET http://example.org/feed.xml with headers {'Accept'=>'*/*', 'User-Agent'=>'Ruby'}>
939
+ #<Timeout::Error: execution expired>
940
+ #<NameError: undefined local variable or method `body' for #<DashFu::Bee::Feed:0x000001014b6430>>
941
+ #<OpenURI::HTTPError: 400 >
942
+ #<WebMock::NetConnectNotAllowedError: Real HTTP connections are disabled. Unregistered request: GET http://example.org/feed.xml with headers {'Accept'=>'*/*', 'User-Agent'=>'Ruby'}>
943
+ #<NameError: undefined local variable or method `io' for #<DashFu::Bee::Feed:0x000001015d9650>>
944
+ #<NameError: undefined local variable or method `io' for #<DashFu::Bee::Feed:0x00000101727188>>
945
+ #<NameError: undefined local variable or method `io' for #<DashFu::Bee::Feed:0x00000100e870c8>>
946
+ #<NameError: undefined local variable or method `io' for #<DashFu::Bee::Feed:0x000001013110f8>>
947
+ #<NameError: undefined local variable or method `io' for #<DashFu::Bee::Feed:0x0000010114e1f8>>
948
+ #<NameError: undefined local variable or method `io' for #<DashFu::Bee::Feed:0x00000101577a90>>
949
+ #<NoMethodError: undefined method `[]' for nil:NilClass>
950
+ #<NoMethodError: undefined method `[]' for nil:NilClass>
951
+ #<NoMethodError: undefined method `[]' for nil:NilClass>
952
+ #<NoMethodError: undefined method `[]' for nil:NilClass>
953
+ #<NameError: uninitialized constant DashFu::Bee::Rack>
954
+ #<NameError: uninitialized constant DashFu::Bee::Rack>
955
+ #<NoMethodError: undefined method `[]' for nil:NilClass>
956
+ #<NoMethodError: undefined method `[]' for nil:NilClass>
957
+ #<NoMethodError: undefined method `[]' for nil:NilClass>
958
+ #<NoMethodError: undefined method `[]' for nil:NilClass>
959
+ #<NoMethodError: undefined method `[]' for nil:NilClass>
960
+ #<NoMethodError: undefined method `[]' for nil:NilClass>
961
+ #<NoMethodError: undefined method `[]' for nil:NilClass>
962
+ #<NoMethodError: undefined method `[]' for nil:NilClass>
963
+ #<NoMethodError: undefined method `[]' for nil:NilClass>
964
+ #<NoMethodError: undefined method `[]' for nil:NilClass>
965
+ #<NoMethodError: undefined method `[]' for nil:NilClass>
966
+ #<NoMethodError: undefined method `[]' for nil:NilClass>
967
+ #<NoMethodError: undefined method `[]' for nil:NilClass>
968
+ #<NoMethodError: undefined method `[]' for nil:NilClass>
969
+ #<NoMethodError: undefined method `[]' for nil:NilClass>
970
+ #<NoMethodError: undefined method `[]' for nil:NilClass>
971
+ #<NoMethodError: undefined method `[]' for nil:NilClass>
972
+ #<NoMethodError: undefined method `[]' for nil:NilClass>
973
+ #<NoMethodError: undefined method `[]' for nil:NilClass>
974
+ #<NoMethodError: undefined method `[]' for nil:NilClass>
975
+ #<NoMethodError: undefined method `[]' for nil:NilClass>
976
+ #<NoMethodError: undefined method `[]' for nil:NilClass>
977
+ #<NoMethodError: undefined method `[]' for nil:NilClass>
978
+ #<NameError: uninitialized constant DashFu::Bee::Feed::Sanitize>
979
+ #<NameError: uninitialized constant DashFu::Bee::Feed::Sanitize>
980
+ #<NameError: uninitialized constant DashFu::Bee::Feed::Sanitize>
981
+ #<NameError: uninitialized constant DashFu::Bee::Feed::Sanitize>
982
+ #<NameError: uninitialized constant DashFu::Bee::Feed::Sanitize>
983
+ #<NameError: uninitialized constant DashFu::Bee::Feed::Sanitize>
984
+ #<NameError: uninitialized constant DashFu::Bee::Feed::Sanitize>
985
+ #<NoMethodError: undefined method `namespaces' for nil:NilClass>
986
+ #<NoMethodError: undefined method `namespaces' for nil:NilClass>
987
+ #<NoMethodError: undefined method `namespaces' for nil:NilClass>
988
+ #<NoMethodError: undefined method `namespaces' for nil:NilClass>
989
+ #<NoMethodError: undefined method `namespaces' for nil:NilClass>
990
+ #<NoMethodError: undefined method `namespaces' for nil:NilClass>
991
+ #<NoMethodError: undefined method `namespaces' for nil:NilClass>
992
+ #<NoMethodError: undefined method `namespaces' for nil:NilClass>
993
+ #<Nokogiri::XML::XPath::SyntaxError: Undefined namespace prefix: ./content:summary>
994
+ #<Nokogiri::XML::XPath::SyntaxError: Undefined namespace prefix: ./content:summary>
995
+ #<Nokogiri::XML::XPath::SyntaxError: Undefined namespace prefix: ./content:summary>
996
+ #<Nokogiri::XML::XPath::SyntaxError: Undefined namespace prefix: ./content:summary>
997
+ #<Nokogiri::XML::XPath::SyntaxError: Undefined namespace prefix: ./content:summary>
998
+ #<Nokogiri::XML::XPath::SyntaxError: Undefined namespace prefix: ./content:summary>
999
+ #<Nokogiri::XML::XPath::SyntaxError: Undefined namespace prefix: ./content:summary>
1000
+ #<Nokogiri::XML::XPath::SyntaxError: Undefined namespace prefix: ./content:summary>
1001
+ #<NoMethodError: undefined method `namespaces' for nil:NilClass>
1002
+ #<NoMethodError: undefined method `namespaces' for nil:NilClass>
1003
+ #<NoMethodError: undefined method `namespaces' for nil:NilClass>
1004
+ #<NoMethodError: undefined method `namespaces' for nil:NilClass>
1005
+ #<NoMethodError: undefined method `namespaces' for nil:NilClass>
1006
+ #<NoMethodError: undefined method `namespaces' for nil:NilClass>
1007
+ #<NoMethodError: undefined method `namespaces' for nil:NilClass>
1008
+ #<NoMethodError: undefined method `namespaces' for nil:NilClass>
1009
+ #<NoMethodError: undefined method `namespaces' for nil:NilClass>
1010
+ #<NoMethodError: undefined method `namespaces' for nil:NilClass>
metadata CHANGED
@@ -4,8 +4,8 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 19
8
- version: "0.19"
7
+ - 20
8
+ version: "0.20"
9
9
  platform: ruby
10
10
  authors:
11
11
  - Assaf Arkin
@@ -30,7 +30,7 @@ dependencies:
30
30
  type: :runtime
31
31
  version_requirements: *id001
32
32
  - !ruby/object:Gem::Dependency
33
- name: json
33
+ name: i18n
34
34
  prerelease: false
35
35
  requirement: &id002 !ruby/object:Gem::Requirement
36
36
  none: false
@@ -43,7 +43,7 @@ dependencies:
43
43
  type: :runtime
44
44
  version_requirements: *id002
45
45
  - !ruby/object:Gem::Dependency
46
- name: nokogiri
46
+ name: json
47
47
  prerelease: false
48
48
  requirement: &id003 !ruby/object:Gem::Requirement
49
49
  none: false
@@ -56,7 +56,7 @@ dependencies:
56
56
  type: :runtime
57
57
  version_requirements: *id003
58
58
  - !ruby/object:Gem::Dependency
59
- name: rack
59
+ name: nokogiri
60
60
  prerelease: false
61
61
  requirement: &id004 !ruby/object:Gem::Requirement
62
62
  none: false
@@ -69,7 +69,7 @@ dependencies:
69
69
  type: :runtime
70
70
  version_requirements: *id004
71
71
  - !ruby/object:Gem::Dependency
72
- name: i18n
72
+ name: rack
73
73
  prerelease: false
74
74
  requirement: &id005 !ruby/object:Gem::Requirement
75
75
  none: false
@@ -81,6 +81,19 @@ dependencies:
81
81
  version: "0"
82
82
  type: :runtime
83
83
  version_requirements: *id005
84
+ - !ruby/object:Gem::Dependency
85
+ name: sanitize
86
+ prerelease: false
87
+ requirement: &id006 !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ segments:
93
+ - 0
94
+ version: "0"
95
+ type: :runtime
96
+ version_requirements: *id006
84
97
  description:
85
98
  email: assaf@labnotes.org
86
99
  executables: []
@@ -94,6 +107,8 @@ files:
94
107
  - lib/dash-fu/bee.rb
95
108
  - lib/dash-fu/bees/backtweets.rb
96
109
  - lib/dash-fu/bees/backtweets.yml
110
+ - lib/dash-fu/bees/feed.rb
111
+ - lib/dash-fu/bees/feed.yml
97
112
  - lib/dash-fu/bees/github.rb
98
113
  - lib/dash-fu/bees/github.yml
99
114
  - lib/dash-fu/bees/github_issues.rb
@@ -103,9 +118,11 @@ files:
103
118
  - test/api_keys.yml
104
119
  - test/backtweets_test.rb
105
120
  - test/cassettes/backtweets.yml
121
+ - test/cassettes/feed.yml
106
122
  - test/cassettes/github.yml
107
123
  - test/cassettes/github_issues.yml
108
124
  - test/cassettes/ruby_gems.yml
125
+ - test/feed_test.rb
109
126
  - test/github_issues_test.rb
110
127
  - test/github_test.rb
111
128
  - test/helpers/activity.rb
@@ -129,7 +146,7 @@ licenses: []
129
146
  post_install_message:
130
147
  rdoc_options:
131
148
  - --title
132
- - DashFu::Bee 0.19
149
+ - DashFu::Bee 0.20
133
150
  - --main
134
151
  - README.rdoc
135
152
  - --webcvs