feedigest 0.1.0 → 0.2.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
  SHA256:
3
- metadata.gz: 401be82539f92da17495ec1bbdc38e9308ee2c1d2bf1d991dd2440f8c7ce9dfd
4
- data.tar.gz: 9ed6fdf1eb2ddfef7862cf3de2cf0052ed38d02eeb3f7a2e054692163234dc07
3
+ metadata.gz: 5e5e0608a5ec2045aa61335d068fe5acb0a8b620f850ff5e44933e3ce1564638
4
+ data.tar.gz: 6c450ac10c9017169004d91390eccb94c9581cf793ec5503525f09d91db728e2
5
5
  SHA512:
6
- metadata.gz: 52813c1c6d54b757a5173673d65d436ce5418222314ddac2ddeb7c9b811089e72efbf6a194bf55e0c761cb7ee8d45a7fd86db180c867c8fa98ed160ef3ebc483
7
- data.tar.gz: b8bb424b7a4fd0b7fcede8ccea5709b4ec88f666de8faf5fe8e5916123ae82c70e4f66a3c8960a677e06e6662bf8f3521b408fdef41da02762fcfb4c53544815
6
+ metadata.gz: 59dea377dc71be1dcbe0f4c162313262fe56ab3817837ce55cf35c01e2c1305f030941e4c3bd75fca7f175e16e4cfb95a16a1152de1a42a84eb686d2433c39ce
7
+ data.tar.gz: 68fc1ce197775bae4175ff2c9e030d260044742a3924de089880ee48b19d286b4df3dfae804a81c5ae6458ccd62d134886700b11f15357fb171508c04bc8b527
@@ -7,6 +7,20 @@ The format is based on [Keep a Changelog][] and this project adheres to
7
7
 
8
8
  ## [Unreleased][]
9
9
 
10
+ ## [0.2.0][] - 2018-07-18
11
+
12
+ ### Added
13
+
14
+ - Support filtering feeds through an external command.
15
+ - Opening informational text in digest email.
16
+ - Support for specifying path to YAML configuration file.
17
+ - Support for command-line options.
18
+
19
+ ### Changed
20
+
21
+ - Configuration from environment variables to a YAML file.
22
+ - `entry_window` config option unit from seconds to hours.
23
+
10
24
  ## [0.1.0][] - 2018-07-04
11
25
 
12
26
  ### Changed
@@ -50,4 +64,5 @@ Initial release.
50
64
  [0.0.2]: https://github.com/agorf/feedigest/compare/0.0.1...0.0.2
51
65
  [0.0.3]: https://github.com/agorf/feedigest/compare/0.0.2...0.0.3
52
66
  [0.1.0]: https://github.com/agorf/feedigest/compare/0.0.3...0.1.0
53
- [Unreleased]: https://github.com/agorf/feedigest/compare/0.1.0...HEAD
67
+ [0.2.0]: https://github.com/agorf/feedigest/compare/0.1.0...0.2.0
68
+ [Unreleased]: https://github.com/agorf/feedigest/compare/0.2.0...HEAD
data/README.md CHANGED
@@ -14,104 +14,136 @@ It was written as a simpler alternative to [feed2email][].
14
14
 
15
15
  As a [gem][] from [RubyGems][]:
16
16
 
17
- ~~~ sh
17
+ ```sh
18
18
  gem install feedigest
19
- ~~~
19
+ ```
20
20
 
21
21
  If the above command fails, make sure the following system packages are
22
22
  installed. For Debian/Ubuntu, issue as root:
23
23
 
24
- ~~~ sh
24
+ ```sh
25
25
  apt-get install build-essential patch ruby-dev zlib1g-dev liblzma-dev
26
- ~~~
26
+ ```
27
27
 
28
28
  [gem]: http://rubygems.org/gems/feedigest
29
29
  [RubyGems]: http://rubygems.org/
30
30
 
31
31
  ## Configuration
32
32
 
33
- feedigest is configured through shell environment variables. Instead of passing
34
- them one by one when you run it, you can store them in a separate file and
35
- source it to make them available:
33
+ feedigest is configured through a [YAML][] configuration file located under
34
+ `~/.feedigest/config.yaml`:
36
35
 
37
- ~~~ sh
38
- $ mkdir -p ~/.feedigest
39
- $ touch ~/.feedigest/env
40
- $ chmod 600 ~/.feedigest/env
41
- $ cat > ~/.feedigest/env
42
- FEEDIGEST_EMAIL_RECIPIENT=me@mydomain.com
43
- ...
44
- ^D
45
- ~~~
36
+ [YAML]: https://en.wikipedia.org/wiki/YAML
46
37
 
47
- **Note:** In the example above, `^D` stands for pressing `Ctrl-D`.
38
+ The following configuration options are supported:
48
39
 
49
- The following environment variables are supported:
50
-
51
- * `FEEDIGEST_ENTRY_WINDOW` (default: `86400`) the maximum age, in seconds, of
52
- entries to include in the digest
53
- * `FEEDIGEST_EMAIL_SENDER` (default: `feedigest@hostname`) the "from" address in
40
+ * `entry_window` (default: `24`) the maximum age, in hours, of entries to
41
+ include in the digest
42
+ * `email_sender` (default: `feedigest@hostname`) the "from" address in
54
43
  the email
55
- * `FEEDIGEST_EMAIL_RECIPIENT` the email address to send the email to
44
+ * `email_recipient` (required) the email address to send the email to
56
45
 
57
- feedigest uses SMTP to send emails. You can get a free plan from [Mailgun][].
58
- The relevant environment variables are:
46
+ feedigest uses SMTP to send emails ([Mailgun][] has a free plan). The relevant
47
+ configuration options are:
59
48
 
60
49
  [Mailgun]: http://www.mailgun.com/
61
50
 
62
- * `FEEDIGEST_SMTP_ADDRESS`
63
- * `FEEDIGEST_SMTP_PORT` (default: `587`)
64
- * `FEEDIGEST_SMTP_USERNAME`
65
- * `FEEDIGEST_SMTP_PASSWORD`
66
- * `FEEDIGEST_SMTP_AUTH` (default: `plain`)
67
- * `FEEDIGEST_SMTP_STARTTLS` (default: `true`)
51
+ * `smtp_address` (required) SMTP service address to connect to
52
+ * `smtp_port` (default: `587`) SMTP service port to connect to
53
+ * `smtp_username` (required) username of your email account
54
+ * `smtp_password` (required) password of your email account
55
+ * `smtp_auth` (default: `plain`) controls authentication method (can also be
56
+ `login` or `cram_md5`)
57
+ * `smtp_starttls` (default: `true`) controls use of STARTTLS
68
58
 
69
- Finally, you need to provide to the standard input (stdin) of feedigest, a
70
- line-separated list of feed URLs:
59
+ Finally, you will need a line-separated list of feed URLs:
71
60
 
72
- ~~~ sh
61
+ ```sh
73
62
  $ cat > ~/.feedigest/feeds.txt
74
63
  https://github.com/agorf/feed2email/commits.atom
75
64
  https://github.com/agorf.atom
76
65
  ...
77
66
  ^D
78
- ~~~
67
+ ```
68
+
69
+ **Note:** In the example above, `^D` stands for pressing `Ctrl-D`.
79
70
 
80
71
  ## Use
81
72
 
82
- ~~~ sh
83
- export $(cat ~/.feedigest/env | xargs) && feedigest send < ~/.feedigest/feeds.txt
84
- ~~~
73
+ ```sh
74
+ feedigest --feeds ~/.feedigest/feeds.txt
75
+ ```
85
76
 
86
77
  You can run this with [cron][] e.g. once per day at 10 am:
87
78
 
88
79
  [cron]: https://en.wikipedia.org/wiki/Cron
89
80
 
90
- ~~~
91
- 0 10 * * * export $(cat ~/.feedigest/env | xargs) && feedigest send < ~/.feedigest/feeds.txt
92
- ~~~
81
+ ```
82
+ 0 10 * * * feedigest --feeds ~/.feedigest/feeds.txt
83
+ ```
93
84
 
94
85
  Alternatively, you can have feedigest simply print the generated email so that
95
- you can send it yourself e.g. by piping it to sendmail. To do that, you simply
96
- replace `feedigest send` with `feedigest print`:
97
-
98
- ~~~ sh
99
- feedigest print < ~/.feedigest/feeds.txt
100
- ~~~
101
-
102
- ## Contributing
103
-
104
- Using feedigest and want to help? Please [let me know][contact] how you use it
105
- and if you have any ideas on how to improve it.
106
-
107
- [contact]: https://agorf.gr/contact/
86
+ you can send it yourself e.g. by piping it to sendmail:
87
+
88
+ ```sh
89
+ feedigest --dry-run --feeds ~/.feedigest/feeds.txt
90
+ ```
91
+
92
+ It is also possible to have each feed filtered by a custom command. For example,
93
+ the following script fixes a feed's entry publication dates that use Greek month
94
+ names and don't follow the required RFC822 format:
95
+
96
+ ```ruby
97
+ require 'nokogiri'
98
+
99
+ feed_data = $stdin.read
100
+ doc = Nokogiri.XML(feed_data)
101
+
102
+ case feed_data
103
+ when /advendure\.com/
104
+ doc.css('pubDate').each do |pubdate|
105
+ %w[
106
+ Ιαν Φεβ Μαρ Απρ Μαι Ιουν Ιουλ Αυγ Σεπ Οκτ Νοβ Δεκ
107
+ Δε Τρ Τε Πε Πα Σα Κυ
108
+ ].zip(
109
+ %w[
110
+ Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
111
+ Mo Tu We Th Fr Sa Su
112
+ ]
113
+ ).each do |greek, latin|
114
+ pubdate.content = pubdate.content.sub(greek, latin)
115
+ end
116
+ end
117
+
118
+ print doc
119
+ else
120
+ print feed_data # Do nothing
121
+ end
122
+ ```
123
+
124
+ The script reads the feed XML from its standard input (stdin) and writes the
125
+ modified XML to its standard output (stdout). To use it as a filter, you simply
126
+ pass as a command-line argument the necessary command to execute it:
127
+
128
+ ```sh
129
+ feedigest --filter 'ruby /path/to/filter.rb' --feeds ~/.feedigest/feeds.txt
130
+ ```
131
+
132
+ Finally, it is possible to specify the path to the YAML configuration file with
133
+ `--config`:
134
+
135
+ ```sh
136
+ feedigest --feeds ~/.feedigest/feeds.txt --config ~/.config/feedigest.yaml
137
+ ```
138
+
139
+ You can issue `feedigest -h` to get some help text on the supported options.
108
140
 
109
141
  ## License
110
142
 
111
- Licensed under the MIT license (see [LICENSE.txt][license]).
143
+ [MIT][]
112
144
 
113
- [license]: https://github.com/agorf/feedigest/blob/master/LICENSE.txt
145
+ [MIT]: https://github.com/agorf/feedigest/blob/master/LICENSE.txt
114
146
 
115
147
  ## Author
116
148
 
117
- Angelos Orfanakos, <https://agorf.gr/>
149
+ [Angelos Orfanakos](https://agorf.gr/contact/)
@@ -1,23 +1,21 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- if !%w[send print].include?(ARGV[0])
4
- puts "#$0 <send|print>"
5
- exit 1
6
- end
7
-
8
3
  require 'feedigest'
9
4
 
10
- puts 'Please provide a list of feed URLs (one per line) and press Ctrl-D'
5
+ options = Feedigest::OptionParser.new(ARGV).options
6
+
7
+ Feedigest.config_path = File.expand_path(options[:config])
11
8
 
12
- feed_urls = $stdin.readlines.map(&:strip)
13
- feeds = Feedigest::FeedFetcher.new(feed_urls).feeds
9
+ feed_urls = IO.readlines(options[:feeds]).map(&:strip)
10
+ feeds = Feedigest::FeedFetcher.new(feed_urls, options[:filter]).feeds
14
11
 
15
12
  exit if feeds.empty?
16
13
 
17
14
  mail = Feedigest::MailComposer.new(feeds).mail
18
15
  mail_sender = Feedigest::MailSender.new(mail)
19
16
 
20
- case ARGV[0]
21
- when 'send' then mail_sender.deliver
22
- when 'print' then puts mail_sender
17
+ if options[:dry_run]
18
+ puts mail_sender
19
+ else
20
+ mail_sender.deliver
23
21
  end
@@ -1,6 +1,18 @@
1
1
  module Feedigest; end
2
2
 
3
- require 'feedigest/options'
3
+ require 'feedigest/config'
4
+
5
+ module Feedigest
6
+ class << self
7
+ attr_accessor :config_path
8
+ end
9
+
10
+ def self.config
11
+ @config ||= Feedigest::Config.new(config_path)
12
+ end
13
+ end
14
+
15
+ require 'feedigest/option_parser'
4
16
  require 'feedigest/feed_fetcher'
5
17
  require 'feedigest/mail_composer'
6
18
  require 'feedigest/mail_sender'
@@ -0,0 +1,51 @@
1
+ require 'yaml'
2
+
3
+ class Feedigest::Config
4
+ attr_reader :path
5
+
6
+ def initialize(path)
7
+ @path = path
8
+ end
9
+
10
+ def fetch(key)
11
+ options.fetch(key) # Delegate
12
+ end
13
+
14
+ def options
15
+ return @options if @options
16
+
17
+ @options = default_options.merge(user_options)
18
+ @options[:smtp_starttls] = @options[:smtp_starttls] == 'true'
19
+
20
+ @options
21
+ end
22
+
23
+ # Translate SMTP options of feedigest for Mail gem
24
+ def mail_gem_smtp_options
25
+ {
26
+ address: options.fetch(:smtp_address),
27
+ port: options.fetch(:smtp_port),
28
+ user_name: options.fetch(:smtp_username),
29
+ password: options.fetch(:smtp_password),
30
+ authentication: options.fetch(:smtp_auth),
31
+ enable_starttls: options.fetch(:smtp_starttls)
32
+ }
33
+ end
34
+
35
+ private
36
+
37
+ def user_options
38
+ YAML.safe_load(File.read(path)).
39
+ map { |k, v| [k.to_sym, v] }.to_h # Symbolize keys
40
+ end
41
+
42
+ def default_options
43
+ {
44
+ entry_window: 24,
45
+ email_sender: "feedigest@#{`hostname`.strip}",
46
+ smtp_port: 587,
47
+ smtp_auth: 'plain',
48
+ smtp_starttls: 'true' # Gets converted to boolean
49
+ }
50
+ end
51
+ end
@@ -1,13 +1,17 @@
1
- require 'feedigest/options'
1
+ require 'feedigest/config'
2
2
  require 'feedjira'
3
+ require 'open-uri'
3
4
 
4
5
  class Feedigest::FeedFetcher
5
6
  Feed = Struct.new(:url, :title, :entries, :error)
6
7
 
7
- attr_reader :feed_urls
8
+ USER_AGENT = "feedigest/#{Feedigest::VERSION}".freeze
8
9
 
9
- def initialize(feed_urls)
10
+ attr_reader :feed_urls, :filter_cmd
11
+
12
+ def initialize(feed_urls, filter_cmd = nil)
10
13
  @feed_urls = feed_urls
14
+ @filter_cmd = filter_cmd
11
15
  end
12
16
 
13
17
  def feeds
@@ -34,9 +38,18 @@ class Feedigest::FeedFetcher
34
38
  )
35
39
  end
36
40
 
41
+ def fetch_feed(url)
42
+ feed_fd = OpenURI.open_uri(url, 'User-Agent' => USER_AGENT)
43
+
44
+ if filter_cmd
45
+ filter_feed(feed_fd)
46
+ else
47
+ feed_fd.read
48
+ end
49
+ end
50
+
37
51
  def fetch_and_parse_feed(url)
38
- feed = Feedjira::Feed.fetch_and_parse(url)
39
- [feed, nil]
52
+ [Feedjira::Feed.parse(fetch_feed(url)), nil]
40
53
  rescue StandardError => e
41
54
  [nil, e.message]
42
55
  end
@@ -50,6 +63,14 @@ class Feedigest::FeedFetcher
50
63
  end
51
64
 
52
65
  def window_start
53
- @window_start ||= Time.now - Feedigest.options[:entry_window]
66
+ @window_start ||= Time.now - Feedigest.config.fetch(:entry_window) * 3600
67
+ end
68
+
69
+ def filter_feed(feed_fd)
70
+ IO.popen(filter_cmd, 'r+') do |io|
71
+ IO.copy_stream(feed_fd, io)
72
+ io.close_write
73
+ io.read
74
+ end
54
75
  end
55
76
  end
@@ -1,4 +1,4 @@
1
- require 'feedigest/options'
1
+ require 'feedigest/config'
2
2
  require 'feedigest/version'
3
3
  require 'nokogiri'
4
4
  require 'reverse_markdown'
@@ -14,8 +14,8 @@ class Feedigest::MailComposer
14
14
 
15
15
  def mail
16
16
  @mail ||= Mail.new(
17
- Feedigest.options[:email_sender],
18
- Feedigest.options[:email_recipient],
17
+ Feedigest.config.fetch(:email_sender),
18
+ Feedigest.config.fetch(:email_recipient),
19
19
  subject,
20
20
  html_body,
21
21
  text_body
@@ -36,6 +36,8 @@ class Feedigest::MailComposer
36
36
  @html_body ||=
37
37
  Nokogiri::HTML::Builder.new(encoding: 'utf-8') { |builder|
38
38
  builder.div do
39
+ header_html(builder)
40
+
39
41
  feeds.each do |feed|
40
42
  feed_html(builder, feed)
41
43
  end
@@ -49,6 +51,18 @@ class Feedigest::MailComposer
49
51
  ReverseMarkdown.convert(html_body)
50
52
  end
51
53
 
54
+ def header_html(builder)
55
+ builder.p(
56
+ sprintf(
57
+ 'In the last %{hours}, %{entries} %{were} published in %{feeds}:',
58
+ hours: pluralize(Feedigest.config.fetch(:entry_window), 'hour'),
59
+ entries: pluralize(entries_count, 'entry', 'entries'),
60
+ were: entries_count == 1 ? 'was' : 'were',
61
+ feeds: pluralize(feeds_without_error.size, 'feed')
62
+ )
63
+ )
64
+ end
65
+
52
66
  def feed_html(builder, feed)
53
67
  builder.div do
54
68
  if feed.error
@@ -1,4 +1,4 @@
1
- require 'feedigest/options'
1
+ require 'feedigest/config'
2
2
  require 'forwardable'
3
3
  require 'mail'
4
4
 
@@ -47,6 +47,6 @@ class Feedigest::MailSender
47
47
  end
48
48
 
49
49
  def setup_delivery_method!(mail)
50
- mail.delivery_method(:smtp, Feedigest.smtp_options)
50
+ mail.delivery_method(:smtp, Feedigest.config.mail_gem_smtp_options)
51
51
  end
52
52
  end
@@ -0,0 +1,61 @@
1
+ require 'feedigest/version'
2
+ require 'slop'
3
+
4
+ class Feedigest::OptionParser
5
+ attr_reader :args
6
+
7
+ def initialize(args)
8
+ @args = args
9
+ end
10
+
11
+ def options
12
+ options = build_options
13
+ parser = Slop::Parser.new(options)
14
+
15
+ begin
16
+ parser.parse(args).to_hash
17
+ rescue Slop::Error => e
18
+ puts "error: #{e.message}"
19
+ puts
20
+ puts options
21
+ exit
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def build_options
28
+ Slop::Options.new do |o|
29
+ o.string(
30
+ '--feeds',
31
+ 'path to file that contains a line-separated list of feed URLs',
32
+ required: true
33
+ )
34
+
35
+ o.string '--filter', 'command to filter each feed with'
36
+
37
+ o.string(
38
+ '--config',
39
+ 'path to YAML configuration file',
40
+ default: '~/.feedigest/config.yaml'
41
+ )
42
+
43
+ o.bool(
44
+ '-n',
45
+ '--dry-run',
46
+ 'print email instead of sending it',
47
+ default: false
48
+ )
49
+
50
+ o.on '--version', 'print version' do
51
+ puts Feedigest::VERSION
52
+ exit
53
+ end
54
+
55
+ o.on '-h', '--help', 'print help' do
56
+ puts o
57
+ exit
58
+ end
59
+ end
60
+ end
61
+ end
@@ -1,3 +1,3 @@
1
1
  module Feedigest
2
- VERSION = '0.1.0'.freeze
2
+ VERSION = '0.2.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: feedigest
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Angelos Orfanakos
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-07-04 00:00:00.000000000 Z
11
+ date: 2018-07-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: feedjira
@@ -66,6 +66,26 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '1.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: slop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '4.6'
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: 4.6.2
79
+ type: :runtime
80
+ prerelease: false
81
+ version_requirements: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - "~>"
84
+ - !ruby/object:Gem::Version
85
+ version: '4.6'
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: 4.6.2
69
89
  - !ruby/object:Gem::Dependency
70
90
  name: bundler-audit
71
91
  requirement: !ruby/object:Gem::Requirement
@@ -92,10 +112,11 @@ files:
92
112
  - README.md
93
113
  - bin/feedigest
94
114
  - lib/feedigest.rb
115
+ - lib/feedigest/config.rb
95
116
  - lib/feedigest/feed_fetcher.rb
96
117
  - lib/feedigest/mail_composer.rb
97
118
  - lib/feedigest/mail_sender.rb
98
- - lib/feedigest/options.rb
119
+ - lib/feedigest/option_parser.rb
99
120
  - lib/feedigest/version.rb
100
121
  homepage: https://github.com/agorf/feedigest
101
122
  licenses:
@@ -1,23 +0,0 @@
1
- module Feedigest
2
- def self.options
3
- @options ||= {
4
- entry_window: ENV.fetch('FEEDIGEST_ENTRY_WINDOW', 60 * 60 * 24), # Seconds
5
- email_sender: ENV.fetch(
6
- 'FEEDIGEST_EMAIL_SENDER',
7
- "feedigest@#{`hostname`.strip}"
8
- ),
9
- email_recipient: ENV.fetch('FEEDIGEST_EMAIL_RECIPIENT')
10
- }
11
- end
12
-
13
- def self.smtp_options
14
- @smtp_options ||= {
15
- address: ENV.fetch('FEEDIGEST_SMTP_ADDRESS'),
16
- port: ENV.fetch('FEEDIGEST_SMTP_PORT', '587').to_i,
17
- user_name: ENV.fetch('FEEDIGEST_SMTP_USERNAME'),
18
- password: ENV.fetch('FEEDIGEST_SMTP_PASSWORD'),
19
- authentication: ENV.fetch('FEEDIGEST_SMTP_AUTH', 'plain'),
20
- enable_starttls: ENV.fetch('FEEDIGEST_SMTP_STARTTLS', 'true') == 'true'
21
- }
22
- end
23
- end