dash-bees 0.19 → 0.20

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