digest_email 1.0.1
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/LICENSE.txt +22 -0
- data/README.md +137 -0
- data/bin/digest_email +74 -0
- data/lib/digest_email/digest.rb +45 -0
- data/lib/digest_email/digest_element.rb +21 -0
- data/lib/digest_email/digest_footer.rb +36 -0
- data/lib/digest_email/digest_header.rb +65 -0
- data/lib/digest_email/digest_item.rb +42 -0
- data/lib/digest_email/digest_items.rb +26 -0
- data/lib/digest_email/digest_parser.rb +41 -0
- data/lib/digest_email/digest_parser_validator.rb +22 -0
- data/lib/digest_email/version.rb +3 -0
- data/lib/digest_email.rb +13 -0
- data/templates/default.html +134 -0
- metadata +60 -0
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Peter Hamilton
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
# DigestEmail
|
2
|
+
|
3
|
+
This is a project put together in a weekend by [Peter Hamilton](http://peterhamilton.github.com).
|
4
|
+
The overall aim was to simplify the process of generating weekly Computing Society emails for students.
|
5
|
+
|
6
|
+
There were several reasons for making this gem:
|
7
|
+
- **Fun** - It's not often I have time to make side projects right now. This was a nice non-uni related hack
|
8
|
+
- **Learning** - I wanted to do several things - learn more about TDD, create a command line ruby gem etc. This seemed like a great opportunity to tick the boxes
|
9
|
+
- **Consistency** - Currently everyone provides their digest info in random formats all over the place. Now they can provide the yaml config for their digest entry and I can just combine them all and generate a digest email
|
10
|
+
- **Legacy** - I wanted something future society members could use and improve on
|
11
|
+
|
12
|
+
The result is a gem which turns [this](https://gist.github.com/4092848) into [this](http://docsoc.s3.amazonaws.com/sample/sample_digest_email.html)
|
13
|
+
|
14
|
+
## Background
|
15
|
+
I'm responsible for collating together around 4-6 notices which need to go out to our students each week for jobs, clubs, meetings and events. I currently have to write raw HTML for each email which involves a lot of Copy-Paste and inline styles and this weekend I decided to find a way to automate the process.
|
16
|
+
|
17
|
+
I just collated and generated this weeks digest using this method and it's gone from taking **2 hours** down to **5 minutes**. I'd call that a success.
|
18
|
+
|
19
|
+
## Installation
|
20
|
+
|
21
|
+
Add this line to your application's Gemfile:
|
22
|
+
|
23
|
+
gem 'digest_email'
|
24
|
+
|
25
|
+
And then execute:
|
26
|
+
|
27
|
+
$ bundle
|
28
|
+
|
29
|
+
Or install it yourself as:
|
30
|
+
|
31
|
+
$ gem install digest_email
|
32
|
+
|
33
|
+
## Usage
|
34
|
+
|
35
|
+
### 1. Write a digest file
|
36
|
+
I name mine something like 2012-01-01.yml.
|
37
|
+
|
38
|
+
A digest file must consist of 3 main sections, a header, items and a footer.
|
39
|
+
|
40
|
+
- Header
|
41
|
+
Must contain:
|
42
|
+
- `title` - The main digest title
|
43
|
+
- `subtitle` - Optional
|
44
|
+
- `subsubtitle` - Optional
|
45
|
+
|
46
|
+
- Items
|
47
|
+
Contains a list of items, each of which must contain:
|
48
|
+
|
49
|
+
- `list_title` - The title which appears in the digest summary at the top
|
50
|
+
- `image` - A full url to a hosted image (150px wide recommended)
|
51
|
+
- `title` - The title for the item
|
52
|
+
- `body` - The body for the item
|
53
|
+
|
54
|
+
Fields are parsed as markdown using [Maruku](https://github.com/bhollis/maruku) so the body can be easily customised
|
55
|
+
|
56
|
+
- Footer
|
57
|
+
Must contain:
|
58
|
+
- `signature` - Will be aligned to the right e.g. "DocSoc 2012/2013"
|
59
|
+
- `sponsors_image` - A 600px wide hosted image for the bottom of the email
|
60
|
+
|
61
|
+
See below for a full example, also available as a [github gist](https://gist.github.com/4092848)
|
62
|
+
```yaml
|
63
|
+
# sample_digest_email.yml
|
64
|
+
# Parses with the digest_email (http://peterhamilton.github.com/digest_email) gem
|
65
|
+
# See the result of running this file through the gem here: http://docsoc.s3.amazonaws.com/sample/sample_digest_email.html
|
66
|
+
header:
|
67
|
+
title: "Weekly Digest"
|
68
|
+
subtitle: November 12th 2012
|
69
|
+
subsubtitle:
|
70
|
+
|
71
|
+
items:
|
72
|
+
- list_title: New, Easy Email Digests
|
73
|
+
image: https://s3-eu-west-1.amazonaws.com/docsoc/sample/suitcase.jpg
|
74
|
+
title: We have nice simple email digests!
|
75
|
+
body: |
|
76
|
+
This is an example of a simple digest item.
|
77
|
+
In this example it might be for a potential industry placement - for example, check out the rather nice suitcase image on the left
|
78
|
+
|
79
|
+
- list_title: Robotics Hackathon
|
80
|
+
image: https://s3-eu-west-1.amazonaws.com/docsoc/sample/walle.jpg
|
81
|
+
title: These aren't the droids you're looking for...
|
82
|
+
body: |
|
83
|
+
Another sample sigest item. This one is all about a fantasy hackathon!
|
84
|
+
|
85
|
+
For more details contact *[Peter Hamilton](http://peterhamilton.github.com)*
|
86
|
+
|
87
|
+
footer:
|
88
|
+
signature: -- DoCSoc Committee 2012
|
89
|
+
sponsors_image: https://s3-eu-west-1.amazonaws.com/docsoc/docsocsponsors.jpg # Bottom Sponsor Banner
|
90
|
+
```
|
91
|
+
|
92
|
+
### 2. Write a template
|
93
|
+
An HTML file. The digest email renderer will look for the tag {{content}} and replace it with generated digest html.
|
94
|
+
|
95
|
+
If you need to add styling, you may do so in the header and it will be converted to inline styles for email client compatibility.
|
96
|
+
|
97
|
+
You can see a sample template at [https://peterhamilton.github.com/digest-email/blob/master/templates/default.html]
|
98
|
+
|
99
|
+
### 3. Generate
|
100
|
+
|
101
|
+
In the command line type `digest_email -h` to see a list of commands
|
102
|
+
|
103
|
+
```bash
|
104
|
+
digest_email -h
|
105
|
+
Usage: digest_email [generate] [OPTIONS]
|
106
|
+
|
107
|
+
Commands
|
108
|
+
generate: renders the email digest
|
109
|
+
|
110
|
+
Required:
|
111
|
+
-f, --file FILE A digest file e.g. 2012-01-01.yml
|
112
|
+
-o, --output FILE The file to output the html to e.g index.html
|
113
|
+
|
114
|
+
Optional:
|
115
|
+
-t, --template FILE an HTML file with a {{content}} template tag
|
116
|
+
-w, --warnings if present, will show premailer warnings
|
117
|
+
-h, --help help
|
118
|
+
```
|
119
|
+
|
120
|
+
For example to generate a digest email from [this template file](https://peterhamilton.github.com/digest-email/blob/master/templates/default.html) (template.html locally) and [this digest file](https://gist.github.com/4092848) (2012-01-01.yml locally) you would type the following:
|
121
|
+
|
122
|
+
```bash
|
123
|
+
digest_email -f 2012-01-01.yml -t template.html -o 2012-01-01_digest.html
|
124
|
+
```
|
125
|
+
|
126
|
+
Which results in the file you can see [here](http://docsoc.s3.amazonaws.com/sample/sample_digest_email.html). From there it's easy to send it out as an email.
|
127
|
+
|
128
|
+
## Contributing
|
129
|
+
This is a project put together in a few days in my spare time for a fairly specific use case, however if you do see improvements which could be made, I'd love to have people contribute.
|
130
|
+
|
131
|
+
Just follow the following recommended process:
|
132
|
+
|
133
|
+
1. Fork it
|
134
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
135
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
136
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
137
|
+
5. Create new Pull Request
|
data/bin/digest_email
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'digest_email'
|
5
|
+
require 'optparse'
|
6
|
+
|
7
|
+
options = {}
|
8
|
+
|
9
|
+
opt_parser = OptionParser.new do |opt|
|
10
|
+
opt.banner = "Usage: digest_email [generate] [OPTIONS]"
|
11
|
+
opt.separator ""
|
12
|
+
opt.separator "Commands"
|
13
|
+
opt.separator " generate: renders the email digest"
|
14
|
+
opt.separator ""
|
15
|
+
opt.separator "Required:"
|
16
|
+
|
17
|
+
opt.on("-f","--file FILE","A digest file e.g. 2012-01-01.yml") do |template|
|
18
|
+
options[:file] = template
|
19
|
+
end
|
20
|
+
|
21
|
+
opt.on("-o","--output FILE","The file to output the html to e.g index.html") do |output|
|
22
|
+
options[:output] = output
|
23
|
+
end
|
24
|
+
|
25
|
+
opt.separator ""
|
26
|
+
|
27
|
+
opt.separator "Optional:"
|
28
|
+
|
29
|
+
opt.on("-t","--template FILE","an HTML file with a {{content}} template tag") do |template|
|
30
|
+
options[:template] = template
|
31
|
+
end
|
32
|
+
|
33
|
+
opt.on("-w","--warnings","if present, will show premailer warnings") do |warnings|
|
34
|
+
options[:warnings] = warnings
|
35
|
+
end
|
36
|
+
|
37
|
+
opt.on("-h","--help","help") do
|
38
|
+
puts opt_parser
|
39
|
+
exit
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
begin
|
45
|
+
opt_parser.parse!(ARGV)
|
46
|
+
raise OptionParser::MissingArgument if options[:file].nil?
|
47
|
+
|
48
|
+
case ARGV[0]
|
49
|
+
when "generate"
|
50
|
+
begin
|
51
|
+
puts "#{Time.now} - Rendering Digest"
|
52
|
+
digest_yaml = YAML.load_file(options[:file])
|
53
|
+
|
54
|
+
template_file = options[:template] || File.join(File.dirname(__FILE__), "..", "templates/default.html")
|
55
|
+
warnings = options[:warnings] || false
|
56
|
+
template = File.read template_file
|
57
|
+
|
58
|
+
d = DigestEmail.parse(digest_yaml)
|
59
|
+
|
60
|
+
File.open(options[:output], 'w') { |file| file.write(d.render template, warnings) }
|
61
|
+
puts "#{Time.now} - Done. Digest saved as #{options[:output]}"
|
62
|
+
rescue Exception => e
|
63
|
+
puts "Error generating digest: #{e}"
|
64
|
+
exit
|
65
|
+
end
|
66
|
+
else
|
67
|
+
raise
|
68
|
+
end
|
69
|
+
rescue
|
70
|
+
puts opt_parser
|
71
|
+
exit
|
72
|
+
end
|
73
|
+
|
74
|
+
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'digest_email/digest_element'
|
2
|
+
require 'premailer'
|
3
|
+
|
4
|
+
module DigestEmail
|
5
|
+
class Digest < DigestElement
|
6
|
+
|
7
|
+
|
8
|
+
def initialize(header, items, footer)
|
9
|
+
@header = header
|
10
|
+
@items = items
|
11
|
+
@footer = footer
|
12
|
+
@children = [@header, @items, @footer]
|
13
|
+
end
|
14
|
+
|
15
|
+
def render(template = "{{content}}", show_warnings = false)
|
16
|
+
html = [@header.render(@items.children), @items.render, @footer.render].join
|
17
|
+
html = template.gsub '{{content}}', wrap(html)
|
18
|
+
|
19
|
+
# Convert any styles to inline
|
20
|
+
premailer = Premailer.new(html, :with_html_string => true)
|
21
|
+
html = premailer.to_inline_css
|
22
|
+
|
23
|
+
# Output any CSS warnings
|
24
|
+
if show_warnings
|
25
|
+
puts "Email Client Compatibility Warnings:"
|
26
|
+
premailer.warnings.each do |w|
|
27
|
+
puts "#{w[:message]} (#{w[:level]}) may not render properly in #{w[:clients]}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
html
|
31
|
+
end
|
32
|
+
|
33
|
+
def wrap(inner)
|
34
|
+
["<div class=\"digest\">",
|
35
|
+
"<table>",
|
36
|
+
"<tr>",
|
37
|
+
"<td>",
|
38
|
+
inner,
|
39
|
+
"</td>",
|
40
|
+
"</tr>",
|
41
|
+
"</table>",
|
42
|
+
"</div>"].join
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'digest_email/digest_element'
|
2
|
+
|
3
|
+
module DigestEmail
|
4
|
+
class DigestElement
|
5
|
+
attr_accessor :children
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@children = []
|
9
|
+
end
|
10
|
+
|
11
|
+
# Render each child, join all the html and wrap it
|
12
|
+
def render
|
13
|
+
wrap @children.map(&:render).join("\n")
|
14
|
+
end
|
15
|
+
|
16
|
+
# By default don't wrap with anything
|
17
|
+
def wrap(inner)
|
18
|
+
inner
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'digest_email/digest_element'
|
2
|
+
require 'maruku'
|
3
|
+
|
4
|
+
module DigestEmail
|
5
|
+
class DigestFooter < DigestElement
|
6
|
+
attr_accessor :signature, :sponsors_image
|
7
|
+
|
8
|
+
def initialize(footer)
|
9
|
+
super()
|
10
|
+
@signature = footer["signature"]
|
11
|
+
@sponsors_image = footer["sponsors_image"]
|
12
|
+
end
|
13
|
+
|
14
|
+
def render
|
15
|
+
parsed_signature = Maruku.new(@signature).to_html
|
16
|
+
html = [
|
17
|
+
"<div class=\"digest-email-dot-seperator\"></div>",
|
18
|
+
"<div class=\"digest-email-footer-signature\"><div>#{parsed_signature}</div></div>",
|
19
|
+
render_sponsors_image
|
20
|
+
]
|
21
|
+
|
22
|
+
wrap html.join("\n")
|
23
|
+
end
|
24
|
+
|
25
|
+
def render_sponsors_image
|
26
|
+
html = ["<div class=\"digest-email-footer-sponsors-image\">"]
|
27
|
+
html << ["<img src=\"#{@sponsors_image}\"/>"]
|
28
|
+
html << ["</div>"]
|
29
|
+
html.join("\n")
|
30
|
+
end
|
31
|
+
|
32
|
+
def wrap(inner)
|
33
|
+
"<div class=\"digest-email-footer\">#{inner}</div>"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'digest_email/digest_element'
|
2
|
+
require 'maruku'
|
3
|
+
|
4
|
+
module DigestEmail
|
5
|
+
class DigestHeader < DigestElement
|
6
|
+
attr_accessor :title, :subtitle, :subsubtitle
|
7
|
+
|
8
|
+
def initialize(header)
|
9
|
+
super()
|
10
|
+
@title = header["title"]
|
11
|
+
|
12
|
+
if header.has_key? "subtitle"
|
13
|
+
@subtitle = header["subtitle"]
|
14
|
+
else
|
15
|
+
@subtitle = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
if header.has_key? "subsubtitle"
|
19
|
+
@subsubtitle = header["subsubtitle"]
|
20
|
+
else
|
21
|
+
@subsubtitle = nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def render(items)
|
26
|
+
parsed_title = Maruku.new(@title).to_html
|
27
|
+
|
28
|
+
html = ["<div class=\"digest-email-header-title\">#{parsed_title}</div>"]
|
29
|
+
|
30
|
+
unless @subtitle.nil?
|
31
|
+
parsed_subtitle = Maruku.new(@subtitle).to_html
|
32
|
+
html << ["<div class=\"digest-email-header-subtitle\">",
|
33
|
+
parsed_subtitle,
|
34
|
+
"</div>"]
|
35
|
+
end
|
36
|
+
|
37
|
+
unless @subsubtitle.nil?
|
38
|
+
parsed_subsubtitle = Maruku.new(@subsubtitle).to_html
|
39
|
+
html << ["<div class=\"digest-email-header-subsubtitle\">",
|
40
|
+
parsed_subsubtitle,
|
41
|
+
"</div>"]
|
42
|
+
end
|
43
|
+
|
44
|
+
# Contents List
|
45
|
+
html << ["<div class=\"digest-email-dot-seperator\"></div>"]
|
46
|
+
html << render_contents_list(items)
|
47
|
+
html << ["<div class=\"digest-email-dot-seperator\"></div>"]
|
48
|
+
|
49
|
+
wrap html.join("\n")
|
50
|
+
end
|
51
|
+
|
52
|
+
def render_contents_list(items)
|
53
|
+
html = ["<div class=\"digest-email-header-contents-list\">"]
|
54
|
+
html << ["<ol>"]
|
55
|
+
html << items.map { |item| "<li>#{item.list_title}</li>" }.join
|
56
|
+
html << ["</ol>"]
|
57
|
+
html << ["</div>"]
|
58
|
+
html.join
|
59
|
+
end
|
60
|
+
|
61
|
+
def wrap(inner)
|
62
|
+
"<div class=\"digest-email-header\">#{inner}</div>"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'digest_email/digest_element'
|
2
|
+
require 'maruku'
|
3
|
+
|
4
|
+
module DigestEmail
|
5
|
+
class DigestItem < DigestElement
|
6
|
+
attr_accessor :list_title, :title, :image, :body
|
7
|
+
|
8
|
+
def initialize(item)
|
9
|
+
super()
|
10
|
+
@list_title = item["list_title"]
|
11
|
+
@title = item["title"]
|
12
|
+
@image = item["image"]
|
13
|
+
@body = item["body"]
|
14
|
+
end
|
15
|
+
|
16
|
+
def render
|
17
|
+
parsed_title = Maruku.new(@title).to_html
|
18
|
+
parsed_body = Maruku.new(@body).to_html
|
19
|
+
|
20
|
+
html = [
|
21
|
+
render_image,
|
22
|
+
"<div class=\"digest-email-item-content-container\">",
|
23
|
+
"<div class=\"digest-email-item-title\">#{parsed_title}</div>",
|
24
|
+
"<div class=\"digest-email-item-body\">#{parsed_body}</div>",
|
25
|
+
"</div>"
|
26
|
+
]
|
27
|
+
|
28
|
+
wrap html.join("\n")
|
29
|
+
end
|
30
|
+
|
31
|
+
def render_image
|
32
|
+
html = ["<div class=\"digest-email-item-image\">"]
|
33
|
+
html << ["<img src=\"#{@image}\"/>"]
|
34
|
+
html << ["</div>"]
|
35
|
+
html.join("\n")
|
36
|
+
end
|
37
|
+
|
38
|
+
def wrap(inner)
|
39
|
+
"<div class=\"digest-email-item\">#{inner}<div class=\"clear\"></div></div>"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'digest_email/digest_element'
|
2
|
+
|
3
|
+
class Array
|
4
|
+
# As per http://stackoverflow.com/questions/3676027
|
5
|
+
def intersperse(separator)
|
6
|
+
(inject([]) { |a,v| a + [v,separator] })[0...-1]
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module DigestEmail
|
11
|
+
class DigestItems < DigestElement
|
12
|
+
def initialize(items)
|
13
|
+
super()
|
14
|
+
@children = items
|
15
|
+
end
|
16
|
+
|
17
|
+
def render
|
18
|
+
rendered_items = @children.map(&:render)
|
19
|
+
wrap rendered_items.join "\n<div class=\"digest-email-dot-seperator\"></div>\n"
|
20
|
+
end
|
21
|
+
|
22
|
+
def wrap(inner)
|
23
|
+
"<div class=\"digest-email-items\">#{inner}</div>"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/digest_parser_validator'
|
2
|
+
|
3
|
+
module DigestEmail
|
4
|
+
class DigestParser
|
5
|
+
extend DigestParserValidator
|
6
|
+
|
7
|
+
def self.parse(digest)
|
8
|
+
begin
|
9
|
+
validate_indices(digest, ["header", "items", "footer"])
|
10
|
+
|
11
|
+
header = parse_header(digest["header"])
|
12
|
+
body = parse_items(digest["items"])
|
13
|
+
footer = parse_footer(digest["footer"])
|
14
|
+
|
15
|
+
DigestEmail::Digest.new(header, body, footer)
|
16
|
+
rescue Exception => e
|
17
|
+
raise "Failed to parse digest: #{e.message}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.parse_header(header)
|
22
|
+
validate_index(header, "title")
|
23
|
+
DigestEmail::DigestHeader.new header
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.parse_items(items)
|
27
|
+
DigestEmail::DigestItems.new items.map{|item| parse_item(item)}
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.parse_item(item)
|
31
|
+
validate_indices(item, ["list_title", "image", "title", "body"])
|
32
|
+
|
33
|
+
DigestEmail::DigestItem.new item
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.parse_footer(footer)
|
37
|
+
validate_indices(footer, ["signature", "sponsors_image"])
|
38
|
+
DigestEmail::DigestFooter.new footer
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module DigestParserValidator
|
2
|
+
|
3
|
+
# Invalidates index if missing or empty, taking advantage of lazy eval
|
4
|
+
def valid_index?(item, index)
|
5
|
+
item.has_key?(index) and !item[index].nil? and !item[index].empty?
|
6
|
+
end
|
7
|
+
|
8
|
+
# Validate an index for given hash
|
9
|
+
def validate_index(item, index)
|
10
|
+
unless valid_index?(item, index)
|
11
|
+
raise "Missing digest field #{index.capitalize}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Validates multiple indices for given hash
|
16
|
+
def validate_indices(item, indices)
|
17
|
+
indices.each do |index|
|
18
|
+
validate_index(item, index)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
data/lib/digest_email.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require "digest_email/version"
|
2
|
+
require "digest_email/digest"
|
3
|
+
require "digest_email/digest_header"
|
4
|
+
require "digest_email/digest_items"
|
5
|
+
require "digest_email/digest_item"
|
6
|
+
require "digest_email/digest_footer"
|
7
|
+
require "digest_email/digest_parser"
|
8
|
+
|
9
|
+
module DigestEmail
|
10
|
+
def self.parse(digest_hash)
|
11
|
+
DigestParser.parse digest_hash
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
2
|
+
<html xmlns="http://www.w3.org/1999/xhtml">
|
3
|
+
<head>
|
4
|
+
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
|
5
|
+
<style type="text/css">
|
6
|
+
|
7
|
+
/*
|
8
|
+
Elements taken from:
|
9
|
+
http://meyerweb.com/eric/tools/css/reset/
|
10
|
+
v2.0 | 20110126
|
11
|
+
License: none (public domain)
|
12
|
+
*/
|
13
|
+
|
14
|
+
table {
|
15
|
+
-premailer-width: 600;
|
16
|
+
}
|
17
|
+
|
18
|
+
p {
|
19
|
+
margin: 5px 0px;
|
20
|
+
padding: 0;
|
21
|
+
border: 0;
|
22
|
+
vertical-align: baseline;
|
23
|
+
}
|
24
|
+
|
25
|
+
/* HTML5 display-role reset for older browsers */
|
26
|
+
article, aside, details, figcaption, figure,
|
27
|
+
footer, header, hgroup, menu, nav, section {
|
28
|
+
display: block;
|
29
|
+
}
|
30
|
+
|
31
|
+
body {
|
32
|
+
line-height: 1;
|
33
|
+
}
|
34
|
+
|
35
|
+
blockquote, q {
|
36
|
+
quotes: none;
|
37
|
+
}
|
38
|
+
blockquote:before, blockquote:after,
|
39
|
+
q:before, q:after {
|
40
|
+
content: '';
|
41
|
+
content: none;
|
42
|
+
}
|
43
|
+
table {
|
44
|
+
border-collapse: collapse;
|
45
|
+
border-spacing: 0;
|
46
|
+
-premailer-width: 600;
|
47
|
+
-premailer-cellpadding: 20;
|
48
|
+
}
|
49
|
+
|
50
|
+
/* END RESET */
|
51
|
+
|
52
|
+
* {
|
53
|
+
font-family: Arial, Helvetica, sans-serif!important;
|
54
|
+
}
|
55
|
+
|
56
|
+
body {
|
57
|
+
color:#000000;
|
58
|
+
padding: 0px;
|
59
|
+
margin: 0px;
|
60
|
+
color: #444;
|
61
|
+
}
|
62
|
+
|
63
|
+
.digest {
|
64
|
+
width: 600px;
|
65
|
+
margin: 0 auto;
|
66
|
+
}
|
67
|
+
|
68
|
+
.digest-email-header-title {
|
69
|
+
font-size:50px;
|
70
|
+
line-height:60px;
|
71
|
+
text-align: center;
|
72
|
+
}
|
73
|
+
|
74
|
+
.digest-email-header-subtitle {
|
75
|
+
font-size: 20px;
|
76
|
+
text-align: center;
|
77
|
+
margin-bottom: 10px;
|
78
|
+
}
|
79
|
+
|
80
|
+
.digest-email-header-contents-list {
|
81
|
+
font-size: 20px;
|
82
|
+
line-height: 25px;
|
83
|
+
}
|
84
|
+
|
85
|
+
.digest-email-item {
|
86
|
+
margin: 10px 0px;
|
87
|
+
}
|
88
|
+
|
89
|
+
.digest-email-item-image {
|
90
|
+
width: 150px;
|
91
|
+
float: left;
|
92
|
+
}
|
93
|
+
|
94
|
+
.digest-email-item-content-container {
|
95
|
+
margin-left:10px;
|
96
|
+
width: 440px;
|
97
|
+
float: left;
|
98
|
+
text-align: justify;
|
99
|
+
font-size: 14px;
|
100
|
+
line-height: 20px;
|
101
|
+
}
|
102
|
+
|
103
|
+
.digest-email-item-title {
|
104
|
+
font-weight: bold;
|
105
|
+
margin-bottom: 5px;
|
106
|
+
}
|
107
|
+
|
108
|
+
.digest-email-dot-seperator {
|
109
|
+
background: url('https://s3-eu-west-1.amazonaws.com/docsoc/dots.jpg');
|
110
|
+
background-repeat: repeat-x;
|
111
|
+
height: 1px;
|
112
|
+
margin: 5px 0px;
|
113
|
+
}
|
114
|
+
|
115
|
+
.digest-email-footer-signature {
|
116
|
+
padding-top: 10px;
|
117
|
+
text-align: right;
|
118
|
+
}
|
119
|
+
|
120
|
+
.digest-email-footer-sponsors-image {
|
121
|
+
margin-top: 15px;
|
122
|
+
}
|
123
|
+
|
124
|
+
.clear {
|
125
|
+
clear: both;
|
126
|
+
}
|
127
|
+
|
128
|
+
|
129
|
+
</style>
|
130
|
+
</head>
|
131
|
+
<body>
|
132
|
+
{{content}}
|
133
|
+
</body>
|
134
|
+
</html>
|
metadata
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: digest_email
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Peter Hamilton
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-11-17 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Gem to generate a weekly email digest
|
15
|
+
email:
|
16
|
+
- peter@inspiredpixel.net
|
17
|
+
executables:
|
18
|
+
- digest_email
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- lib/digest_email/digest.rb
|
23
|
+
- lib/digest_email/digest_element.rb
|
24
|
+
- lib/digest_email/digest_footer.rb
|
25
|
+
- lib/digest_email/digest_header.rb
|
26
|
+
- lib/digest_email/digest_item.rb
|
27
|
+
- lib/digest_email/digest_items.rb
|
28
|
+
- lib/digest_email/digest_parser.rb
|
29
|
+
- lib/digest_email/digest_parser_validator.rb
|
30
|
+
- lib/digest_email/version.rb
|
31
|
+
- lib/digest_email.rb
|
32
|
+
- templates/default.html
|
33
|
+
- bin/digest_email
|
34
|
+
- README.md
|
35
|
+
- LICENSE.txt
|
36
|
+
homepage: ''
|
37
|
+
licenses: []
|
38
|
+
post_install_message:
|
39
|
+
rdoc_options: []
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
requirements: []
|
55
|
+
rubyforge_project:
|
56
|
+
rubygems_version: 1.8.24
|
57
|
+
signing_key:
|
58
|
+
specification_version: 3
|
59
|
+
summary: Gem to generate a weekly email digest
|
60
|
+
test_files: []
|