ghost_in_the_post 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +24 -0
  4. data/lib/ghost_in_the_post.rb +47 -0
  5. data/lib/ghost_in_the_post/automatic.rb +19 -0
  6. data/lib/ghost_in_the_post/ghost_on_command.rb +10 -0
  7. data/lib/ghost_in_the_post/ghost_on_delivery.rb +12 -0
  8. data/lib/ghost_in_the_post/mail_ghost.rb +30 -0
  9. data/lib/ghost_in_the_post/mailer.rb +18 -0
  10. data/lib/ghost_in_the_post/phantom/staticize.js +27 -0
  11. data/lib/ghost_in_the_post/phantom_transform.rb +91 -0
  12. data/lib/ghost_in_the_post/version.rb +3 -0
  13. data/spec/dummy/Gemfile +3 -0
  14. data/spec/dummy/Gemfile.lock +115 -0
  15. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  16. data/spec/dummy/app/assets/javascripts/test.js +1 -0
  17. data/spec/dummy/app/mailers/auto_mailer.rb +13 -0
  18. data/spec/dummy/app/mailers/mailer.rb +13 -0
  19. data/spec/dummy/app/views/auto_mailer/normal_email.html.erb +13 -0
  20. data/spec/dummy/app/views/auto_mailer/normal_email.text +1 -0
  21. data/spec/dummy/app/views/mailer/normal_email.html.erb +12 -0
  22. data/spec/dummy/app/views/mailer/normal_email.text +1 -0
  23. data/spec/dummy/bin/bundle +3 -0
  24. data/spec/dummy/bin/rails +4 -0
  25. data/spec/dummy/bin/rake +4 -0
  26. data/spec/dummy/bin/setup +29 -0
  27. data/spec/dummy/config.ru +4 -0
  28. data/spec/dummy/config/application.rb +13 -0
  29. data/spec/dummy/config/boot.rb +5 -0
  30. data/spec/dummy/config/environment.rb +5 -0
  31. data/spec/dummy/config/environments/development.rb +11 -0
  32. data/spec/dummy/config/initializers/assets.rb +11 -0
  33. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  34. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  35. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  36. data/spec/dummy/config/initializers/ghost_in_the_post.rb +5 -0
  37. data/spec/dummy/config/initializers/inflections.rb +16 -0
  38. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  39. data/spec/dummy/config/initializers/session_store.rb +3 -0
  40. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  41. data/spec/dummy/config/routes.rb +2 -0
  42. data/spec/dummy/config/secrets.yml +22 -0
  43. data/spec/dummy/log/development.log +216 -0
  44. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/1hVihiFdBkDBsuCvA1I2vuFa9N6p-5zQ-qKvf4vz7jA.cache +0 -0
  45. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/23mzeUAO-pIzGJ1n84Dbg25udWeSiBcd6e_QsDihWQc.cache +3 -0
  46. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/6544SZAu2zCeDrdro4h3izdKNXVvHzstwz12UxeiJoA.cache +2 -0
  47. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/BMI0IB92xLhvUPcr_K0JuSk8zczmrEEbJCd3qwHauhQ.cache +1 -0
  48. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/GtqgDsC0JMsKfj0x5uQrbPcSS3QIPIzLKrq2fGJDBf0.cache +0 -0
  49. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/IFLV6zTaBXU1ccodbD3QJ4xUIOEix_Fbb7j8wsxrHw8.cache +1 -0
  50. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/Lc8lcXR-AibSZCIbx_imdfIOmD-zbZ_pwSNMs0o0Dos.cache +0 -0
  51. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/O9gjtBITAGlfzUwc2DT5N0GW2bo2b2yQ64gnQljLogM.cache +1 -0
  52. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/OQXwnC5z5STtd87Tr2dz1QBt4iZ8PoZTH8KBtBlUGnE.cache +0 -0
  53. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/OZ04c215C4Euu3scfhmxmte0o_tZOVWTqrv61cpy7H4.cache +1 -0
  54. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/_Bn81xMZj4dS_W5s9uSDhtEdpRZtsc21rcKEYAPCsZY.cache +0 -0
  55. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/_Cw52w24AWZEbxLmduyWBMA6Y1OmJiEm1VNn7pY4rtk.cache +2 -0
  56. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/_HO5Nyj_TzTxVwa4OOf_0UJfBCmjgkv7NsRU29AD5Oo.cache +0 -0
  57. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/eN_ueL7m-D9xkEZpaLKwCxdrNzyz-MW9jcdJ5TdF9j8.cache +1 -0
  58. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/efKfhZKxLC7ERXlMb5CQMqYwbvWS-A--pH-0fqzvRiQ.cache +1 -0
  59. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/goEv0RKoifagsjFSdEHY-23E9ybQn_t1HHEZq7PlKQI.cache +1 -0
  60. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/hZi1k6tpxxCGYxRe7zY74ItcOI8gZrREOpGuA8JSpGg.cache +0 -0
  61. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/n6_6RjvPX2rCv5dytbSnnDrGwAj1ZcPfninuNUeP_iE.cache +1 -0
  62. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/nXo-w2o4zQBM_uSUnniuP53t5erBbSZPecMED9YnzQI.cache +1 -0
  63. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/oB1njyQ78MOMRf_5TvuYzHleeI8eke5-FDIIVl7qas0.cache +1 -0
  64. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/pEhaat2KBd5SrT7szC_8R1_6hK17FTpvoRFkmCRSD3M.cache +0 -0
  65. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/qxbw0jMkxTNeM5T2QovOD9vTyvjHVjBN5tZdwBf519A.cache +0 -0
  66. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/y98HqYF9nxOk8t_j3BKv9Nc4LuSAEzhER7rCCvOM6xI.cache +1 -0
  67. data/spec/lib/ghost_in_the_post/automatic_spec.rb +42 -0
  68. data/spec/lib/ghost_in_the_post/ghost_on_command_spec.rb +26 -0
  69. data/spec/lib/ghost_in_the_post/mail_ghost_spec.rb +73 -0
  70. data/spec/lib/ghost_in_the_post/mailer_spec.rb +38 -0
  71. data/spec/lib/ghost_in_the_post/phantom_transform_spec.rb +134 -0
  72. data/spec/lib/ghost_in_the_post_spec.rb +60 -0
  73. data/spec/spec_helper.rb +12 -0
  74. data/spec/support/mailer_support.rb +23 -0
  75. data/spec/support/quiet_load.rb +6 -0
  76. metadata +238 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d5264602db0f4c3dff7764e85b40b297046a9a55
4
+ data.tar.gz: 289c490672985941a51227c4a9133ec1ba9e175c
5
+ SHA512:
6
+ metadata.gz: c7b6aca158ff83c738d9cb85d01d7011d9cae5d8e47a8f5ed6650dc394086a216217ab2e7c557796fbe02583b0c6489d959caa90c00e3ff1376bc3c38aa20f75
7
+ data.tar.gz: 40ae64bdf2f34875db66564f55de75b23563ed87595fe10102fc894e78ea091abf67e0d0c9572106c65fc1e358a74f0be406d1a19bf9d44737eb481f1ddc9fb2
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2016 Tim Anema
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,24 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+ require 'rdoc/task'
7
+ require "bundler/gem_tasks"
8
+
9
+ desc "Run specs"
10
+ task :spec do
11
+ sh "bundle exec rspec -f progress"
12
+ end
13
+
14
+ RDoc::Task.new(:rdoc) do |rdoc|
15
+ rdoc.rdoc_dir = 'rdoc'
16
+ rdoc.title = 'GhostInThePost'
17
+ rdoc.options << '--line-numbers'
18
+ rdoc.rdoc_files.include('README.rdoc')
19
+ rdoc.rdoc_files.include('lib/**/*.rb')
20
+ end
21
+
22
+ Bundler::GemHelper.install_tasks
23
+
24
+ task :default => :spec
@@ -0,0 +1,47 @@
1
+ require 'rails'
2
+ module GhostInThePost
3
+ ATTRIBUTE_NAMES = [
4
+ :phantomjs_path,
5
+ :includes,
6
+ :remove_js_tags,
7
+ ]
8
+ private_constant :ATTRIBUTE_NAMES
9
+ cattr_reader(*ATTRIBUTE_NAMES)
10
+
11
+ @@phantomjs_path = nil #setting this to nil helps testing
12
+ @@includes = []
13
+ @@remove_js_tags = true
14
+
15
+ def self.config=(new_config={})
16
+ self.complain_about_unknown_keys(new_config.keys)
17
+ @@phantomjs_path = new_config[:phantomjs_path]
18
+ @@includes = Array(new_config[:includes])
19
+ @@remove_js_tags = new_config[:remove_js_tags].nil? ? true : new_config[:remove_js_tags]
20
+ raise ArgumentError, "GhostInThePost.config.phantomjs_path is not set" if self.phantomjs_path.nil?
21
+ end
22
+
23
+ def self.phantomjs_path
24
+ @@phantomjs_path or raise ArgumentError, "GhostInThePost.config.phantomjs_path is not set"
25
+ end
26
+
27
+ private
28
+
29
+ def self.complain_about_unknown_keys(keys)
30
+ invalid_keys = keys - ATTRIBUTE_NAMES
31
+ if invalid_keys.size > 0
32
+ raise ArgumentError, "Unknown configuration parameters: #{invalid_keys}", caller(1)
33
+ end
34
+ end
35
+
36
+ end
37
+
38
+ require "ghost_in_the_post/version"
39
+
40
+ require "ghost_in_the_post/phantom_transform"
41
+ require "ghost_in_the_post/mail_ghost"
42
+
43
+ require "ghost_in_the_post/ghost_on_command"
44
+ require "ghost_in_the_post/ghost_on_delivery"
45
+
46
+ require "ghost_in_the_post/mailer"
47
+ require "ghost_in_the_post/automatic"
@@ -0,0 +1,19 @@
1
+ module GhostInThePost
2
+ module Automatic
3
+ attr_accessor :included_scripts
4
+
5
+ def include_script(*paths)
6
+ @included_scripts ||= []
7
+ @included_scripts += paths
8
+ end
9
+
10
+ def mail(*args, &block)
11
+ super.tap do |email|
12
+ email.extend GhostOnCommand
13
+ email.extend GhostOnDelivery
14
+ email.included_scripts = @included_scripts
15
+ end
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,10 @@
1
+ module GhostInThePost
2
+ module GhostOnCommand
3
+ attr_accessor :included_scripts
4
+
5
+ def ghost
6
+ MailGhost.new(self, included_scripts).execute
7
+ end
8
+ end
9
+ end
10
+
@@ -0,0 +1,12 @@
1
+ module GhostInThePost
2
+ module GhostOnDelivery
3
+ def deliver
4
+ ghost
5
+ super
6
+ end
7
+ def deliver!
8
+ ghost
9
+ super
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,30 @@
1
+ module GhostInThePost
2
+ class MailGhost
3
+ attr_reader :email, :included_scripts
4
+
5
+ def initialize(email, included_scripts)
6
+ @email = email
7
+ @included_scripts = Array(included_scripts).compact
8
+ end
9
+
10
+ def execute
11
+ improve_body if email.content_type =~ /^text\/html/
12
+ improve_html_part(email.html_part) if email.html_part
13
+ email
14
+ end
15
+
16
+ private
17
+
18
+ def improve_body
19
+ email.body = ghost_html(email.body.decoded)
20
+ end
21
+
22
+ def improve_html_part(html_part)
23
+ html_part.body = ghost_html(html_part.body.decoded)
24
+ end
25
+
26
+ def ghost_html(old_html)
27
+ PhantomTransform.new(old_html, included_scripts).transform
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,18 @@
1
+ module GhostInThePost
2
+ module Mailer
3
+ attr_accessor :included_scripts
4
+
5
+ def include_script(*paths)
6
+ @included_scripts ||= []
7
+ @included_scripts += paths
8
+ end
9
+
10
+ def mail(*args, &block)
11
+ super.tap do |email|
12
+ email.extend GhostOnCommand
13
+ email.included_scripts = included_scripts
14
+ end
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ var system = require('system'),
3
+ page = require('webpage').create(),
4
+ html = system.args[1], //the email to be processed
5
+ no_script = system.args[2] == "true",
6
+ include = system.args[3]; //the injected js file
7
+
8
+ page.onLoadFinished = function(status) {
9
+ //load included scripts to be loaded on the dom
10
+ if(!!include){
11
+ page.injectJs(include);
12
+ }
13
+ if(no_script) {
14
+ page.evaluate(function(){
15
+ var script_tags = document.getElementsByTagName("script");
16
+ for(var i = 0; i < script_tags.length; i++) {
17
+ script_tags[i].parentNode.removeChild(script_tags[i]);
18
+ }
19
+ })
20
+ }
21
+ //write out to console to export contents
22
+ console.log(page.content);
23
+ phantom.exit(); //get out of here
24
+ };
25
+ //load html content
26
+ page.setContent(html, 'http://www.whatever.com');
27
+
@@ -0,0 +1,91 @@
1
+ module GhostInThePost
2
+ class PhantomTransform
3
+ PHANTOMJS_SCRIPT = File.expand_path('../phantom/staticize.js', __FILE__)
4
+
5
+ def initialize(html, included_scripts=[])
6
+ @html = html
7
+ @included_scripts = Array(included_scripts).compact
8
+ end
9
+
10
+ def transform
11
+ injectable_scripts.any? ? transform_with_injections : simple_transform
12
+ end
13
+
14
+ private
15
+
16
+ def command
17
+ [
18
+ GhostInThePost.phantomjs_path,
19
+ PHANTOMJS_SCRIPT,
20
+ @html,
21
+ GhostInThePost.remove_js_tags
22
+ ]
23
+ end
24
+
25
+ #there is scripts to inject so create a tmp file to put them all in
26
+ def transform_with_injections
27
+ #set output to just html so that if there is an error that is caught it will
28
+ #at least return valid html
29
+ output = @html
30
+ begin
31
+ #generate a tempfile with all the js that we need so that phantom can inject
32
+ #easily and not have to max out a single command
33
+ file = Tempfile.new(['inject', '.js'])
34
+ injectable_scripts.map do |script|
35
+ asset = find_asset_in_pipeline(script)
36
+ file.write(asset.to_s) unless asset.nil?
37
+ end.compact
38
+ file.close #closing the file makes it accessible by phantom
39
+
40
+ #generate the html with the javascript
41
+ output = IO.popen(command + [file.path]){|io| io.read}
42
+
43
+ file.unlink
44
+ rescue => e
45
+ #clean up the temp file
46
+ file.unlink
47
+ end
48
+ output
49
+ end
50
+
51
+ #no scripts to inject so just run the command
52
+ def simple_transform
53
+ IO.popen(command){|io| io.read}
54
+ end
55
+
56
+ def injectable_scripts
57
+ @injectable_scripts ||= (GhostInThePost.includes + @included_scripts)
58
+ end
59
+
60
+ def find_asset_in_pipeline(name)
61
+ normalized_name = normalize_asset_name(name)
62
+ Rails.application.assets[normalized_name] || Rails.application.assets[remove_asset_digest(normalized_name)]
63
+ end
64
+
65
+ def normalize_asset_name(href)
66
+ remove_asset_prefix(href.split('?').first)
67
+ end
68
+
69
+ DIGEST_PATTERN = /
70
+ - # Digest comes after a dash
71
+ (?:
72
+ [a-z0-9]{32} | # Old style is 32 character hash
73
+ [a-z0-9]{64} # New style is 64 characters
74
+ )
75
+ \. # Dot for the file extension
76
+ /x.freeze
77
+
78
+ def remove_asset_digest(path)
79
+ path.gsub(DIGEST_PATTERN, '.')
80
+ end
81
+
82
+ def remove_asset_prefix(path)
83
+ path.sub(Regexp.new("^#{Regexp.quote(asset_prefix)}/?"), "")
84
+ end
85
+
86
+ def asset_prefix
87
+ ::Rails.application.try(:config).try(:assets).try(:prefix) || "/assets"
88
+ end
89
+
90
+ end
91
+ end
@@ -0,0 +1,3 @@
1
+ module GhostInThePost
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+ gem 'rails', '4.2.3'
3
+ gem 'ghost_in_the_post', :path => '../..'
@@ -0,0 +1,115 @@
1
+ PATH
2
+ remote: ../..
3
+ specs:
4
+ ghost_in_the_post (0.0.1)
5
+ rails (~> 4.2.3)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ actionmailer (4.2.3)
11
+ actionpack (= 4.2.3)
12
+ actionview (= 4.2.3)
13
+ activejob (= 4.2.3)
14
+ mail (~> 2.5, >= 2.5.4)
15
+ rails-dom-testing (~> 1.0, >= 1.0.5)
16
+ actionpack (4.2.3)
17
+ actionview (= 4.2.3)
18
+ activesupport (= 4.2.3)
19
+ rack (~> 1.6)
20
+ rack-test (~> 0.6.2)
21
+ rails-dom-testing (~> 1.0, >= 1.0.5)
22
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
23
+ actionview (4.2.3)
24
+ activesupport (= 4.2.3)
25
+ builder (~> 3.1)
26
+ erubis (~> 2.7.0)
27
+ rails-dom-testing (~> 1.0, >= 1.0.5)
28
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
29
+ activejob (4.2.3)
30
+ activesupport (= 4.2.3)
31
+ globalid (>= 0.3.0)
32
+ activemodel (4.2.3)
33
+ activesupport (= 4.2.3)
34
+ builder (~> 3.1)
35
+ activerecord (4.2.3)
36
+ activemodel (= 4.2.3)
37
+ activesupport (= 4.2.3)
38
+ arel (~> 6.0)
39
+ activesupport (4.2.3)
40
+ i18n (~> 0.7)
41
+ json (~> 1.7, >= 1.7.7)
42
+ minitest (~> 5.1)
43
+ thread_safe (~> 0.3, >= 0.3.4)
44
+ tzinfo (~> 1.1)
45
+ arel (6.0.3)
46
+ builder (3.2.2)
47
+ concurrent-ruby (1.0.0)
48
+ concurrent-ruby (1.0.0-java)
49
+ erubis (2.7.0)
50
+ globalid (0.3.6)
51
+ activesupport (>= 4.1.0)
52
+ i18n (0.7.0)
53
+ json (1.8.3)
54
+ json (1.8.3-java)
55
+ loofah (2.0.3)
56
+ nokogiri (>= 1.5.9)
57
+ mail (2.6.3)
58
+ mime-types (>= 1.16, < 3)
59
+ mime-types (2.99)
60
+ mini_portile2 (2.0.0)
61
+ minitest (5.8.4)
62
+ nokogiri (1.6.7.2)
63
+ mini_portile2 (~> 2.0.0.rc2)
64
+ nokogiri (1.6.7.2-java)
65
+ rack (1.6.4)
66
+ rack-test (0.6.3)
67
+ rack (>= 1.0)
68
+ rails (4.2.3)
69
+ actionmailer (= 4.2.3)
70
+ actionpack (= 4.2.3)
71
+ actionview (= 4.2.3)
72
+ activejob (= 4.2.3)
73
+ activemodel (= 4.2.3)
74
+ activerecord (= 4.2.3)
75
+ activesupport (= 4.2.3)
76
+ bundler (>= 1.3.0, < 2.0)
77
+ railties (= 4.2.3)
78
+ sprockets-rails
79
+ rails-deprecated_sanitizer (1.0.3)
80
+ activesupport (>= 4.2.0.alpha)
81
+ rails-dom-testing (1.0.7)
82
+ activesupport (>= 4.2.0.beta, < 5.0)
83
+ nokogiri (~> 1.6.0)
84
+ rails-deprecated_sanitizer (>= 1.0.1)
85
+ rails-html-sanitizer (1.0.3)
86
+ loofah (~> 2.0)
87
+ railties (4.2.3)
88
+ actionpack (= 4.2.3)
89
+ activesupport (= 4.2.3)
90
+ rake (>= 0.8.7)
91
+ thor (>= 0.18.1, < 2.0)
92
+ rake (10.5.0)
93
+ sprockets (3.5.2)
94
+ concurrent-ruby (~> 1.0)
95
+ rack (> 1, < 3)
96
+ sprockets-rails (3.0.1)
97
+ actionpack (>= 4.0)
98
+ activesupport (>= 4.0)
99
+ sprockets (>= 3.0.0)
100
+ thor (0.19.1)
101
+ thread_safe (0.3.5)
102
+ thread_safe (0.3.5-java)
103
+ tzinfo (1.2.2)
104
+ thread_safe (~> 0.1)
105
+
106
+ PLATFORMS
107
+ java
108
+ ruby
109
+
110
+ DEPENDENCIES
111
+ ghost_in_the_post!
112
+ rails (= 4.2.3)
113
+
114
+ BUNDLED WITH
115
+ 1.10.6