correole 0.0.1 → 0.0.2
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.
- checksums.yaml +4 -4
- data/config/configuration.rb +70 -0
- data/config/database.yml +10 -0
- data/config/dependencies.rb +12 -0
- data/config/example.config.yml +18 -0
- data/config/production.html.erb +21 -0
- data/config/production.txt.erb +13 -0
- data/config/test.config.yml +15 -0
- data/config/test.html.erb +21 -0
- data/config/test.txt.erb +13 -0
- data/db/migrate/0001_create_database.rb +9 -0
- data/db/migrate/0002_create_items.rb +12 -0
- data/lib/correole/api.rb +118 -0
- data/lib/correole/feed.rb +37 -0
- data/lib/correole/item.rb +17 -0
- data/lib/correole/purge.rb +18 -0
- data/lib/correole/qputs.rb +3 -0
- data/lib/correole/send.rb +81 -0
- data/lib/correole/subscriber.rb +3 -0
- data/lib/correole/version.rb +4 -0
- metadata +21 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fa914631ebd1483e1e4ec3f2e72b2e6ad8b0842c
|
4
|
+
data.tar.gz: 48491bc95bdfaaf363f337dfce859af9310225a6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5ff71315753d357a1db73ca235e18d1f58c1c443e0b8f543439c00ba0210eedfb8ad5a9567fb492d362d8639273d2a2b21eec5a6712f2e87f2a6330edc4149ca
|
7
|
+
data.tar.gz: d2c0e6bbeb236291e188e6e14009cb0fb9668b50c3b0f09303239efb52a5ed76c90ccb9adb216e755ba26fc29fe121d25d799299f24de2a1ccdbb948c82c6058
|
@@ -0,0 +1,70 @@
|
|
1
|
+
DEFAULT_ENV = 'production'
|
2
|
+
DEFAULT_CONFIG_FILE = 'config.yml'
|
3
|
+
|
4
|
+
ENV['RACK_ENV'] ||= DEFAULT_ENV
|
5
|
+
ENV['CONFIG_FILE'] ||= DEFAULT_CONFIG_FILE
|
6
|
+
|
7
|
+
class Configuration
|
8
|
+
|
9
|
+
CONFIG_KEYS = [
|
10
|
+
'QUIET',
|
11
|
+
'FEED',
|
12
|
+
'CONFIRMATION_URI',
|
13
|
+
'BASE_URI',
|
14
|
+
'SUBJECT',
|
15
|
+
'FROM',
|
16
|
+
'HTML_TEMPLATE',
|
17
|
+
'PLAIN_TEMPLATE',
|
18
|
+
'SMTP_HOST',
|
19
|
+
'SMTP_PORT',
|
20
|
+
'SMTP_USER',
|
21
|
+
'SMTP_PASS',
|
22
|
+
'SMTP_AUTH',
|
23
|
+
'SMTP_TTLS' ]
|
24
|
+
|
25
|
+
class << self
|
26
|
+
CONFIG_KEYS.each do |k|
|
27
|
+
attr_accessor k.downcase.to_sym
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.load!
|
32
|
+
|
33
|
+
config_file = File.expand_path "../#{ENV['CONFIG_FILE']}", __FILE__
|
34
|
+
YAML.load_file(config_file)[ENV['RACK_ENV']].each_pair do |k, v|
|
35
|
+
ENV[k.upcase] ||= v.to_s rescue abort "Cannot load configuration key #{k}."
|
36
|
+
end rescue qputs "Could not load configuration file #{config_file}."
|
37
|
+
|
38
|
+
CONFIG_KEYS.each do |k|
|
39
|
+
case k
|
40
|
+
when 'QUIET', 'SMTP_TTLS'
|
41
|
+
# Cannot store boolean values in ENV, thus this.
|
42
|
+
self.send("#{k.downcase}=".to_sym, ENV[k] == 'true')
|
43
|
+
when 'HTML_TEMPLATE', 'PLAIN_TEMPLATE'
|
44
|
+
file = File.expand_path "../#{ENV[k]}", __FILE__
|
45
|
+
template = File.read file rescue abort "Cannot load template #{ENV[k]}."
|
46
|
+
self.send("#{k.downcase}=".to_sym, template)
|
47
|
+
when 'SMTP_USER', 'SMTP_PASS'
|
48
|
+
# For user name and password, Mail interprets '' as an input.
|
49
|
+
# It doesn't do the same with nil.
|
50
|
+
self.send("#{k.downcase}=".to_sym, ENV[k] == '' ? nil : ENV[k])
|
51
|
+
else
|
52
|
+
self.send("#{k.downcase}=".to_sym, ENV[k])
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
Mail.defaults do
|
57
|
+
delivery_method :smtp,
|
58
|
+
address: Configuration.smtp_host,
|
59
|
+
port: Configuration.smtp_port,
|
60
|
+
user_name: Configuration.smtp_user,
|
61
|
+
password: Configuration.smtp_pass,
|
62
|
+
authentication: Configuration.smtp_auth,
|
63
|
+
enable_starttls_auto: Configuration.smtp_ttls
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
Configuration.load!
|
data/config/database.yml
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'sinatra/activerecord'
|
3
|
+
require 'correole/qputs'
|
4
|
+
require 'correole/subscriber'
|
5
|
+
require 'correole/item'
|
6
|
+
require 'correole/feed'
|
7
|
+
require 'correole/api'
|
8
|
+
require 'correole/send'
|
9
|
+
require 'correole/purge'
|
10
|
+
require 'net/http'
|
11
|
+
require 'mail'
|
12
|
+
require 'configuration'
|
@@ -0,0 +1,18 @@
|
|
1
|
+
production: &prod
|
2
|
+
quiet: false
|
3
|
+
base_uri: http://newsletter.ruslanledesma.com
|
4
|
+
feed: http://ruslanledesma.com/feed.xml
|
5
|
+
confirmation_uri: http://ruslanledesma.com/unsubscribed/
|
6
|
+
subject: '<%= title %>: newsletter for <%= date %>'
|
7
|
+
from: '<%= title %> <no-reply@ruslanledesma.com>'
|
8
|
+
html_template: production.html.erb
|
9
|
+
plain_template: production.txt.erb
|
10
|
+
smtp_host:
|
11
|
+
smtp_port:
|
12
|
+
smtp_user:
|
13
|
+
smtp_pass:
|
14
|
+
smtp_auth:
|
15
|
+
smtp_ttls:
|
16
|
+
|
17
|
+
development:
|
18
|
+
<<: *prod
|
@@ -0,0 +1,21 @@
|
|
1
|
+
<html>
|
2
|
+
<body>
|
3
|
+
<h1><%= title %></h1>
|
4
|
+
<h2>New posts</h2>
|
5
|
+
<ul>
|
6
|
+
<% for item in unsent_items %>
|
7
|
+
<li>
|
8
|
+
<h3>
|
9
|
+
<a href="<%= item.link %>"><%= item.title %></a>
|
10
|
+
</h3>
|
11
|
+
<% if item.pub_date -%>
|
12
|
+
<p><i><%= item.pub_date.to_date %></i></p>
|
13
|
+
<% end -%>
|
14
|
+
<p><%= item.description %></p>
|
15
|
+
</li>
|
16
|
+
<% end %>
|
17
|
+
</ul>
|
18
|
+
|
19
|
+
<a href="<%= unsubscribe_uri %>">Unsubscribe here.</a>
|
20
|
+
</body>
|
21
|
+
</html>
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<%= title %>
|
2
|
+
|
3
|
+
New posts
|
4
|
+
<% for item in unsent_items %>
|
5
|
+
- <%= item.title %>
|
6
|
+
<% if item.pub_date -%>
|
7
|
+
<%= item.pub_date.to_date %>
|
8
|
+
<% end -%>
|
9
|
+
<%= item.link %>
|
10
|
+
|
11
|
+
<%= item.description %>
|
12
|
+
<% end %>
|
13
|
+
Unsubscribe here: <%= unsubscribe_uri %>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
test:
|
2
|
+
quiet: true
|
3
|
+
base_uri: http://test.ruslanledesma.com
|
4
|
+
feed: http://ruslanledesma.com/feed.xml # reset by env for end-to-end test
|
5
|
+
confirmation_uri: http://newsletter.ruslanledesma.com
|
6
|
+
subject: 'Test <%= title %> - <%= date %>'
|
7
|
+
from: '<%= title %> <no-reply@ruslanledesma.com>'
|
8
|
+
html_template: test.html.erb
|
9
|
+
plain_template: test.txt.erb
|
10
|
+
smtp_host: localhost # reset by env for end-to-end test
|
11
|
+
smtp_port: 25 # reset by env for end-to-end test
|
12
|
+
smtp_user:
|
13
|
+
smtp_pass:
|
14
|
+
smtp_auth:
|
15
|
+
smtp_ttls: false
|
@@ -0,0 +1,21 @@
|
|
1
|
+
<html>
|
2
|
+
<body>
|
3
|
+
<h1><%= title %></h1>
|
4
|
+
<h2>Items</h2>
|
5
|
+
<ul>
|
6
|
+
<% for item in unsent_items %>
|
7
|
+
<li>
|
8
|
+
<% if item.pub_date -%>
|
9
|
+
<i><%= item.pub_date.to_date %></i>
|
10
|
+
<% end -%>
|
11
|
+
<h3>
|
12
|
+
<a href="<%= item.link %>"><%= item.title %></a>
|
13
|
+
</h3>
|
14
|
+
<p><%= item.description %></p>
|
15
|
+
</li>
|
16
|
+
<% end %>
|
17
|
+
</ul>
|
18
|
+
|
19
|
+
<a href="<%= unsubscribe_uri %>">Unsubscribe here.</a>
|
20
|
+
</body>
|
21
|
+
</html>
|
data/config/test.txt.erb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
class CreateItems < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :items do |t|
|
4
|
+
t.string :title, null: false
|
5
|
+
t.string :description, null: false
|
6
|
+
t.string :link, null: false
|
7
|
+
t.timestamp :pub_date, null: true
|
8
|
+
t.index :link, unique: true
|
9
|
+
t.timestamps null: false
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/correole/api.rb
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
class Api < Sinatra::Base
|
2
|
+
|
3
|
+
UNSUBSCRIBE_PATH = '/unsubscribe'
|
4
|
+
SUBSCRIBERS_ALLOWED_METHODS = 'PUT, DELETE, OPTIONS'
|
5
|
+
SUBSCRIBERS_ALLOWED_ORIGIN = '*'
|
6
|
+
UNSUBSCRIBE_ALLOWED_METHODS = 'GET, OPTIONS'
|
7
|
+
UNSUBSCRIBE_ALLOWED_ORIGIN = '*'
|
8
|
+
|
9
|
+
set :server, :thin
|
10
|
+
enable :logging
|
11
|
+
disable :show_exceptions
|
12
|
+
use ActiveRecord::ConnectionAdapters::ConnectionManagement
|
13
|
+
|
14
|
+
before do
|
15
|
+
content_type 'text/plain'
|
16
|
+
end
|
17
|
+
|
18
|
+
def subscribe(params)
|
19
|
+
response.headers['Access-Control-Allow-Origin'] = SUBSCRIBERS_ALLOWED_ORIGIN
|
20
|
+
s = Subscriber.new(email: params[:email])
|
21
|
+
return 400 if not s.valid?
|
22
|
+
begin
|
23
|
+
s.save
|
24
|
+
logger.info("Subscribed #{params[:email]}.")
|
25
|
+
rescue ActiveRecord::RecordNotUnique
|
26
|
+
logger.info("Already subscribed #{params[:email]}.")
|
27
|
+
Subscriber.find_by_email(params[:email]).touch
|
28
|
+
end
|
29
|
+
"#{params[:email]}\n"
|
30
|
+
end
|
31
|
+
|
32
|
+
def unsubscribe(params)
|
33
|
+
response.headers['Access-Control-Allow-Origin'] = UNSUBSCRIBE_ALLOWED_ORIGIN
|
34
|
+
s = Subscriber.new(email: params[:email])
|
35
|
+
return 400 if not s.valid?
|
36
|
+
s = Subscriber.find_by_email(params[:email])
|
37
|
+
if s != nil
|
38
|
+
s.delete
|
39
|
+
logger.info("Unsubscribed #{params[:email]}.")
|
40
|
+
else
|
41
|
+
logger.info("Tried to unsubscribe #{params[:email]} but address is not subscribed.")
|
42
|
+
end
|
43
|
+
"#{params[:email]}\n"
|
44
|
+
end
|
45
|
+
|
46
|
+
def subscribers_method_not_allowed
|
47
|
+
response.headers['Access-Control-Allow-Methods'] = SUBSCRIBERS_ALLOWED_METHODS
|
48
|
+
405
|
49
|
+
end
|
50
|
+
|
51
|
+
def unsubscribe_method_not_allowed
|
52
|
+
response.headers['Access-Control-Allow-Methods'] = UNSUBSCRIBE_ALLOWED_METHODS
|
53
|
+
405
|
54
|
+
end
|
55
|
+
|
56
|
+
options '/subscribers/:email' do
|
57
|
+
response.headers['Access-Control-Allow-Methods'] = SUBSCRIBERS_ALLOWED_METHODS
|
58
|
+
response.headers['Access-Control-Allow-Origin'] = SUBSCRIBERS_ALLOWED_ORIGIN
|
59
|
+
200
|
60
|
+
end
|
61
|
+
|
62
|
+
put '/subscribers/:email' do
|
63
|
+
subscribe(params)
|
64
|
+
end
|
65
|
+
|
66
|
+
delete '/subscribers/:email' do
|
67
|
+
unsubscribe(params)
|
68
|
+
end
|
69
|
+
|
70
|
+
[ :get,
|
71
|
+
:post,
|
72
|
+
:patch
|
73
|
+
].each do |verb|
|
74
|
+
send verb, '/subscribers/:email' do
|
75
|
+
subscribers_method_not_allowed
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
options "#{UNSUBSCRIBE_PATH}/:email" do
|
80
|
+
response.headers['Access-Control-Allow-Methods'] = UNSUBSCRIBE_ALLOWED_METHODS
|
81
|
+
response.headers['Access-Control-Allow-Origin'] = UNSUBSCRIBE_ALLOWED_ORIGIN
|
82
|
+
200
|
83
|
+
end
|
84
|
+
|
85
|
+
get "#{UNSUBSCRIBE_PATH}/:email" do
|
86
|
+
r = unsubscribe(params)
|
87
|
+
return r if r.is_a? Integer
|
88
|
+
response.headers['Location'] = Configuration.confirmation_uri
|
89
|
+
[302, r]
|
90
|
+
end
|
91
|
+
|
92
|
+
[ :put,
|
93
|
+
:delete,
|
94
|
+
:post,
|
95
|
+
:patch
|
96
|
+
].each do |verb|
|
97
|
+
send verb, "#{UNSUBSCRIBE_PATH}/:email" do
|
98
|
+
unsubscribe_method_not_allowed
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
not_found do
|
103
|
+
[404, "Not found\n"]
|
104
|
+
end
|
105
|
+
|
106
|
+
error 400 do
|
107
|
+
[400, "Bad request\n"]
|
108
|
+
end
|
109
|
+
|
110
|
+
error 405 do
|
111
|
+
[405, "Method not allowed\n"]
|
112
|
+
end
|
113
|
+
|
114
|
+
error 500 do
|
115
|
+
[500, "Internal server error\n"]
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class Feed
|
2
|
+
|
3
|
+
def self.get
|
4
|
+
uri = URI Configuration.feed
|
5
|
+
xml = Net::HTTP.get uri
|
6
|
+
hash = Hash.from_xml xml
|
7
|
+
return {
|
8
|
+
:title => hash['rss']['channel']['title'],
|
9
|
+
:item => hash['rss']['channel']['item'].map { |i|
|
10
|
+
pub_date = nil
|
11
|
+
pub_date = Time.parse(i['pubDate']) if i.has_key? 'pubDate'
|
12
|
+
Item.new(title: i['title'],
|
13
|
+
description: i['description'],
|
14
|
+
link: i['link'],
|
15
|
+
pub_date: pub_date)
|
16
|
+
}
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.split_items(feed)
|
21
|
+
split_feed = {
|
22
|
+
:title => feed[:title],
|
23
|
+
:unsent_item => [],
|
24
|
+
:sent_item => []
|
25
|
+
}
|
26
|
+
feed[:item].each do |i|
|
27
|
+
if Item.where(:link => i.link).any?
|
28
|
+
split_feed[:sent_item] << i
|
29
|
+
else
|
30
|
+
split_feed[:unsent_item] << i
|
31
|
+
end
|
32
|
+
end
|
33
|
+
return split_feed
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Item < ActiveRecord::Base
|
2
|
+
validates :title, presence: true
|
3
|
+
validates :description, presence: true
|
4
|
+
validates :link, presence: true, format: /http.+/
|
5
|
+
|
6
|
+
def ==(o)
|
7
|
+
return o.class == self.class &&
|
8
|
+
o.title == self.title &&
|
9
|
+
o.description == self.description &&
|
10
|
+
o.link == self.link &&
|
11
|
+
o.pub_date == self.pub_date &&
|
12
|
+
o.id == self.id &&
|
13
|
+
o.created_at == self.created_at &&
|
14
|
+
o.updated_at == self.updated_at
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class Purge
|
2
|
+
|
3
|
+
def self.run!
|
4
|
+
qputs "Fetch feed from #{Configuration.feed}."
|
5
|
+
feed = Feed.get
|
6
|
+
unsent_items = Feed.split_items(feed)[:unsent_item]
|
7
|
+
if unsent_items.empty?
|
8
|
+
qputs 'There are no new items, exiting.'
|
9
|
+
return
|
10
|
+
end
|
11
|
+
qputs "There are #{unsent_items.length} new items. The items are the following."
|
12
|
+
unsent_items.each_with_index { |i, j| qputs "[#{j+1}] #{i.link}" }
|
13
|
+
qputs 'Purge the new items by remembering them.'
|
14
|
+
unsent_items.each { |i| i.save }
|
15
|
+
qputs 'Done.'
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
class Send
|
2
|
+
|
3
|
+
def self.run!
|
4
|
+
qputs "Fetch feed from #{Configuration.feed}."
|
5
|
+
feed = Feed.get
|
6
|
+
split_feed = Feed.split_items feed
|
7
|
+
if split_feed[:unsent_item].empty?
|
8
|
+
qputs 'There are no new items, exiting.'
|
9
|
+
return
|
10
|
+
end
|
11
|
+
qputs "There are #{split_feed[:unsent_item].length} new items. The items are the following."
|
12
|
+
split_feed[:unsent_item].each_with_index { |i, j| qputs "[#{j+1}] #{i.link}" }
|
13
|
+
html = compose_html split_feed
|
14
|
+
plain = compose_plain split_feed
|
15
|
+
count = Subscriber.count
|
16
|
+
Subscriber.find_each.with_index do |s, i|
|
17
|
+
html_s = personalize html, s.email
|
18
|
+
plain_s = personalize plain, s.email
|
19
|
+
qputs "[#{i+1}/#{count}] Send newsletter to #{s.email}."
|
20
|
+
begin
|
21
|
+
send_out feed[:title], html_s, plain_s, s.email
|
22
|
+
rescue => exc
|
23
|
+
qputs "Could not send newsletter to #{s.email} for the following reason."
|
24
|
+
qputs exc.message
|
25
|
+
end
|
26
|
+
end
|
27
|
+
qputs 'Remember new items.'
|
28
|
+
split_feed[:unsent_item].each { |i| i.save }
|
29
|
+
qputs 'Done.'
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def self.template_bindings(split_feed)
|
35
|
+
title = split_feed[:title]
|
36
|
+
unsent_items = split_feed[:unsent_item]
|
37
|
+
sent_items = split_feed[:sent_item]
|
38
|
+
unsubscribe_uri = nil # supress unused variable warning
|
39
|
+
unsubscribe_uri = "#{Configuration.base_uri}#{Api::UNSUBSCRIBE_PATH}/<%= recipient %>"
|
40
|
+
title = '' if !title.is_a?(String)
|
41
|
+
unsent_items = [] if !unsent_items.is_a?(Array)
|
42
|
+
sent_items = [] if !unsent_items.is_a?(Array)
|
43
|
+
return binding
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.compose_html(split_feed)
|
47
|
+
template = Configuration.html_template
|
48
|
+
bindings = template_bindings(split_feed)
|
49
|
+
return ERB.new(template, nil, '-').result(bindings)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.compose_plain(split_feed)
|
53
|
+
template = Configuration.plain_template
|
54
|
+
bindings = template_bindings(split_feed)
|
55
|
+
return ERB.new(template, nil, '-').result(bindings)
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.personalize(message, recipient)
|
59
|
+
return ERB.new(message).result(binding)
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.send_out(title, html, plain, recipient)
|
63
|
+
date = nil # supress unused variable warning
|
64
|
+
date = Date.today.strftime('%a, %d %b %Y')
|
65
|
+
Mail.deliver do
|
66
|
+
to recipient
|
67
|
+
from ERB.new(Configuration.from).result(binding)
|
68
|
+
subject ERB.new(Configuration.subject).result(binding)
|
69
|
+
|
70
|
+
text_part do
|
71
|
+
body plain
|
72
|
+
end
|
73
|
+
|
74
|
+
html_part do
|
75
|
+
content_type 'text/html; charset=UTF-8'
|
76
|
+
body html
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: correole
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ruslan Ledesma Garza
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-07-
|
11
|
+
date: 2016-07-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sinatra
|
@@ -202,6 +202,25 @@ extensions: []
|
|
202
202
|
extra_rdoc_files: []
|
203
203
|
files:
|
204
204
|
- bin/correole
|
205
|
+
- config/configuration.rb
|
206
|
+
- config/database.yml
|
207
|
+
- config/dependencies.rb
|
208
|
+
- config/example.config.yml
|
209
|
+
- config/production.html.erb
|
210
|
+
- config/production.txt.erb
|
211
|
+
- config/test.config.yml
|
212
|
+
- config/test.html.erb
|
213
|
+
- config/test.txt.erb
|
214
|
+
- db/migrate/0001_create_database.rb
|
215
|
+
- db/migrate/0002_create_items.rb
|
216
|
+
- lib/correole/api.rb
|
217
|
+
- lib/correole/feed.rb
|
218
|
+
- lib/correole/item.rb
|
219
|
+
- lib/correole/purge.rb
|
220
|
+
- lib/correole/qputs.rb
|
221
|
+
- lib/correole/send.rb
|
222
|
+
- lib/correole/subscriber.rb
|
223
|
+
- lib/correole/version.rb
|
205
224
|
homepage: http://ruslanledesma.com/
|
206
225
|
licenses:
|
207
226
|
- MIT
|