griddler 0.1.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.
- data/LICENSE +19 -0
- data/README.md +119 -0
- data/Rakefile +12 -0
- data/app/controllers/griddler/emails_controller.rb +6 -0
- data/config/initializers/griddler.rb +1 -0
- data/config/routes.rb +3 -0
- data/lib/griddler/configuration.rb +43 -0
- data/lib/griddler/email.rb +45 -0
- data/lib/griddler/email_format.rb +6 -0
- data/lib/griddler/email_parser.rb +49 -0
- data/lib/griddler/engine.rb +4 -0
- data/lib/griddler/version.rb +3 -0
- data/lib/griddler.rb +8 -0
- data/lib/tasks/griddler_tasks.rake +4 -0
- metadata +117 -0
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2012 Caleb Thompson, Joel Oliveira and thoughtbot, inc.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
Griddler
|
2
|
+
========
|
3
|
+
|
4
|
+
Griddler is a Rails engine (full plugin) that provides an endpoint for the
|
5
|
+
[Sendgrid parse
|
6
|
+
api](http://sendgrid.com/docs/API%20Reference/Webhooks/parse.html) that hands
|
7
|
+
off a built email object to a class implemented by you.
|
8
|
+
|
9
|
+
Installation
|
10
|
+
------------
|
11
|
+
|
12
|
+
Add griddler to your application's Gemfile and run `bundle install`:
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
gem 'griddler'
|
16
|
+
```
|
17
|
+
|
18
|
+
Griddler comes with a default endpoint that will be displayed at the bottom of
|
19
|
+
the output of `rake routes`. If there is a previously defined route that matches
|
20
|
+
`/email_processor`–or you would like to rename the matched path–you may
|
21
|
+
add the route to the desired position in routes.rb with the following:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
match '/email_processor' => 'griddler/emails#create', via: :post
|
25
|
+
```
|
26
|
+
|
27
|
+
Defaults
|
28
|
+
--------
|
29
|
+
|
30
|
+
By default Griddler will look for a class to be created in your application
|
31
|
+
called EmailProcessor with a class method implemented named process, taking in
|
32
|
+
one argument (presumably `email`). For example, in `./lib/email_processor.rb`:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
class EmailProcessor
|
36
|
+
def self.process(email)
|
37
|
+
# all of your application-specific code here - creating models, processing
|
38
|
+
# reports, etc
|
39
|
+
end
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
43
|
+
The contents of the `email` object passed into your process method is a hash
|
44
|
+
containing:
|
45
|
+
|
46
|
+
* `:to`
|
47
|
+
* `:from`
|
48
|
+
* `:subject`
|
49
|
+
* `:body`
|
50
|
+
|
51
|
+
Each of those has some sensible defaults.
|
52
|
+
|
53
|
+
`:from` and `:subject` will contain the obvious values found in the email, the
|
54
|
+
raw from and subject values.
|
55
|
+
|
56
|
+
`:body` will contain the full contents of the email body **unless** there is a
|
57
|
+
line in the email containing the string `-- Reply ABOVE THIS LINE --`. In that
|
58
|
+
case `:body` will contain everything before that line.
|
59
|
+
|
60
|
+
`:to` will contain all of the text before the email's "@" character. We've found
|
61
|
+
that this is the most often use portion of the email address and consider it to
|
62
|
+
be the token we'll key off of for interaction with our application.
|
63
|
+
|
64
|
+
Configuration Options
|
65
|
+
---------------------
|
66
|
+
|
67
|
+
An initializer can be created to control some of the options in Griddler.
|
68
|
+
Defaults are shown below with sample overrides following. In
|
69
|
+
`config/initializer/griddler.rb`:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
Griddler.configure do |config|
|
73
|
+
config.handler_class = EmailProcessor # MyEmailProcessor
|
74
|
+
config.handler_method = :process # :go
|
75
|
+
config.raw_body = false # true
|
76
|
+
config.to = :token # :raw, :email, :hash
|
77
|
+
# :raw => 'AppName <s13.6b2d13dc6a1d33db7644@mail.myapp.com>'
|
78
|
+
# :email => 's13.6b2d13dc6a1d33db7644@mail.myapp.com'
|
79
|
+
# :token => 's13.6b2d13dc6a1d33db7644'
|
80
|
+
# :hash => { raw: '', email: '', token: '', host: '' }
|
81
|
+
config.reply_delimeter = '-- REPLY ABOVE THIS LINE --'
|
82
|
+
end
|
83
|
+
```
|
84
|
+
|
85
|
+
* `config.handler_class` change the class Griddler will use to handle your
|
86
|
+
incoming emails.
|
87
|
+
* `config.handler_method` change the class method called on
|
88
|
+
`config.handler_class`.
|
89
|
+
* `config.reply_delimeter` change the string searched for that will split your
|
90
|
+
body.
|
91
|
+
* `config.raw_body` use the full email body whether or not
|
92
|
+
`config.reply_delimeter` is set.
|
93
|
+
* `config.to` change the format of the returned value for the `:to` key in the
|
94
|
+
email object. `:hash` will return all options within a (surprise!) - hash.
|
95
|
+
|
96
|
+
More Information
|
97
|
+
----------------
|
98
|
+
|
99
|
+
* [Sendgrid](http://www.sendgrid.com)
|
100
|
+
* [Sendgrid Parse API](www.sendgrid.com/docs/API Reference/Webhooks/parse.html)
|
101
|
+
|
102
|
+
Credits
|
103
|
+
-------
|
104
|
+
|
105
|
+
Griddler was written by Caleb Thompson and Joel Oliveira.
|
106
|
+
|
107
|
+
Large portions of the codebase were extracted from thoughtbot's
|
108
|
+
[Trajectory](http://www.apptrajectory.com).
|
109
|
+
|
110
|
+

|
111
|
+
|
112
|
+
The names and logos for thoughtbot are trademarks of thoughtbot, inc.
|
113
|
+
|
114
|
+
License
|
115
|
+
-------
|
116
|
+
|
117
|
+
Griddler is Copyright © 2012 Caleb Thompson, Joel Oliveira and thoughtbot. It is
|
118
|
+
free software, and may be redistributed under the terms specified in the LICENSE
|
119
|
+
file.
|
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
|
8
|
+
Bundler::GemHelper.install_tasks
|
9
|
+
|
10
|
+
require 'rspec/core/rake_task'
|
11
|
+
RSpec::Core::RakeTask.new(:spec)
|
12
|
+
task default: :spec
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'griddler'
|
data/config/routes.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
module Griddler
|
2
|
+
class << self
|
3
|
+
attr_accessor :configuration
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.configure
|
7
|
+
self.configuration = Configuration.new
|
8
|
+
|
9
|
+
if block_given?
|
10
|
+
yield configuration
|
11
|
+
end
|
12
|
+
|
13
|
+
self.configuration
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.configuration
|
17
|
+
@configuration || self.configure
|
18
|
+
end
|
19
|
+
|
20
|
+
class Configuration
|
21
|
+
attr_accessor :handler_class, :handler_method, :raw_body, :reply_delimiter, :to
|
22
|
+
|
23
|
+
def to
|
24
|
+
@to ||= :token
|
25
|
+
end
|
26
|
+
|
27
|
+
def handler_class
|
28
|
+
@handler_class ||= EmailProcessor
|
29
|
+
end
|
30
|
+
|
31
|
+
def handler_method
|
32
|
+
@handler_method ||= :process
|
33
|
+
end
|
34
|
+
|
35
|
+
def raw_body
|
36
|
+
@raw_body ||= false
|
37
|
+
end
|
38
|
+
|
39
|
+
def reply_delimiter
|
40
|
+
@reply_delimiter ||= 'Reply ABOVE THIS LINE'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'iconv'
|
2
|
+
|
3
|
+
class Griddler::Email
|
4
|
+
attr_accessor :to, :from, :body, :subject
|
5
|
+
|
6
|
+
def initialize(params)
|
7
|
+
@to = extract_address(params[:to], config.to)
|
8
|
+
@from = extract_address(params[:from], :email)
|
9
|
+
@subject = params[:subject]
|
10
|
+
|
11
|
+
if config.raw_body
|
12
|
+
@body = params[:text]
|
13
|
+
else
|
14
|
+
@body = extract_body(params[:text], params[:charsets])
|
15
|
+
end
|
16
|
+
|
17
|
+
handler_class = config.handler_class
|
18
|
+
handler_method = config.handler_method
|
19
|
+
handler_class.send(handler_method, self)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def extract_address(address, type)
|
25
|
+
parsed = EmailParser.parse_address(address)
|
26
|
+
if type == :hash
|
27
|
+
parsed
|
28
|
+
else
|
29
|
+
parsed[type]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def extract_body(body_text, charsets)
|
34
|
+
if charsets.present?
|
35
|
+
charsets = ActiveSupport::JSON.decode(charsets)
|
36
|
+
body_text = Iconv.new('utf-8', charsets['text']).iconv(body_text)
|
37
|
+
end
|
38
|
+
|
39
|
+
EmailParser.extract_reply_body(body_text)
|
40
|
+
end
|
41
|
+
|
42
|
+
def config
|
43
|
+
Griddler.configuration
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,6 @@
|
|
1
|
+
module Griddler::EmailFormat
|
2
|
+
LocalPartSpecialChars = Regexp.escape('!#$%&\'*-/=?+-^_`{|}~')
|
3
|
+
LocalPartUnquoted = '(([[:alnum:]' + LocalPartSpecialChars + ']+[\.\+]+))*[[:alnum:]' + LocalPartSpecialChars + '+]+'
|
4
|
+
LocalPartQuoted = '\"(([[:alnum:]' + LocalPartSpecialChars + '\.\+]*|(\\\\[\u0001-\uFFFF]))*)\"'
|
5
|
+
Regex = Regexp.new('((' + LocalPartUnquoted + ')|(' + LocalPartQuoted + ')+)@(((\w+\-+)|(\w+\.))*\w{1,63}\.[a-z]{2,6})', Regexp::EXTENDED | Regexp::IGNORECASE)
|
6
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# Parse emails from their full format into a hash containing full email, host,
|
2
|
+
# local token, and the raw argument.
|
3
|
+
#
|
4
|
+
# Some Body <somebody@example.com>
|
5
|
+
# # => {
|
6
|
+
# token: 'somebody',
|
7
|
+
# host: 'example.com',
|
8
|
+
# email: 'somebody@example.com',
|
9
|
+
# full: 'Some Body <somebody@example.com>',
|
10
|
+
# }
|
11
|
+
module EmailParser
|
12
|
+
def self.parse_address(full_address)
|
13
|
+
email_address = extract_email_address(full_address)
|
14
|
+
token, host = split_address(email_address)
|
15
|
+
{
|
16
|
+
token: token,
|
17
|
+
host: host,
|
18
|
+
email: email_address,
|
19
|
+
full: full_address,
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.extract_reply_body(body)
|
24
|
+
if body
|
25
|
+
delimeter = Griddler.configuration.reply_delimiter
|
26
|
+
body.split(delimeter).first.
|
27
|
+
split(/^\s*[-]+\s*Original Message\s*[-]+\s*$/).first.
|
28
|
+
split(/^\s*--\s*$/).first.
|
29
|
+
split(/[\r]*\n/).reject do |line|
|
30
|
+
line =~ /^\s*>/ ||
|
31
|
+
line =~ /^\s*On.*wrote:$/ ||
|
32
|
+
line =~ /^\s*Sent from my /
|
33
|
+
end.
|
34
|
+
join("\n").
|
35
|
+
gsub(/^\s*On.*\r?\n?\s*.*\s*wrote:$/,'').
|
36
|
+
strip
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def self.extract_email_address(full_address)
|
43
|
+
full_address.split('<').last.delete('>').strip
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.split_address(email_address)
|
47
|
+
email_address.try :split, '@'
|
48
|
+
end
|
49
|
+
end
|
data/lib/griddler.rb
ADDED
metadata
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: griddler
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Caleb Thompson
|
9
|
+
- Joel Oliveira
|
10
|
+
- thoughtbot
|
11
|
+
autorequire:
|
12
|
+
bindir: bin
|
13
|
+
cert_chain: []
|
14
|
+
date: 2012-11-06 00:00:00.000000000 Z
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: rails
|
18
|
+
requirement: !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ! '>='
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 3.2.0
|
24
|
+
type: :runtime
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: !ruby/object:Gem::Requirement
|
27
|
+
none: false
|
28
|
+
requirements:
|
29
|
+
- - ! '>='
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: 3.2.0
|
32
|
+
- !ruby/object:Gem::Dependency
|
33
|
+
name: sqlite3
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
35
|
+
none: false
|
36
|
+
requirements:
|
37
|
+
- - ! '>='
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
type: :development
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: rspec-rails
|
50
|
+
requirement: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
type: :development
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ! '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
description:
|
65
|
+
email:
|
66
|
+
- cjaysson@gmail.com
|
67
|
+
- joel@thoughtbot.com
|
68
|
+
executables: []
|
69
|
+
extensions: []
|
70
|
+
extra_rdoc_files: []
|
71
|
+
files:
|
72
|
+
- app/controllers/griddler/emails_controller.rb
|
73
|
+
- config/initializers/griddler.rb
|
74
|
+
- config/routes.rb
|
75
|
+
- lib/griddler/configuration.rb
|
76
|
+
- lib/griddler/email.rb
|
77
|
+
- lib/griddler/email_format.rb
|
78
|
+
- lib/griddler/email_parser.rb
|
79
|
+
- lib/griddler/engine.rb
|
80
|
+
- lib/griddler/version.rb
|
81
|
+
- lib/griddler.rb
|
82
|
+
- lib/tasks/griddler_tasks.rake
|
83
|
+
- LICENSE
|
84
|
+
- Rakefile
|
85
|
+
- README.md
|
86
|
+
homepage: http://thoughtbot.com
|
87
|
+
licenses: []
|
88
|
+
post_install_message:
|
89
|
+
rdoc_options: []
|
90
|
+
require_paths:
|
91
|
+
- app
|
92
|
+
- lib
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ! '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
segments:
|
100
|
+
- 0
|
101
|
+
hash: -1174056135577180289
|
102
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
103
|
+
none: false
|
104
|
+
requirements:
|
105
|
+
- - ! '>='
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0'
|
108
|
+
segments:
|
109
|
+
- 0
|
110
|
+
hash: -1174056135577180289
|
111
|
+
requirements: []
|
112
|
+
rubyforge_project:
|
113
|
+
rubygems_version: 1.8.24
|
114
|
+
signing_key:
|
115
|
+
specification_version: 3
|
116
|
+
summary: SendGrid Parse API client Rails Engine
|
117
|
+
test_files: []
|