feed2email 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f18bbfd4e8a09cb263f70c7966af0fa9e6602a3c
4
- data.tar.gz: fe00a0445e6010eca1d847861f33687beb7a8dc7
3
+ metadata.gz: 51069bce6917475aa8499384d330396eebde34c1
4
+ data.tar.gz: cde703782a959046c866aa459123d050e6df5fc2
5
5
  SHA512:
6
- metadata.gz: 8fddbfd1416024208e32e65ba16a5eeb51c3e6537d6b39d0e49ba945ae8ab5c9bf9c8c14f3e976b173da9f06976e15b54ebc18ac73c62250727f2e3a58eafa1b
7
- data.tar.gz: 0f1cbbdcba1768b9f75ad29c21b3c5c4d17cd55446dbaa1aed49fc72d9b63ec58a3c333dd1d8a0001e31f9cc6555ac106a8c40db764bcc127b004a2e0605f69c
6
+ metadata.gz: 702e5ab54cd401d097b572bfdea2acfc48af045382ddc988135a1f93d8b100790c950b1cf1c32a0405feab4ae8d746c289fffd3ed328ff6974e2418eef87f383
7
+ data.tar.gz: 271c7c5d97acbc461dcdf24ac481255d0ddd6a387535c8fb9dc1fef58fcb6783aed9235c87d7a089de6e625036e06ae6399ec2f3b861486af257c697bb526f0f
data/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ### 0.8.0
2
+
3
+ * Command-line interface for managing feeds (c20bf0c, e5c6dec)
4
+ * Perform feed autodiscovery in `add` command (46523ee)
5
+ * Store feed metadata in feed list, so no more feed files (cfbd725)
6
+ * Add `f2e` symlink to `feed2email` binary for running convenience (6add6b9)
7
+ * Improve send delay between entry processing (2118aef)
8
+ * Fix feed fetching exception handling (8eb68cf)
9
+ * Sync feed metadata only if all entries are processed (73f0947)
10
+ * Record entry to history only if email was sent (db59e77)
11
+ * Always fetch feed when permanently redirected (4fb147f)
12
+ * Ignore redirections to the same location (26513d1e)
13
+ * Major rewrite of README file with new instructions
14
+
1
15
  ### 0.7.0
2
16
 
3
17
  * Prevent simultaneous running instances
@@ -11,7 +25,7 @@
11
25
  * Render text/plain body as Markdown
12
26
  * Cache feed fetching with Last-Modified and ETag HTTP headers
13
27
  * Update feed URI on permanent redirect
14
- * Make sender a required config option
28
+ * Make `sender` a required config option
15
29
  * Maintain a separate history file per feed
16
30
 
17
31
  ### 0.5.0
data/README.md CHANGED
@@ -1,22 +1,25 @@
1
1
  # feed2email [![Gem Version](https://badge.fury.io/rb/feed2email.svg)](http://badge.fury.io/rb/feed2email)
2
2
 
3
- RSS/Atom feed updates in your email
4
-
5
- ## Why
6
-
7
- I don't like having a separate application for feeds when I'm already checking
8
- my email. I also never read a thing when feeds are kept in a separate place.
9
-
10
3
  feed2email is a [headless][] RSS/Atom feed aggregator that sends feed entries
11
- via email. It was written primarily as a replacement of [rss2email][] and aims
4
+ via email. It was initially written as a replacement of [rss2email][] and aims
12
5
  to be simple, fast and easy to use.
13
6
 
14
7
  [headless]: http://en.wikipedia.org/wiki/Headless_software
15
8
  [rss2email]: http://www.allthingsrss.com/rss2email/
16
9
 
10
+ ## Features
11
+
12
+ * Easy command-line feed management (add, remove, enable/disable)
13
+ * Feed fetching caching
14
+ * Feed autodiscovery
15
+ * Support for sending email with SMTP or a local MTA (e.g. sendmail)
16
+ * _text/html_ and _text/plain_ (Markdown) multipart emails
17
+ * Support for feed permanent redirections
18
+ * Auto-fixing relative feed entry permalinks
19
+
17
20
  ## Installation
18
21
 
19
- Install as a [gem][] from [RubyGems][]:
22
+ As a [gem][] from [RubyGems][]:
20
23
 
21
24
  ~~~ sh
22
25
  $ gem install feed2email
@@ -27,19 +30,24 @@ $ gem install feed2email
27
30
 
28
31
  ## Configuration
29
32
 
30
- Through a [YAML][] file at `~/.feed2email/config.yml`.
33
+ Through a [YAML][] file that you create at `~/.feed2email/config.yml`.
31
34
 
32
35
  [YAML]: http://en.wikipedia.org/wiki/YAML
33
36
 
34
37
  Each line in the configuration file contains a key-value pair. Each key-value
35
- pair is separated with a colon: `foo: bar`
38
+ pair is separated with a colon, e.g.: `foo: bar`
36
39
 
37
- ### Generic options
40
+ ### General options
38
41
 
39
42
  * `recipient` (required) is the email address to send email to
40
43
  * `sender` (required) is the email address to send email from (can be any)
41
44
  * `send_delay` (optional) is the number of seconds to wait between each email to
42
45
  avoid SMTP server throttling errors (default is `10`; use `0` to disable)
46
+ * `max_entries` (optional) is the maximum number of entries to process per feed
47
+ (default is `20`; use `0` for unlimited)
48
+
49
+ ### Logging options
50
+
43
51
  * `log_path` (optional) is the _absolute_ path to the log file (default is
44
52
  `true` which logs to standard output; use `false` to disable logging)
45
53
  * `log_level` (optional) is the logging verbosity level and can be `fatal`
@@ -50,15 +58,15 @@ pair is separated with a colon: `foo: bar`
50
58
  * `log_shift_size` (optional) is the maximum log file size in _megabytes_ and it
51
59
  only applies when `log_shift_age` is a number greater than zero (default is
52
60
  `1`)
53
- * `max_entries` (optional) is the maximum number of entries to process per feed
54
- (default is `20`; use `0` for unlimited)
61
+
62
+ ### Delivery options
55
63
 
56
64
  It is possible to send email via SMTP or an [MTA][] (default). If `config.yml`
57
65
  contains options for both, feed2email will use SMTP.
58
66
 
59
67
  [MTA]: http://en.wikipedia.org/wiki/Message_transfer_agent
60
68
 
61
- ### SMTP
69
+ #### SMTP
62
70
 
63
71
  For this method you need to have access to an SMTP service. [Mailgun][] has a
64
72
  free plan.
@@ -76,11 +84,11 @@ free plan.
76
84
  **Warning:** Unless it has correct restricted permissions, anyone with access in
77
85
  your system will be able to read `config.yml` and your password. To prevent
78
86
  this, feed2email will not run and complain if it detects the wrong permissions.
79
- To set the correct permissions, issue `chmod 600 ~/.feed2email/config.yml`
87
+ To set the correct permissions, issue `chmod 600 ~/.feed2email/config.yml`.
80
88
 
81
89
  [Mailgun]: http://www.mailgun.com/
82
90
 
83
- ### MTA
91
+ #### MTA
84
92
 
85
93
  For this method you need to have an [MTA][] with a [Sendmail][]-compatible
86
94
  interface set up and working in your system like [msmtp][] or [Postfix][].
@@ -96,68 +104,125 @@ interface set up and working in your system like [msmtp][] or [Postfix][].
96
104
 
97
105
  ### Managing feeds
98
106
 
99
- Create or edit `~/.feed2email/feeds.yml` and add the URL of the feed you want to
100
- subscribe to, prefixed with a dash and a space:
107
+ First, add some feeds:
108
+
109
+ ~~~ sh
110
+ $ feed2email add https://github.com/agorf.atom
111
+ Added feed https://github.com/agorf.atom at index 0
112
+ $ feed2email add https://github.com/agorf/feed2email/commits.atom
113
+ Added feed https://github.com/agorf/feed2email/commits.atom at index 1
114
+ ~~~
115
+
116
+ **Tip:** You only have to type a feed2email command until it is unambiguous e.g.
117
+ instead of `feed2email list`, you can simply issue `feed2email l` as long as
118
+ there is no other command beginning with an `l`.
101
119
 
102
- ~~~ yaml
103
- - https://github.com/agorf/feed2email/commits.atom
120
+ It is also possible to pass a website URL and let feed2email autodiscover any
121
+ feeds:
122
+
123
+ ~~~ sh
124
+ $ feed2email add http://www.rubyinside.com/
125
+ 0: http://www.rubyinside.com/feed/ "Ruby Inside" (application/rss+xml)
126
+ Please enter a feed to subscribe to: 0
127
+ Added feed http://www.rubyinside.com/feed/ at index 2
128
+ $ feed2email add http://thechangelog.com/137/
129
+ 0: http://thechangelog.com/137/feed/ "The Changelog » #137: Better GitHub Issues with HuBoard and Ryan Rauh Comments Feed" (application/rss+xml)
130
+ 1: http://thechangelog.com/feed/ "RSS 2.0 Feed" (application/rss+xml)
131
+ Please enter a feed to subscribe to: 1
132
+ Added feed http://thechangelog.com/feed/ at index 3
133
+ $ feed2email add http://thechangelog.com/137/
134
+ 0: http://thechangelog.com/137/feed/ "The Changelog » #137: Better GitHub Issues with HuBoard and Ryan Rauh Comments Feed" (application/rss+xml)
135
+ Please enter a feed to subscribe to: ^C
136
+ ~~~
137
+
138
+ Note that on the last command, feed2email autodiscovers the same two feeds as in
139
+ the second command, but only lists the ones that haven't been already added.
140
+ Autodiscovery is then cancelled by pressing `Ctrl-C`.
141
+
142
+ The feed list so far:
143
+
144
+ ~~~ sh
145
+ $ feed2email list
146
+ 0: https://github.com/agorf.atom
147
+ 1: https://github.com/agorf/feed2email/commits.atom
148
+ 2: http://www.rubyinside.com/feed/
149
+ 3: http://thechangelog.com/feed/
150
+ ~~~
151
+
152
+ To disable a feed so that it is not processed with `feed2email process`, toggle
153
+ it:
154
+
155
+ ~~~ sh
156
+ $ feed2email toggle 1
157
+ Disabled feed at index 1
158
+ $ feed2email list
159
+ 0: https://github.com/agorf.atom
160
+ 1: DISABLED https://github.com/agorf/feed2email/commits.atom
161
+ 2: http://www.rubyinside.com/feed/
162
+ 3: http://thechangelog.com/feed/
104
163
  ~~~
105
164
 
106
- To disable a feed, comment its line by prefixing it with a hash symbol:
165
+ It is also possible to remove it from the list:
107
166
 
108
- ~~~ yaml
109
- #- https://github.com/agorf/feed2email/commits.atom
167
+ ~~~ sh
168
+ $ feed2email remove 1
169
+ Removed feed at index 1
170
+ Warning: Feed list indices have changed!
110
171
  ~~~
111
172
 
112
- ### Running for the first time
173
+ It has been removed, but what is that weird warning?
113
174
 
114
- When feed2email runs for the first time or after adding a new feed:
175
+ Since the feed that got removed was at index 1, every feed below it got
176
+ reindexed. So feed2email warns you that the feed indices have changed: the feed
177
+ at index 2 is now at index 1 and the feed at index 3 is now at index 2.
115
178
 
116
- * All feed entries are skipped (no email sent)
117
- * `~/.feed2email/history-<digest>.yml` is created for each feed containing these
118
- (old) entries, where `<digest>` is the MD5 hex digest of the feed URL
179
+ Indeed:
119
180
 
120
- **Warning:** Versions prior to 0.6.0 used a single history file for all feeds.
121
- Before using version 0.6.0 for the first time, please make sure you run the
122
- provided migration script: `feed2email-migrate-history` If you don't, feed2email
123
- will think it's run for the first time and will treat all entries as old (thus
124
- no email will be sent and you may miss some entries).
181
+ ~~~ sh
182
+ $ feed2email list
183
+ 0: https://github.com/agorf.atom
184
+ 1: http://www.rubyinside.com/feed/
185
+ 2: http://thechangelog.com/feed/
186
+ ~~~
125
187
 
126
- ### Receiving specific entries from a feed
188
+ **Tip:** feed2email installs `f2e` as a symbolic link to the feed2email binary,
189
+ so you can use that to avoid typing the whole name every time, e.g.: `f2e list`
190
+ or even `f2e l`.
127
191
 
128
- 1. Add the feed URL to `~/.feed2email/feeds.yml`
129
- 1. Run feed2email once so that the feed's history file is generated
130
- 1. Remove the entries you want to receive from the feed's history (i.e. with
131
- your text editor)
132
- 1. Remove the feed's meta file (`meta-<digest>.yml`, where `<digest>` is the MD5
133
- hex digest of the feed URL) to bust feed fetching caching
192
+ ### Running
134
193
 
135
- Next time feed2email runs, these entries will be treated as new and will be
136
- processed (sent as email).
194
+ ~~~ sh
195
+ $ feed2email process
196
+ ~~~
137
197
 
138
- ### Permanent redirections
198
+ When run, feed2email will go through your feed list, fetch each feed (if
199
+ necessary) and send an email for each new entry. Any output is logged to the
200
+ standard output, unless configured otherwise.
139
201
 
140
- Before processing each feed, feed2email issues a [HEAD request][] to check
141
- whether it has been permanently moved by looking for a _301 Moved Permanently_
142
- HTTP status and its respective _Location_ header. In such case, feed2email
143
- updates `~/.feed2email/feeds.yml` with the new location and all feed entries are
144
- skipped (no email sent). If you do want to have some of them sent as email, see
145
- [Receiving specific entries from a feed](#receiving-specific-entries-from-a-feed).
202
+ **Warning:** Prior to version 0.8.0 where a command-line interface was
203
+ introduced, the way to run feed2email was simply `feed2email`. Now this will
204
+ just print helpful text on how to use it.
146
205
 
147
- [HEAD request]: http://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods
206
+ When a new feed is detected (which is the case when feed2email runs for the
207
+ first time on your feed list), all of its entries are skipped and no email is
208
+ sent. This is so that you don't get spammed when you add a feed for the first
209
+ time.
148
210
 
149
- ### Feed caching
211
+ If you want to receive a specific entry from a newly added feed, edit the feed's
212
+ history file with `feed2email history` and remove the entry. Then issue
213
+ `feed2email fetch` to clear the feed's fetch cache. Next time
214
+ `feed2email process` runs, the entry will be treated as new and will be
215
+ processed.
150
216
 
151
- feed2email caches fetched feeds with the _Last-Modified_ and _Etag_ HTTP
152
- headers. If you want to force a feed to be fetched, remove the feed's meta file
153
- (`~/.feed2email/meta-<digest>.yml`, where `<digest>` is the MD5 hex digest of
154
- the feed URL). Next time feed2email runs, the feed will be fetched.
217
+ ### Getting help
155
218
 
156
- ### Automating
219
+ Issue `feed2email` or `feed2email help` at any point to get helpful text on how
220
+ to use feed2email.
157
221
 
158
- You can use [cron][] to run feed2email automatically e.g. once every hour.
222
+ ## Contributing
159
223
 
160
- [cron]: http://en.wikipedia.org/wiki/Cron
224
+ Using feed2email and want to help? Just [contact me](http://agorf.gr/) and
225
+ describe how you use it and if you have any ideas on how it can be improved.
161
226
 
162
227
  ## License
163
228
 
data/bin/f2e ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ if File.new('/tmp/feed2email.lock', 'w').flock(File::LOCK_NB | File::LOCK_EX)
4
+ require 'feed2email/cli'
5
+ Feed2Email::Cli.start(ARGV)
6
+ else
7
+ abort 'An instance of feed2email is already running. Exiting...'
8
+ end
9
+
data/bin/feed2email CHANGED
@@ -1,11 +1,9 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'feed2email'
4
- require 'feed2email/feed'
5
-
6
3
  if File.new('/tmp/feed2email.lock', 'w').flock(File::LOCK_NB | File::LOCK_EX)
7
- Feed2Email::Feed.process_all
4
+ require 'feed2email/cli'
5
+ Feed2Email::Cli.start(ARGV)
8
6
  else
9
- $stderr.puts 'An instance of feed2email is already running. Exiting...'
10
- exit 1
7
+ abort 'An instance of feed2email is already running. Exiting...'
11
8
  end
9
+
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'yaml'
4
+
5
+ CONFIG_DIR = File.expand_path('~/.feed2email')
6
+ FEEDS_FILE = File.join(CONFIG_DIR, 'feeds.yml')
7
+
8
+ if !File.exist?(FEEDS_FILE)
9
+ $stderr.puts "Missing feed list file #{FEEDS_FILE}"
10
+ exit 1
11
+ end
12
+
13
+ feeds_data = YAML.load(open(FEEDS_FILE))
14
+
15
+ unless feeds_data.is_a?(Array) && feeds_data.all? {|data| data.is_a?(String) }
16
+ $stderr.puts "Invalid data type for feed list file #{FEEDS_FILE}"
17
+ exit 2
18
+ end
19
+
20
+ File.rename(FEEDS_FILE, "#{FEEDS_FILE}.bak")
21
+
22
+ begin
23
+ open(FEEDS_FILE, 'w') do |f|
24
+ f.write(
25
+ feeds_data.map {|uri|
26
+ { uri: uri, enabled: true }
27
+ }.to_yaml
28
+ )
29
+ end
30
+
31
+ puts "Migrated feed list file #{FEEDS_FILE}"
32
+ puts "Renamed #{FEEDS_FILE} to #{FEEDS_FILE}.bak"
33
+ rescue
34
+ File.rename("#{FEEDS_FILE}.bak", FEEDS_FILE)
35
+ raise
36
+ end
data/lib/feed2email.rb CHANGED
@@ -1,22 +1,27 @@
1
1
  require 'feed2email/config'
2
+ require 'feed2email/feed_list'
2
3
  require 'feed2email/lazy_smtp_connection'
3
4
  require 'feed2email/logger'
4
5
 
5
6
  module Feed2Email
6
- CONFIG_DIR = File.expand_path('~/.feed2email')
7
+ CONFIG_DIR = File.join(ENV['HOME'], '.feed2email')
7
8
 
8
- def self.config; @config end
9
-
10
- def self.logger
11
- @logger.logger # delegate
9
+ def self.config
10
+ @config ||= Config.new(File.join(CONFIG_DIR, 'config.yml'))
12
11
  end
13
12
 
14
- def self.smtp_connection; @smtp_connection end
15
-
16
- @config = Config.new(File.join(CONFIG_DIR, 'config.yml'))
13
+ def self.feed_list
14
+ @feed_list ||= FeedList.new(File.join(CONFIG_DIR, 'feeds.yml'))
15
+ end
17
16
 
18
- @logger = Logger.new(config['log_path'], config['log_level'],
19
- config['log_shift_age'], config['log_shift_size'])
17
+ def self.logger
18
+ @logger ||= Logger.new(
19
+ config['log_path'], config['log_level'], config['log_shift_age'],
20
+ config['log_shift_size']
21
+ ).logger
22
+ end
20
23
 
21
- @smtp_connection = LazySMTPConnection.new
24
+ def self.smtp_connection
25
+ @smtp_connection ||= LazySMTPConnection.new
26
+ end
22
27
  end
@@ -0,0 +1,158 @@
1
+ require 'thor'
2
+ require 'feed2email'
3
+ require 'feed2email/feed_autodiscoverer'
4
+ require 'feed2email/redirection_checker'
5
+
6
+ module Feed2Email
7
+ class Cli < Thor
8
+ desc 'add URL', 'subscribe to feed at URL'
9
+ def add(uri)
10
+ uri = handle_permanent_redirection(uri)
11
+ uri = perform_feed_autodiscovery(uri)
12
+
13
+ begin
14
+ feed_list << uri
15
+ rescue FeedList::DuplicateFeedError => e
16
+ abort e.message
17
+ end
18
+
19
+ if feed_list.sync
20
+ puts "Added feed #{uri} at index #{feed_list.size - 1}"
21
+ else
22
+ abort 'Failed to add feed'
23
+ end
24
+ end
25
+
26
+ desc 'fetch FEED', 'clear fetch cache for feed at index FEED'
27
+ def fetch(index)
28
+ index = check_feed_index(index, in: (0...feed_list.size))
29
+ feed_list.clear_fetch_cache(index)
30
+
31
+ if feed_list.sync
32
+ puts "Cleared fetch cache for feed at index #{index}"
33
+ else
34
+ abort "Failed to clear fetch cache for feed at index #{index}"
35
+ end
36
+ end
37
+
38
+ desc 'history FEED', 'edit history file of feed at index FEED with $EDITOR'
39
+ def history(index)
40
+ abort '$EDITOR not set' unless ENV['EDITOR']
41
+
42
+ index = check_feed_index(index, in: (0...feed_list.size))
43
+ require 'feed2email/feed_history'
44
+ history_path = FeedHistory.new(feed_list[index][:uri]).path
45
+ exec(ENV['EDITOR'], history_path)
46
+ end
47
+
48
+ desc 'remove FEED', 'unsubscribe from feed at index FEED'
49
+ def remove(index)
50
+ index = check_feed_index(index, in: (0...feed_list.size))
51
+ deleted = feed_list.delete_at(index)
52
+
53
+ if deleted && feed_list.sync
54
+ puts "Removed feed at index #{index}"
55
+
56
+ if feed_list.size != index # feed was not the last
57
+ puts 'Warning: Feed list indices have changed!'
58
+ end
59
+ else
60
+ abort "Failed to remove feed at index #{index}"
61
+ end
62
+ end
63
+
64
+ desc 'toggle FEED', 'enable/disable feed at index FEED'
65
+ def toggle(index)
66
+ index = check_feed_index(index, in: (0...feed_list.size))
67
+ toggled = feed_list.toggle(index)
68
+ enabled = feed_list[index][:enabled]
69
+
70
+ if toggled && feed_list.sync
71
+ puts "#{enabled ? 'En' : 'Dis'}abled feed at index #{index}"
72
+ else
73
+ abort "Failed to #{enabled ? 'en' : 'dis'}able feed at index #{index}"
74
+ end
75
+ end
76
+
77
+ desc 'list', 'list feed subscriptions'
78
+ def list
79
+ puts feed_list
80
+ end
81
+
82
+ desc 'process', 'process feed subscriptions'
83
+ def process
84
+ feed_list.process
85
+ end
86
+
87
+ desc 'version', 'show feed2email version'
88
+ def version
89
+ require 'feed2email/version'
90
+ puts "feed2email #{Feed2Email::VERSION}"
91
+ end
92
+
93
+ no_commands do
94
+ def check_feed_index(index, options = {})
95
+ if index.to_i.to_s != index ||
96
+ (options[:in] && !options[:in].include?(index.to_i))
97
+ puts if index.nil? # Ctrl-D
98
+ abort 'Invalid index'
99
+ end
100
+
101
+ index.to_i
102
+ end
103
+
104
+ def feed_list
105
+ Feed2Email.feed_list # delegate
106
+ end
107
+
108
+ def handle_permanent_redirection(uri)
109
+ checker = RedirectionChecker.new(uri)
110
+
111
+ if checker.permanently_redirected?
112
+ puts "Got permanently redirected to #{checker.location}"
113
+ checker.location
114
+ else
115
+ uri
116
+ end
117
+ end
118
+
119
+ def perform_feed_autodiscovery(uri)
120
+ discoverer = FeedAutodiscoverer.new(uri)
121
+
122
+ discovered_feeds = discoverer.feeds.reject {|feed|
123
+ feed_list.include?(feed[:uri])
124
+ }
125
+
126
+ if discovered_feeds.empty?
127
+ if discoverer.content_type == 'text/html'
128
+ puts 'Could not find any new feeds'
129
+ exit
130
+ end
131
+
132
+ return uri
133
+ end
134
+
135
+ justify = discovered_feeds.size.to_s.size
136
+
137
+ discovered_feeds.each_with_index do |feed, i|
138
+ puts '%{index}: %{uri} %{title}(%{content_type})' % {
139
+ index: i.to_s.rjust(justify),
140
+ uri: feed[:uri],
141
+ title: feed[:title] ? "\"#{feed[:title]}\" " : '',
142
+ content_type: feed[:content_type]
143
+ }
144
+ end
145
+
146
+ begin
147
+ response = ask('Please enter a feed to subscribe to:')
148
+ rescue Interrupt # Ctrl-C
149
+ puts
150
+ exit
151
+ end
152
+
153
+ index = check_feed_index(response, in: (0...discovered_feeds.size))
154
+ discovered_feeds[index][:uri]
155
+ end
156
+ end
157
+ end
158
+ end