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 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
+ ![thoughtbot](http://thoughtbot.com/images/tm/logo.png)
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,6 @@
1
+ class Griddler::EmailsController < ActionController::Base
2
+ def create
3
+ Griddler::Email.new(params)
4
+ head :ok
5
+ end
6
+ end
@@ -0,0 +1 @@
1
+ require 'griddler'
data/config/routes.rb ADDED
@@ -0,0 +1,3 @@
1
+ Rails.application.routes.draw do
2
+ match '/email_processor' => 'griddler/emails#create', via: :post, as: :email_processor
3
+ end
@@ -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
@@ -0,0 +1,4 @@
1
+ module Griddler
2
+ class Engine < ::Rails::Engine
3
+ end
4
+ end
@@ -0,0 +1,3 @@
1
+ module Griddler
2
+ VERSION = "0.1.0"
3
+ end
data/lib/griddler.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'griddler/engine'
2
+ require 'griddler/email'
3
+ require 'griddler/email_format'
4
+ require 'griddler/email_parser'
5
+ require 'griddler/configuration'
6
+
7
+ module Griddler
8
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :griddler do
3
+ # # Task goes here
4
+ # end
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: []