feedigest 0.1.0 → 0.2.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.
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