hootenanny 0.0.1 → 0.1.0
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/.gitignore +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +5 -7
- data/Gemfile.lock +46 -28
- data/app/controllers/hootenanny/notifications_controller.rb +31 -0
- data/app/controllers/hootenanny/parameters.rb +16 -0
- data/app/controllers/hootenanny/subscriptions_controller.rb +24 -3
- data/app/models/hootenanny/publish_notification.rb +90 -0
- data/app/models/hootenanny/subscription.rb +72 -23
- data/config/routes.rb +2 -1
- data/db/migrate/20130607182642_add_started_at_and_lease_duration_to_subscriptions.rb +15 -0
- data/db/migrate/20130608225621_add_hmac_secret_to_subscription.rb +5 -0
- data/db/migrate/20130611235218_add_publish_notifications.rb +9 -0
- data/db/migrate/20130612153138_add_timestamps_to_publish_notification.rb +5 -0
- data/db/migrate/20130705200729_add_processed_flag_to_publish_notifications.rb +6 -0
- data/db/migrate/20130711061329_switch_publish_notification_process_state_from_boolean_to_string.rb +11 -0
- data/db/migrate/20130711061558_add_index_to_notifications_state.rb +5 -0
- data/hootenanny.gemspec +6 -2
- data/lib/hootenanny/configuration.rb +43 -0
- data/lib/hootenanny/correspondent.rb +44 -0
- data/lib/hootenanny/errors.rb +42 -1
- data/lib/hootenanny/feed.rb +75 -0
- data/lib/hootenanny/feed/atom_feed.rb +18 -0
- data/lib/hootenanny/feed/atom_feed_item.rb +8 -0
- data/lib/hootenanny/feed/digest_feed.rb +14 -0
- data/lib/hootenanny/feed/digest_feed_item.rb +11 -0
- data/lib/hootenanny/feed/feed_item.rb +30 -0
- data/lib/hootenanny/feed/file.rb +66 -0
- data/lib/hootenanny/feed/json_feed.rb +48 -0
- data/lib/hootenanny/feed/json_feed_item.rb +8 -0
- data/lib/hootenanny/feed/null_feed.rb +27 -0
- data/lib/hootenanny/feed/rss_feed.rb +52 -0
- data/lib/hootenanny/feed/rss_feed_item.rb +8 -0
- data/lib/hootenanny/feed_store.rb +30 -0
- data/lib/hootenanny/feed_store/file_feed_store.rb +55 -0
- data/lib/hootenanny/feed_store/web_feed_store.rb +42 -0
- data/lib/hootenanny/hub.rb +116 -22
- data/lib/hootenanny/publish_notification_expiration_policy.rb +17 -0
- data/lib/hootenanny/request.rb +40 -0
- data/lib/hootenanny/request/publish_notification.rb +94 -0
- data/lib/hootenanny/request/subscription.rb +153 -0
- data/lib/hootenanny/subscription_delivery.rb +47 -0
- data/lib/hootenanny/topic.rb +38 -0
- data/lib/hootenanny/topic_synchronizer.rb +71 -0
- data/lib/hootenanny/uri.rb +53 -0
- data/lib/hootenanny/verification.rb +108 -0
- data/lib/hootenanny/version.rb +1 -1
- data/spec/dummy/config/database.yml +12 -6
- data/spec/dummy/db/migrate/20130607183149_add_started_at_and_lease_duration_to_subscriptions.hootenanny.rb +16 -0
- data/spec/dummy/db/migrate/20130608231253_add_hmac_secret_to_subscription.hootenanny.rb +6 -0
- data/spec/dummy/db/migrate/20130611235546_add_publish_notifications.hootenanny.rb +10 -0
- data/spec/dummy/db/migrate/20130612153353_add_timestamps_to_publish_notification.hootenanny.rb +6 -0
- data/spec/dummy/db/migrate/20130705200832_add_processed_flag_to_publish_notifications.hootenanny.rb +7 -0
- data/spec/dummy/db/migrate/20130711061518_switch_publish_notification_process_state_from_boolean_to_string.hootenanny.rb +12 -0
- data/spec/dummy/db/migrate/20130711061629_add_index_to_notifications_state.hootenanny.rb +6 -0
- data/spec/factories/publish_notification.rb +13 -0
- data/spec/factories/requests/publish_notification.rb +11 -0
- data/spec/factories/requests/subscription.rb +46 -0
- data/spec/factories/subscription.rb +14 -2
- data/spec/factories/verification.rb +15 -0
- data/spec/features/publishers/can_notify_the_hub_of_content_updates_spec.rb +58 -0
- data/spec/features/subscribers/are_protected_from_unwarranted_subscriptions_spec.rb +59 -0
- data/spec/features/subscribers/can_receive_distributions_of_topic_content_spec.rb +175 -0
- data/spec/features/subscribers/can_subscribe_to_a_topic_spec.rb +76 -7
- data/spec/fixtures/feeds/atom/97d79220f68b4bf27.atom +0 -0
- data/spec/fixtures/feeds/atom/sample_feed.atom +51 -0
- data/spec/fixtures/feeds/digest/97d79220f68b4bf27.digest +1 -0
- data/spec/fixtures/feeds/digest/complete_broadcasted_items/5b187098da59f077f/97d79220f68b4bf27.digest +6 -0
- data/spec/fixtures/feeds/digest/incomplete_broadcasted_items/5b187098da59f077f/97d79220f68b4bf27.digest +5 -0
- data/spec/fixtures/feeds/digest/sample_feed.digest +6 -0
- data/spec/fixtures/feeds/json/97d79220f68b4bf27.json +1 -0
- data/spec/fixtures/feeds/json/feed_with_one_item.json +21 -0
- data/spec/fixtures/feeds/json/sample_feed.json +30 -0
- data/spec/fixtures/feeds/rss/5b187098da59f077f/97d79220f68b4bf27.rss +21 -0
- data/spec/fixtures/feeds/rss/97d79220f68b4bf27.rss +0 -0
- data/spec/fixtures/feeds/rss/feed_with_one_item.rss +15 -0
- data/spec/fixtures/feeds/rss/minimal_feed.rss +12 -0
- data/spec/fixtures/feeds/rss/sample_feed.rss +22 -0
- data/spec/fixtures/feeds/rss/sample_feed_2.rss +22 -0
- data/spec/lib/hootenanny/configuration_spec.rb +7 -0
- data/spec/lib/hootenanny/correspondent_spec.rb +94 -0
- data/spec/lib/hootenanny/errors_spec.rb +21 -0
- data/spec/lib/hootenanny/feed/atom_feed_item_spec.rb +9 -0
- data/spec/lib/hootenanny/feed/atom_feed_spec.rb +40 -0
- data/spec/lib/hootenanny/feed/digest_feed_item_spec.rb +9 -0
- data/spec/lib/hootenanny/feed/digest_feed_spec.rb +40 -0
- data/spec/lib/hootenanny/feed/feed_item_spec.rb +49 -0
- data/spec/lib/hootenanny/feed/file_spec.rb +66 -0
- data/spec/lib/hootenanny/feed/json_feed_item_spec.rb +9 -0
- data/spec/lib/hootenanny/feed/json_feed_spec.rb +128 -0
- data/spec/lib/hootenanny/feed/null_feed_spec.rb +9 -0
- data/spec/lib/hootenanny/feed/rss_feed_item_spec.rb +9 -0
- data/spec/lib/hootenanny/feed/rss_feed_spec.rb +143 -0
- data/spec/lib/hootenanny/feed_spec.rb +159 -0
- data/spec/lib/hootenanny/feed_store/file_feed_store_spec.rb +58 -0
- data/spec/lib/hootenanny/feed_store/web_feed_store_spec.rb +47 -0
- data/spec/lib/hootenanny/feed_store_spec.rb +27 -0
- data/spec/lib/hootenanny/hub_spec.rb +73 -0
- data/spec/lib/hootenanny/publish_notification_expiration_policy_spec.rb +35 -0
- data/spec/lib/hootenanny/request/publish_notification_spec.rb +43 -0
- data/spec/lib/hootenanny/request/subscription_spec.rb +89 -0
- data/spec/lib/hootenanny/request_spec.rb +21 -0
- data/spec/lib/hootenanny/subscription_delivery_spec.rb +54 -0
- data/spec/lib/hootenanny/topic_spec.rb +15 -0
- data/spec/lib/hootenanny/topic_synchronizer_spec.rb +98 -0
- data/spec/lib/hootenanny/uri_spec.rb +32 -0
- data/spec/lib/hootenanny/verification_spec.rb +92 -0
- data/spec/models/hootenanny/publish_notification_spec.rb +55 -0
- data/spec/models/hootenanny/subscription_spec.rb +58 -19
- data/spec/support/verification.rb +63 -0
- metadata +231 -14
- data/spec/controllers/hootenanny/hub_spec.rb +0 -15
@@ -0,0 +1,175 @@
|
|
1
|
+
require 'rspectacular/spec_helpers/rails_engine'
|
2
|
+
require 'pathname'
|
3
|
+
require 'hootenanny'
|
4
|
+
|
5
|
+
###
|
6
|
+
# In order me to be albe to process information I find relevant within a timely fashion
|
7
|
+
# As a Subscriber
|
8
|
+
# I want to be able to receive topic content updates from the hub
|
9
|
+
#
|
10
|
+
feature 'Subscribers receive content updates' do
|
11
|
+
let(:topic_content) { JSON.dump(JSON.load(Pathname.new('./spec/fixtures/feeds/json/sample_feed.json').read)) }
|
12
|
+
let(:partial_topic_content) { JSON.dump(JSON.load(Pathname.new('./spec/fixtures/feeds/json/feed_with_one_item.json').read)) }
|
13
|
+
let(:topic_digest_content) { '{"items":["1a6ed1752c74c2b7fcc0f43ce39e53f8f220cd85f85e339b06eb75a0f16bbea8","6d557b1225129abebcb56d4c986dc63bf644ee865c3833f52ca69e75b4275052"]}' }
|
14
|
+
let(:partial_topic_digest_content) { '{"items":["6d557b1225129abebcb56d4c986dc63bf644ee865c3833f52ca69e75b4275052"]}' }
|
15
|
+
let(:broadcasted_topic_dir) { Pathname.new('./tmp/broadcasted_topics') }
|
16
|
+
let(:broadcasted_topic_file) { Pathname.new('./tmp/broadcasted_topics/5b187098da59f077f/97d79220f68b4bf27.digest') }
|
17
|
+
|
18
|
+
after(:each) { broadcasted_topic_dir.rmtree if broadcasted_topic_dir.exist? }
|
19
|
+
|
20
|
+
scenario 'Subscribers receive full content updates for their feed if they have never before had a feed broadcast' do
|
21
|
+
# Given that a topic has content
|
22
|
+
stub_request(:get, 'http://topic.com').
|
23
|
+
to_return(body: topic_content,
|
24
|
+
headers: {'Content-Type' => 'application/json'})
|
25
|
+
|
26
|
+
# And the subscriber is set up to receive said content
|
27
|
+
content_delivery_request = \
|
28
|
+
stub_request(:post, 'http://subscriber.com').
|
29
|
+
with( :body => topic_content,
|
30
|
+
:headers => {'Content-Type' => 'application/json'})
|
31
|
+
|
32
|
+
# And there is a notification that the topic has new content
|
33
|
+
create(:publish_notification, :topic => 'http://topic.com')
|
34
|
+
|
35
|
+
# And a subscriber is subscribed to that topic
|
36
|
+
create(:subscription, :active,
|
37
|
+
subscriber: 'http://subscriber.com',
|
38
|
+
topic: 'http://topic.com')
|
39
|
+
|
40
|
+
# And the subscriber has previously never received a content update
|
41
|
+
expect(broadcasted_topic_dir).not_to be_exist
|
42
|
+
|
43
|
+
# When the broadcasting process is kicked off
|
44
|
+
Hootenanny::Hub.broadcast
|
45
|
+
|
46
|
+
# Then the subscriber should have been sent all of the content for the feed
|
47
|
+
expect(content_delivery_request).to have_been_requested
|
48
|
+
|
49
|
+
# And the feed they were sent should be saved to the default broadcast store
|
50
|
+
expect(broadcasted_topic_file).to be_exist
|
51
|
+
expect(broadcasted_topic_file.read).to eql topic_digest_content
|
52
|
+
|
53
|
+
# And the notification should be marked as processed
|
54
|
+
expect(Hootenanny::PublishNotification.processed).to have(1).items
|
55
|
+
expect(Hootenanny::PublishNotification.unprocessed).to have(0).items
|
56
|
+
end
|
57
|
+
|
58
|
+
scenario 'Subscribers receive partial content updates if they have previously been sent a content update' do
|
59
|
+
# Given that a topic has content
|
60
|
+
stub_request(:get, 'http://topic.com').
|
61
|
+
to_return(body: topic_content,
|
62
|
+
headers: {'Content-Type' => 'application/json'})
|
63
|
+
|
64
|
+
# And the subscriber is set up to receive said content
|
65
|
+
content_delivery_request = \
|
66
|
+
stub_request(:post, 'http://subscriber.com').
|
67
|
+
with( :body => partial_topic_content,
|
68
|
+
:headers => {'Content-Type' => 'application/json'})
|
69
|
+
|
70
|
+
# And there is a notification that the topic has new content
|
71
|
+
create(:publish_notification, :topic => 'http://topic.com')
|
72
|
+
|
73
|
+
# And a subscriber is subscribed to that topic
|
74
|
+
create(:subscription, :active,
|
75
|
+
subscriber: 'http://subscriber.com',
|
76
|
+
topic: 'http://topic.com')
|
77
|
+
|
78
|
+
# And the subscriber has previously received a content update
|
79
|
+
broadcasted_topic_file.dirname.mkpath
|
80
|
+
::File.write(broadcasted_topic_file, partial_topic_digest_content)
|
81
|
+
|
82
|
+
# When the broadcasting process is kicked off
|
83
|
+
Hootenanny::Hub.broadcast
|
84
|
+
|
85
|
+
# Then the subscriber should have been sent only the new or updated items for that feed
|
86
|
+
expect(content_delivery_request).to have_been_requested
|
87
|
+
|
88
|
+
# And the feed they were sent should be saved to the default broadcast store
|
89
|
+
expect(broadcasted_topic_file).to be_exist
|
90
|
+
expect(broadcasted_topic_file.read).to eql topic_digest_content
|
91
|
+
|
92
|
+
# And the notification should be marked as processed
|
93
|
+
expect(Hootenanny::PublishNotification.processed).to have(1).items
|
94
|
+
expect(Hootenanny::PublishNotification.unprocessed).to have(0).items
|
95
|
+
end
|
96
|
+
|
97
|
+
scenario 'Subscribers who reply unsuccessfully will have their content update reposted' do
|
98
|
+
# Given that a topic has content
|
99
|
+
stub_request(:get, 'http://topic.com').
|
100
|
+
to_return(body: topic_content,
|
101
|
+
headers: {'Content-Type' => 'application/json'})
|
102
|
+
|
103
|
+
# And the subscriber is not ready to receive said content
|
104
|
+
content_delivery_request = \
|
105
|
+
stub_request(:post, 'http://subscriber.com').
|
106
|
+
with( :body => topic_content,
|
107
|
+
:headers => {'Content-Type' => 'application/json'}).
|
108
|
+
to_return(:status => 500)
|
109
|
+
|
110
|
+
# And there is a notification that the topic has new content
|
111
|
+
create(:publish_notification, :topic => 'http://topic.com')
|
112
|
+
|
113
|
+
# And a subscriber is subscribed to that topic
|
114
|
+
create(:subscription, :active,
|
115
|
+
subscriber: 'http://subscriber.com',
|
116
|
+
topic: 'http://topic.com')
|
117
|
+
|
118
|
+
# And the subscriber has previously never received a content update
|
119
|
+
expect(broadcasted_topic_dir).not_to be_exist
|
120
|
+
|
121
|
+
# When the broadcasting process is kicked off
|
122
|
+
Hootenanny::Hub.broadcast
|
123
|
+
|
124
|
+
# Then the subscriber should have been sent the items for the feed
|
125
|
+
expect(content_delivery_request).to have_been_requested
|
126
|
+
|
127
|
+
# And the feed they were sent should not be saved to the default broadcast store
|
128
|
+
expect(broadcasted_topic_file).not_to be_exist
|
129
|
+
|
130
|
+
# And the notification should not be marked as processed
|
131
|
+
expect(Hootenanny::PublishNotification.processed).to have(0).items
|
132
|
+
expect(Hootenanny::PublishNotification.unprocessed).to have(1).items
|
133
|
+
end
|
134
|
+
|
135
|
+
scenario 'Subscribers who repeatedly reply unsuccessfully will have their notification request revoked after a reasonable period of time' do
|
136
|
+
# Given that a topic has content
|
137
|
+
stub_request(:get, 'http://topic.com').
|
138
|
+
to_return(body: topic_content,
|
139
|
+
headers: {'Content-Type' => 'application/json'})
|
140
|
+
|
141
|
+
# And the subscriber is not ready to receive said content
|
142
|
+
content_delivery_request = \
|
143
|
+
stub_request(:post, 'http://subscriber.com').
|
144
|
+
with( :body => topic_content,
|
145
|
+
:headers => {'Content-Type' => 'application/json'}).
|
146
|
+
to_return(:status => 500)
|
147
|
+
|
148
|
+
# And there is a notification that the topic has new content
|
149
|
+
create(:publish_notification, :topic => 'http://topic.com')
|
150
|
+
|
151
|
+
# And a subscriber is subscribed to that topic
|
152
|
+
create(:subscription, :active,
|
153
|
+
subscriber: 'http://subscriber.com',
|
154
|
+
topic: 'http://topic.com')
|
155
|
+
|
156
|
+
# And the subscriber has previously never received a content update
|
157
|
+
expect(broadcasted_topic_dir).not_to be_exist
|
158
|
+
|
159
|
+
# And it has been retried unsuccessfully for a day
|
160
|
+
Timecop.travel(1.day.from_now + 1)
|
161
|
+
|
162
|
+
# When the broadcasting process is kicked off
|
163
|
+
Hootenanny::Hub.broadcast
|
164
|
+
|
165
|
+
# Then the subscriber should have been sent the items for the feed
|
166
|
+
expect(content_delivery_request).to have_been_requested
|
167
|
+
|
168
|
+
# And the feed they were sent should not be saved to the default broadcast store
|
169
|
+
expect(broadcasted_topic_file).not_to be_exist
|
170
|
+
|
171
|
+
# And the notification should not be marked as errored
|
172
|
+
expect(Hootenanny::PublishNotification.expired).to have(1).items
|
173
|
+
expect(Hootenanny::PublishNotification.unprocessed).to have(0).items
|
174
|
+
end
|
175
|
+
end
|
@@ -6,30 +6,99 @@ require 'hootenanny'
|
|
6
6
|
# As a potential Subscriber
|
7
7
|
# I want to be able to get notifications about updates to a topic I am interressted in
|
8
8
|
#
|
9
|
-
feature 'Subscribers can subscribe to a topic' do
|
9
|
+
feature 'Subscribers can subscribe to a topic', :web_mock do
|
10
|
+
background do
|
11
|
+
confirmed_subscription_verification('http://mycallback',
|
12
|
+
'http://mytopic')
|
13
|
+
end
|
14
|
+
|
10
15
|
scenario 'Potential subscribers can subscribe to a new topic' do
|
11
16
|
# Given there are no subscriptions
|
12
17
|
expect( Hootenanny::Subscription ).to have(0).items
|
13
18
|
|
14
19
|
# When a subscription is requested for a topic
|
15
|
-
post( '/hootenanny/subscription', topic: '
|
16
|
-
callback: '
|
20
|
+
post( '/hootenanny/subscription', topic: 'http://mytopic',
|
21
|
+
callback: 'http://mycallback')
|
17
22
|
|
18
23
|
# Then the subscriber should be subscribed
|
19
|
-
expect( Hootenanny::Subscription.to('
|
24
|
+
expect( Hootenanny::Subscription.to('http://mytopic') ).to have(1).item
|
20
25
|
|
21
26
|
# And the response should be 204 'No Content'
|
22
27
|
expect( last_response.status ).to eql 204
|
23
28
|
expect( last_response ).to be_empty
|
24
29
|
end
|
25
30
|
|
31
|
+
scenario 'Potential subscribers can request a duration during which the subscription will be active', :time_mock do
|
32
|
+
# Given there are no subscriptions
|
33
|
+
expect( Hootenanny::Subscription ).to have(0).items
|
34
|
+
|
35
|
+
# When a subscription is requested which should be active for a given period
|
36
|
+
# of time
|
37
|
+
post( '/hootenanny/subscription', topic: 'http://mytopic',
|
38
|
+
callback: 'http://mycallback',
|
39
|
+
lease_seconds: 2)
|
40
|
+
|
41
|
+
# And that period has not yet elapsed
|
42
|
+
|
43
|
+
# Then the subscriber should be subscribed
|
44
|
+
expect( Hootenanny::Subscription.to('http://mytopic') ).to have(1).item
|
45
|
+
|
46
|
+
# When the subscription period has elapsed
|
47
|
+
Timecop.freeze(2)
|
48
|
+
|
49
|
+
# Then the subscriber should not be subscribed
|
50
|
+
expect( Hootenanny::Subscription.to('http://mytopic') ).to have(0).items
|
51
|
+
end
|
52
|
+
|
26
53
|
scenario 'Potential subscribers receive error information if there is a problem' do
|
27
54
|
# Given there are no subscriptions
|
28
55
|
expect( Hootenanny::Subscription ).to have(0).items
|
29
56
|
|
30
|
-
# When a
|
57
|
+
# When a subscription request is posted which is confirmable
|
58
|
+
confirmed_subscription_verification('http://mycallback', 'http://!nv4l!d')
|
59
|
+
|
60
|
+
# But is otherwise invalid
|
31
61
|
post( '/hootenanny/subscription', topic: 'http://!nv4l!d',
|
32
|
-
callback: '
|
62
|
+
callback: 'http://mycallback')
|
63
|
+
|
64
|
+
# Then the subscriber should not be subscribed
|
65
|
+
expect( Hootenanny::Subscription ).to have(0).items
|
66
|
+
|
67
|
+
# And the response should be 400 'Bad Request'
|
68
|
+
expect( last_response.status ).to eql 400
|
69
|
+
|
70
|
+
# And the response should include a message indicating the problem
|
71
|
+
expect( last_response.body ).to eql({'error' => {
|
72
|
+
'message' => 'the scheme http does not accept registry part: !nv4l!d (or bad hostname?)' }
|
73
|
+
}.to_json)
|
74
|
+
end
|
75
|
+
|
76
|
+
scenario 'Potential subscribers receive error information if they do not supply all required parameters' do
|
77
|
+
# Given there are no subscriptions
|
78
|
+
expect( Hootenanny::Subscription ).to have(0).items
|
79
|
+
|
80
|
+
# When a invalid subscription is requested for a topic
|
81
|
+
post( '/hootenanny/subscription', callback: 'http://mycallback')
|
82
|
+
|
83
|
+
# Then the subscriber should not be subscribed
|
84
|
+
expect( Hootenanny::Subscription ).to have(0).items
|
85
|
+
|
86
|
+
# And the response should be 400 'Bad Request'
|
87
|
+
expect( last_response.status ).to eql 400
|
88
|
+
|
89
|
+
# And the response should include a message indicating the problem
|
90
|
+
expect( last_response.body ).to eql({'error' => {
|
91
|
+
'message' => 'Required parameter missing: topic' }
|
92
|
+
}.to_json)
|
93
|
+
end
|
94
|
+
|
95
|
+
scenario 'Potential subscribers receive error information if they use a non-HTTP/HTTPS URI' do
|
96
|
+
# Given there are no subscriptions
|
97
|
+
expect( Hootenanny::Subscription ).to have(0).items
|
98
|
+
|
99
|
+
# When a invalid subscription is requested for a topic
|
100
|
+
post( '/hootenanny/subscription', topic: 'ftp://mytopic',
|
101
|
+
callback: 'ftp://mycallback')
|
33
102
|
|
34
103
|
# Then the subscriber should not be subscribed
|
35
104
|
expect( Hootenanny::Subscription ).to have(0).items
|
@@ -39,7 +108,7 @@ feature 'Subscribers can subscribe to a topic' do
|
|
39
108
|
|
40
109
|
# And the response should include a message indicating the problem
|
41
110
|
expect( last_response.body ).to eql({'error' => {
|
42
|
-
'message' => '
|
111
|
+
'message' => 'Only HTTP and HTTPS URIs are allowed.' }
|
43
112
|
}.to_json)
|
44
113
|
end
|
45
114
|
end
|
File without changes
|
@@ -0,0 +1,51 @@
|
|
1
|
+
<feed xmlns='http://www.w3.org/2005/Atom'
|
2
|
+
xmlns:thr='http://purl.org/syndication/thread/1.0'
|
3
|
+
xml:base='https://www.tbray.org/ongoing/ongoing.atom'
|
4
|
+
xml:lang='en-us'>
|
5
|
+
|
6
|
+
<title>ongoing by Tim Bray</title>
|
7
|
+
<link rel='hub' href='http://pubsubhubbub.appspot.com/' />
|
8
|
+
<id>https://www.tbray.org/ongoing/</id>
|
9
|
+
<link href='./' />
|
10
|
+
<link rel='self' href='' />
|
11
|
+
<link rel='replies' thr:count='101' href='/home/tbray.org/www/html/ongoing/comments.atom' />
|
12
|
+
<logo>rsslogo.jpg</logo>
|
13
|
+
<icon>/favicon.ico</icon>
|
14
|
+
<updated>2013-06-17T17:33:02-07:00</updated>
|
15
|
+
<author><name>Tim Bray</name></author>
|
16
|
+
<subtitle>ongoing fragmented essay by Tim Bray</subtitle>
|
17
|
+
<rights>All content written by Tim Bray and photos by Tim Bray Copyright Tim Bray, some rights reserved, see /ongoing/misc/Copyright</rights>
|
18
|
+
<generator uri='/misc/Colophon'>Generated from XML source code using Perl, Expat, Emacs, Mysql, Ruby, Java, and ImageMagick. Industrial-strength technology, baby.</generator>
|
19
|
+
|
20
|
+
<entry xml:base='When/201x/2013/06/16/'>
|
21
|
+
<title>Unmapped Lands</title>
|
22
|
+
<link href='The-Unmapped-Lands' />
|
23
|
+
<link rel='replies' thr:count='0' type='application/xhtml+xml' href='The-Unmapped-Lands#comments' />
|
24
|
+
<id>https://www.tbray.org/ongoing/When/201x/2013/06/16/The-Unmapped-Lands</id>
|
25
|
+
<published>2013-06-16T12:00:00-07:00</published>
|
26
|
+
<updated>2013-06-16T09:47:15-07:00</updated>
|
27
|
+
<category scheme='https://www.tbray.org/ongoing/What/' term='Arts/Books' />
|
28
|
+
<category scheme='https://www.tbray.org/ongoing/What/' term='Arts' />
|
29
|
+
<category scheme='https://www.tbray.org/ongoing/What/' term='Books' />
|
30
|
+
<summary type='xhtml'><div xmlns='http://www.w3.org/1999/xhtml'>summary</div></summary>
|
31
|
+
<content type='xhtml'><div xmlns='http://www.w3.org/1999/xhtml'>
|
32
|
+
description
|
33
|
+
</div></content>
|
34
|
+
</entry>
|
35
|
+
|
36
|
+
<entry xml:base='When/201x/2013/06/16/'>
|
37
|
+
<title>Golang Diaries I</title>
|
38
|
+
<link href='Go-Love-Hate' />
|
39
|
+
<link rel='replies' thr:count='27' type='application/xhtml+xml' href='Go-Love-Hate#comments' />
|
40
|
+
<id>https://www.tbray.org/ongoing/When/201x/2013/06/16/Go-Love-Hate</id>
|
41
|
+
<published>2013-06-16T12:00:00-07:00</published>
|
42
|
+
<updated>2013-06-15T13:07:09-07:00</updated>
|
43
|
+
<category scheme='https://www.tbray.org/ongoing/What/' term='Technology/Software' />
|
44
|
+
<category scheme='https://www.tbray.org/ongoing/What/' term='Technology' />
|
45
|
+
<category scheme='https://www.tbray.org/ongoing/What/' term='Software' />
|
46
|
+
<summary type='xhtml'><div xmlns='http://www.w3.org/1999/xhtml'>summary2</div></summary>
|
47
|
+
<content type='xhtml'><div xmlns='http://www.w3.org/1999/xhtml'>
|
48
|
+
description2
|
49
|
+
</div></content>
|
50
|
+
</entry>
|
51
|
+
</feed>
|
@@ -0,0 +1 @@
|
|
1
|
+
{}
|
@@ -0,0 +1 @@
|
|
1
|
+
{}
|
@@ -0,0 +1,21 @@
|
|
1
|
+
{
|
2
|
+
"title": "Ruby Rogues",
|
3
|
+
"feed_url": "http://rubyrogues.com/feed/",
|
4
|
+
"link": "http://rubyrogues.com",
|
5
|
+
"description": "Rubyist.where(:rogue => true)",
|
6
|
+
"language": "en-US",
|
7
|
+
"author": "Charles Max Wood, James Edward Gray II, David Brady, Avdi Grimm, Josh Susser, Katrina Owen",
|
8
|
+
"explicit": "no",
|
9
|
+
"image": "http://rubyrogues.com/wp-content/uploads/2013/05/RubyRogues_iTunes.jpg",
|
10
|
+
"items": [
|
11
|
+
{
|
12
|
+
"guid": "http://rubyrogues.com/?p=1361",
|
13
|
+
"title": "109 RR Extreme Programming with Will Read",
|
14
|
+
"link": "http://rubyrogues.com/109-rr-extreme-programming-with-will-read/",
|
15
|
+
"description": "description",
|
16
|
+
"pubDate": "Wed, 12 Jun 2013 13:00:31 +0000",
|
17
|
+
"enclosure": "http://traffic.libsyn.com/rubyrogues/RR109ExtremeProgramming.mp3",
|
18
|
+
"duration": "57:13"
|
19
|
+
}
|
20
|
+
]
|
21
|
+
}
|
@@ -0,0 +1,30 @@
|
|
1
|
+
{
|
2
|
+
"title": "Ruby Rogues",
|
3
|
+
"feed_url": "http://rubyrogues.com/feed/",
|
4
|
+
"link": "http://rubyrogues.com",
|
5
|
+
"description": "Rubyist.where(:rogue => true)",
|
6
|
+
"language": "en-US",
|
7
|
+
"author": "Charles Max Wood, James Edward Gray II, David Brady, Avdi Grimm, Josh Susser, Katrina Owen",
|
8
|
+
"explicit": "no",
|
9
|
+
"image": "http://rubyrogues.com/wp-content/uploads/2013/05/RubyRogues_iTunes.jpg",
|
10
|
+
"items": [
|
11
|
+
{
|
12
|
+
"guid": "http://rubyrogues.com/?p=1361",
|
13
|
+
"title": "109 RR Extreme Programming with Will Read",
|
14
|
+
"link": "http://rubyrogues.com/109-rr-extreme-programming-with-will-read/",
|
15
|
+
"description": "description",
|
16
|
+
"pubDate": "Wed, 12 Jun 2013 13:00:31 +0000",
|
17
|
+
"enclosure": "http://traffic.libsyn.com/rubyrogues/RR109ExtremeProgramming.mp3",
|
18
|
+
"duration": "57:13"
|
19
|
+
},
|
20
|
+
{
|
21
|
+
"guid": "http://rubyrogues.com/?p=1354",
|
22
|
+
"title": "108 RR Ruby Trends",
|
23
|
+
"link": "http://rubyrogues.com/108-rr-ruby-trends/",
|
24
|
+
"description": "description 2",
|
25
|
+
"pubDate": "Wed, 05 Jun 2013 13:00:46 +0000",
|
26
|
+
"enclosure": "http://traffic.libsyn.com/rubyrogues/RR108CommunityTrends.mp3",
|
27
|
+
"duration": "57:13"
|
28
|
+
}
|
29
|
+
]
|
30
|
+
}
|
@@ -0,0 +1,21 @@
|
|
1
|
+
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:rawvoice="http://www.rawvoice.com/rawvoiceRssModule/" version="2.0">
|
2
|
+
<channel>
|
3
|
+
<title>Ruby Rogues</title>
|
4
|
+
<link>http://rubyrogues.com</link>
|
5
|
+
<description>Rubyist.where(:rogue => true)</description>
|
6
|
+
<language>en-US</language>
|
7
|
+
<item>
|
8
|
+
<title>109 RR Extreme Programming with Will Read</title>
|
9
|
+
<link>http://rubyrogues.com/109-rr-extreme-programming-with-will-read/</link>
|
10
|
+
<guid isPermaLink="false">http://rubyrogues.com/?p=1361</guid>
|
11
|
+
<description>description text</description>
|
12
|
+
<enclosure url="http://traffic.libsyn.com/rubyrogues/RR109ExtremeProgramming.mp3" length="73410493" type="audio/mpeg"/>
|
13
|
+
</item>
|
14
|
+
<item>
|
15
|
+
<title>108 RR Ruby Trends</title>
|
16
|
+
<link>http://rubyrogues.com/108-rr-ruby-trends/</link>
|
17
|
+
<guid isPermaLink="false">http://rubyrogues.com/?p=1354</guid>
|
18
|
+
<description>description text 2</description>
|
19
|
+
<enclosure url="http://traffic.libsyn.com/rubyrogues/RR108CommunityTrends.mp3" length="63807463" type="audio/mpeg"/>
|
20
|
+
</item>
|
21
|
+
</channel>
|