ar_mailer_revised 0.1 → 1.0.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 +4 -4
- data/.travis.yml +16 -0
- data/README.md +54 -70
- data/ar_mailer_revised.gemspec +7 -2
- data/bin/ar_sendmail +2 -7
- data/lib/action_mailer/ar_mailer.rb +45 -18
- data/lib/ar_mailer_revised.rb +3 -2
- data/lib/ar_mailer_revised/email_scaffold.rb +17 -11
- data/lib/ar_mailer_revised/helpers/command_line.rb +198 -0
- data/lib/ar_mailer_revised/helpers/general.rb +81 -0
- data/lib/ar_mailer_revised/mailman.rb +263 -0
- data/lib/ar_mailer_revised/version.rb +1 -1
- data/lib/generators/ar_mailer_revised/install_generator.rb +37 -0
- data/{generators → lib/generators}/ar_mailer_revised/templates/migration.rb +0 -0
- data/{generators → lib/generators}/ar_mailer_revised/templates/model.rb +1 -0
- data/test/dummy/README.rdoc +28 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/images/.keep +0 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/controllers/concerns/.keep +0 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/mailers/.keep +0 -0
- data/test/dummy/app/mailers/test_mailer.rb +32 -0
- data/test/dummy/app/models/.keep +0 -0
- data/test/dummy/app/models/concerns/.keep +0 -0
- data/test/dummy/app/models/email.rb +31 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +23 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +37 -0
- data/test/dummy/config/environments/production.rb +83 -0
- data/test/dummy/config/environments/test.rb +34 -0
- data/{generators/ar_mailer_revised/templates/initializer.rb → test/dummy/config/initializers/ar_mailer_revised.rb} +2 -2
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +56 -0
- data/test/dummy/config/secrets.yml +22 -0
- data/test/dummy/db/development.sqlite3 +0 -0
- data/test/dummy/db/migrate/20140518140150_create_emails.rb +35 -0
- data/test/dummy/db/schema.rb +28 -0
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/lib/assets/.keep +0 -0
- data/test/dummy/log/.keep +0 -0
- data/test/dummy/log/development.log +119 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/test/mailers/previews/test_mailer_preview.rb +4 -0
- data/test/dummy/test/mailers/test_mailer_test.rb +54 -0
- data/test/generators/install_generator_test.rb +14 -0
- metadata +184 -12
- data/generators/ar_mailer_revised/ar_mailer_revised_generator.rb +0 -23
@@ -0,0 +1,81 @@
|
|
1
|
+
#
|
2
|
+
# This module contains helper functionality for the Mailman class
|
3
|
+
# that handles the actual email sending as a batch process.
|
4
|
+
#
|
5
|
+
# @author Stefan Exner <stex@sterex.de>
|
6
|
+
#
|
7
|
+
|
8
|
+
require 'log4r'
|
9
|
+
|
10
|
+
module ArMailerRevised
|
11
|
+
module Helpers
|
12
|
+
module General
|
13
|
+
#
|
14
|
+
# Generates a logger object using Log4r
|
15
|
+
# The output file is determined by +#log_file+
|
16
|
+
#
|
17
|
+
# If the custom log file path is set to +stdout+ or +stderr+,
|
18
|
+
# these are used instead of a log file.
|
19
|
+
#
|
20
|
+
# @return [Log4r::Logger] the file output logger
|
21
|
+
#
|
22
|
+
def logger
|
23
|
+
unless @logger
|
24
|
+
@logger = Log4r::Logger.new 'ar_mailer'
|
25
|
+
|
26
|
+
if %w[stdout stderr].include?(@options[:log_file])
|
27
|
+
outputter = Log4r::Outputter.send(@options[:log_file])
|
28
|
+
else
|
29
|
+
outputter = Log4r::FileOutputter.new('ar_mailer_log', :filename => log_file)
|
30
|
+
end
|
31
|
+
|
32
|
+
outputter.formatter = Log4r::PatternFormatter.new(:pattern => '[%5l - %c] %d :: %m')
|
33
|
+
@logger.outputters = outputter
|
34
|
+
|
35
|
+
@logger.level = log_level
|
36
|
+
end
|
37
|
+
@logger
|
38
|
+
end
|
39
|
+
|
40
|
+
#
|
41
|
+
# Determines the correct log file location
|
42
|
+
# It defaults to the current environment's log file
|
43
|
+
# @todo Check if that interferes with Rails' logging process
|
44
|
+
#
|
45
|
+
# @return [String] Path to the logfile
|
46
|
+
#
|
47
|
+
def log_file
|
48
|
+
@log_file ||= @options[:log_file] ? File.expand_path(@options[:log_file]) : File.join(Rails.root, 'log', "#{rails_environment}.log")
|
49
|
+
end
|
50
|
+
|
51
|
+
#
|
52
|
+
# Determines the correct log level from the given script arguments
|
53
|
+
# Defaults to +INFO+
|
54
|
+
#
|
55
|
+
# @return [Int] a log level from +Log4r+
|
56
|
+
#
|
57
|
+
def log_level
|
58
|
+
@log_level ||= "Log4r::#{@options[:log_level].upcase}".constantize
|
59
|
+
end
|
60
|
+
|
61
|
+
#
|
62
|
+
# @return [String] the currently active rails environment
|
63
|
+
#
|
64
|
+
def rails_environment
|
65
|
+
ENV['RAILS_ENV']
|
66
|
+
end
|
67
|
+
|
68
|
+
#
|
69
|
+
# Checks if the given environment is currently active
|
70
|
+
# Works like Rails.env.env?
|
71
|
+
#
|
72
|
+
# @param [String, Symbol] env
|
73
|
+
# The environment name
|
74
|
+
#
|
75
|
+
# @return [Bool] +true+ if the current environment matches the given
|
76
|
+
def rails_environment?(env)
|
77
|
+
rails_environment.to_s == env.to_s
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,263 @@
|
|
1
|
+
#
|
2
|
+
# This class handles the actual email sending.
|
3
|
+
# It is called by the +ar_sendmail+ executable in /bin
|
4
|
+
# with command line arguments
|
5
|
+
#
|
6
|
+
# @author Stefan Exner
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'net/smtp'
|
10
|
+
require 'ar_mailer_revised/version'
|
11
|
+
require 'ar_mailer_revised/helpers/command_line'
|
12
|
+
require 'ar_mailer_revised/helpers/general'
|
13
|
+
|
14
|
+
module ArMailerRevised
|
15
|
+
class Mailman
|
16
|
+
include ArMailerRevised::Helpers::General
|
17
|
+
include ArMailerRevised::Helpers::CommandLine
|
18
|
+
|
19
|
+
#
|
20
|
+
# Simply holds a copy of the options given in from command line
|
21
|
+
#
|
22
|
+
def initialize(options = {})
|
23
|
+
@options = options
|
24
|
+
end
|
25
|
+
|
26
|
+
def run
|
27
|
+
logger.debug 'ArMailerRevised initialized with the following options:'
|
28
|
+
logger.debug Hirb::Helpers::AutoTable.render @options
|
29
|
+
|
30
|
+
deliver_emails
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
#
|
36
|
+
# Performs a single email sending for the given batch size
|
37
|
+
# Only emails which are ready for sending are actually sent.
|
38
|
+
# "Ready for sending" means in this case, that +delivery_time+ is +nil+
|
39
|
+
# or set to a time which is <= Time.now
|
40
|
+
#
|
41
|
+
# Take a look at +EmailScaffold+ for more information
|
42
|
+
# about the used scopes
|
43
|
+
#
|
44
|
+
# @todo: Check if we should delete emails which cause SMTPFatalErrors
|
45
|
+
# @todo: Probably add better error handling than simple re-tries
|
46
|
+
#
|
47
|
+
def deliver_emails
|
48
|
+
total_mail_count = ArMailerRevised.email_class.ready_to_deliver.count
|
49
|
+
emails = ArMailerRevised.email_class.ready_to_deliver.with_batch_size(@options[:batch_size])
|
50
|
+
|
51
|
+
if emails.empty?
|
52
|
+
logger.info 'No emails to be sent, existing'
|
53
|
+
return
|
54
|
+
end
|
55
|
+
|
56
|
+
logger.info "Starting batch sending process, sending #{emails.count} / #{total_mail_count} mails"
|
57
|
+
|
58
|
+
group_emails_by_settings(emails).each do |settings_hash, grouped_emails|
|
59
|
+
setting = OpenStruct.new(settings_hash)
|
60
|
+
logger.info "Using setting #{setting.address}:#{setting.port}/#{setting.user_name}"
|
61
|
+
|
62
|
+
smtp = Net::SMTP.new(setting.address, setting.port)
|
63
|
+
smtp.open_timeout = 10
|
64
|
+
smtp.read_timeout = 10
|
65
|
+
setup_tls(smtp, setting)
|
66
|
+
|
67
|
+
#Connect to the server and handle possible errors
|
68
|
+
begin
|
69
|
+
smtp.start(setting.domain, setting.user_name, setting.password, setting.authentication) do
|
70
|
+
grouped_emails.each do |email|
|
71
|
+
send_email(smtp, email)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
rescue Net::SMTPAuthenticationError => e
|
75
|
+
handle_smtp_authentication_error(setting, e, grouped_emails)
|
76
|
+
rescue Net::SMTPServerBusy => e
|
77
|
+
logger.warn 'Server is busy, trying again next batch.'
|
78
|
+
logger.warn 'Complete Error: ' + e.to_s
|
79
|
+
rescue Net::OpenTimeout, Net::ReadTimeout => e
|
80
|
+
handle_smtp_timeout(setting, e, grouped_emails)
|
81
|
+
rescue Net::SMTPSyntaxError, Net::SMTPFatalError, Net::SMTPUnknownError => e
|
82
|
+
#TODO: Should we remove the custom SMTP settings here as well?
|
83
|
+
logger.warn 'Other SMTP error, trying again next batch.'
|
84
|
+
logger.warn 'Complete Error: ' + e.to_s
|
85
|
+
rescue Exception => e
|
86
|
+
logger.warn 'Other Error, trying again next batch.'
|
87
|
+
logger.warn 'Complete Error: ' + e.to_s
|
88
|
+
puts e.backtrace
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
#
|
94
|
+
# As there may be multiple emails using the same SMTP settings,
|
95
|
+
# it would just slow down the sending having to connect to the server
|
96
|
+
# multiple times. Therefore, all emails with the same settings
|
97
|
+
# are grouped together.
|
98
|
+
#
|
99
|
+
# @param [Array<Email>] emails
|
100
|
+
# Emails to be grouped together
|
101
|
+
#
|
102
|
+
# @return [Hash<Setting, Email>]
|
103
|
+
# Hash mapping SMTP settings to emails.
|
104
|
+
# All emails which did not have custom SMTP settings are
|
105
|
+
# grouped together under the default SMTP settings.
|
106
|
+
#
|
107
|
+
def group_emails_by_settings(emails)
|
108
|
+
emails.inject({}) do |hash, email|
|
109
|
+
setting = ActionMailer::Base.smtp_settings
|
110
|
+
|
111
|
+
if email.smtp_settings
|
112
|
+
setting = email.smtp_settings.clone
|
113
|
+
setting[:custom_setting] = true
|
114
|
+
end
|
115
|
+
|
116
|
+
hash[setting] ||= []
|
117
|
+
hash[setting] << email
|
118
|
+
|
119
|
+
hash
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
#
|
124
|
+
# Sets the wished TLS / StartTLS options in the
|
125
|
+
# given SMTP instance, based on what the user defined
|
126
|
+
# in his application's / the email's SMTP settings.
|
127
|
+
#
|
128
|
+
# Available Settings are (descending importance, meaning that
|
129
|
+
# a higher importance setting will override a lower importance setting)
|
130
|
+
#
|
131
|
+
# 1. +:enable_starttls_auto+ enables STARTTLS if the serves is capable to handle it
|
132
|
+
# 2. +:enable_starttls+ forces the usage of STARTTLS, whether the server is capable of it or not
|
133
|
+
# 3. +:tls+ forces the usage of TLS (SSL SMTP)
|
134
|
+
#
|
135
|
+
def setup_tls(smtp, setting)
|
136
|
+
if setting.enable_starttls_auto
|
137
|
+
logger.debug 'Using STARTTLS, if the server accepts it'
|
138
|
+
smtp.enable_starttls_auto
|
139
|
+
elsif setting.enable_starttls
|
140
|
+
logger.debug 'Forcing STARTTLS'
|
141
|
+
smtp.enable_starttls
|
142
|
+
elsif setting.tls
|
143
|
+
logger.debug 'Forcing TLS'
|
144
|
+
smtp.enable_tls
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
#
|
149
|
+
# Performs an email sending attempt
|
150
|
+
#
|
151
|
+
# @param [Net::SMTP] smtp
|
152
|
+
# The SMTP connection which already has to be established
|
153
|
+
#
|
154
|
+
# @param [Email]
|
155
|
+
# The email record to be sent.
|
156
|
+
#
|
157
|
+
# Error handling works as follows:
|
158
|
+
#
|
159
|
+
# - If the server is busy while sending the email (SMTPServerBusy),
|
160
|
+
# the system will leave the email at its old place in the queue and try
|
161
|
+
# again next batch as we simply assume that the server failure is just temporarily
|
162
|
+
# and the email will not cause the whole email sending to stagnate
|
163
|
+
#
|
164
|
+
# - If another error occurs, the system will adjust the last_send_attempt
|
165
|
+
# in the email record and therefore move it to the end of the queue to
|
166
|
+
# ensure that other (working) emails are sent without being held up
|
167
|
+
# in the queue by this probably malformed one.
|
168
|
+
#
|
169
|
+
# Errors are logged with the :warn level.
|
170
|
+
#
|
171
|
+
def send_email(smtp, email)
|
172
|
+
logger.info "Sending Email ##{email.id}"
|
173
|
+
smtp.send_message(email.mail, email.from, email.to)
|
174
|
+
email.destroy
|
175
|
+
rescue Net::SMTPServerBusy => e
|
176
|
+
logger.warn 'Server is currently busy, trying again next batch'
|
177
|
+
logger.warn 'Complete Error: ' + e.to_s
|
178
|
+
rescue Net::SMTPSyntaxError, Net::SMTPFatalError, Net::SMTPUnknownError, Net::ReadTimeout => e
|
179
|
+
logger.warn 'Other exception, trying again next batch: ' + e.to_s
|
180
|
+
adjust_last_send_attempt!(email)
|
181
|
+
end
|
182
|
+
|
183
|
+
#-----------------------------------------------------------------
|
184
|
+
# SMTP connection error handling
|
185
|
+
# These errors happen directly when connecting to the SMTP server
|
186
|
+
#-----------------------------------------------------------------
|
187
|
+
|
188
|
+
#
|
189
|
+
# Handles Net::OpenTimeout and Net::ReadTimeout occurring
|
190
|
+
# while connecting to an SMTP server.
|
191
|
+
#
|
192
|
+
# If the setting was a custom SMTP setting, it will be removed from
|
193
|
+
# all given emails - but only if it failed before.
|
194
|
+
# With this, each email setting gets 2 tries.
|
195
|
+
#
|
196
|
+
# @param [OpenStruct] setting
|
197
|
+
# The used SMTP settings
|
198
|
+
#
|
199
|
+
# @param [Exception] exception
|
200
|
+
# The exception thrown
|
201
|
+
#
|
202
|
+
# @param [Array<Email>] emails
|
203
|
+
# All emails to be delivered using this system (in the current batch)
|
204
|
+
#
|
205
|
+
def handle_smtp_timeout(setting, exception, emails)
|
206
|
+
logger.warn "SMTP connection timeout while connecting to '#{setting.address}:#{setting.port}'"
|
207
|
+
logger.warn 'Complete Error: ' + exception.to_s
|
208
|
+
|
209
|
+
if setting.custom_setting
|
210
|
+
emails.each do |email|
|
211
|
+
if email.previously_attempted?
|
212
|
+
logger.warn 'Setting default SMTP settings for all affected emails, they will be sent next batch.'
|
213
|
+
remove_custom_smtp_settings!(email)
|
214
|
+
else
|
215
|
+
adjust_last_send_attempt!(email)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
#
|
222
|
+
# Handles authentication errors occuring while connecting to an SMTP server.
|
223
|
+
# @see #handle_smtp_timeout
|
224
|
+
#
|
225
|
+
# The main difference is, that custom SMTP settings will be deleted directly
|
226
|
+
# as it isn't very likely that time will solve the error.
|
227
|
+
#
|
228
|
+
def handle_smtp_authentication_error(setting, exception, emails)
|
229
|
+
logger.warn "SMTP authentication error while connecting to '#{setting.host}:#{setting.port}'"
|
230
|
+
logger.warn 'Complete Error: ' + exception.to_s
|
231
|
+
|
232
|
+
if setting.custom_setting
|
233
|
+
logger.warn 'Setting default SMTP settings for all affected emails, they will be sent next batch.'
|
234
|
+
|
235
|
+
if setting.custom_setting
|
236
|
+
emails.each { |email| remove_custom_smtp_settings!(email) }
|
237
|
+
end
|
238
|
+
else
|
239
|
+
logger.error "Your application's base setting ('#{setting.host}:#{setting.port}') produced an authentication error!"
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
#
|
244
|
+
# Adjusts the last send attempt timestamp in the given
|
245
|
+
# email to the current time.
|
246
|
+
#
|
247
|
+
def adjust_last_send_attempt!(email)
|
248
|
+
logger.info "Setting last send attempt for email ##{email.id} (was: #{email.last_send_attempt})"
|
249
|
+
email.last_send_attempt = Time.now.to_i
|
250
|
+
email.save(:validate => false)
|
251
|
+
end
|
252
|
+
|
253
|
+
#
|
254
|
+
# Removes the custom smtp settings from a given email record
|
255
|
+
# and saves it without validations
|
256
|
+
#
|
257
|
+
def remove_custom_smtp_settings!(email)
|
258
|
+
logger.info "Removing custom SMTP settings (#{email.smtp_settings[:address]}:#{email.smtp_settings[:port]}) for email ##{email.id}"
|
259
|
+
email.smtp_settings = nil
|
260
|
+
email.save(:validate => false)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module ArMailerRevised
|
2
|
+
module Generators
|
3
|
+
class InstallGenerator < Rails::Generators::Base
|
4
|
+
include Rails::Generators::Migration
|
5
|
+
|
6
|
+
source_root File.expand_path('../templates', __FILE__)
|
7
|
+
|
8
|
+
argument :model_name, :type => :string, :default => "Email"
|
9
|
+
|
10
|
+
def self.next_migration_number(path)
|
11
|
+
if @prev_migration_nr
|
12
|
+
@prev_migration_nr += 1
|
13
|
+
else
|
14
|
+
@prev_migration_nr = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
|
15
|
+
end
|
16
|
+
@prev_migration_nr.to_s
|
17
|
+
end
|
18
|
+
|
19
|
+
desc 'Installs everything necessary'
|
20
|
+
def create_install
|
21
|
+
template 'model.rb', "app/models/#{model_name.classify.underscore}.rb"
|
22
|
+
migration_template 'migration.rb', "db/migrate/create_#{model_name.classify.underscore.pluralize}.rb"
|
23
|
+
|
24
|
+
initializer 'ar_mailer_revised.rb', <<INIT
|
25
|
+
ArMailerRevised.configuration do |config|
|
26
|
+
|
27
|
+
#The model your application is using for email sending.
|
28
|
+
#If you created it using the ArMailerRevised generator, the below
|
29
|
+
#model name should already be correct.
|
30
|
+
config.email_class = #{model_name}
|
31
|
+
|
32
|
+
end
|
33
|
+
INIT
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
File without changes
|
@@ -21,6 +21,7 @@
|
|
21
21
|
# Serialized Hash storing custom SMTP settings just for this email.
|
22
22
|
# If this value is +nil+, the system will use the default SMTP settings set up in the application
|
23
23
|
#
|
24
|
+
|
24
25
|
class <%= model_name.classify %> < ActiveRecord::Base
|
25
26
|
#Helper methods and named scopes provided by ArMailerRevised
|
26
27
|
include ArMailerRevised::EmailScaffold
|
@@ -0,0 +1,28 @@
|
|
1
|
+
== README
|
2
|
+
|
3
|
+
This README would normally document whatever steps are necessary to get the
|
4
|
+
application up and running.
|
5
|
+
|
6
|
+
Things you may want to cover:
|
7
|
+
|
8
|
+
* Ruby version
|
9
|
+
|
10
|
+
* System dependencies
|
11
|
+
|
12
|
+
* Configuration
|
13
|
+
|
14
|
+
* Database creation
|
15
|
+
|
16
|
+
* Database initialization
|
17
|
+
|
18
|
+
* How to run the test suite
|
19
|
+
|
20
|
+
* Services (job queues, cache servers, search engines, etc.)
|
21
|
+
|
22
|
+
* Deployment instructions
|
23
|
+
|
24
|
+
* ...
|
25
|
+
|
26
|
+
|
27
|
+
Please feel free to use a different markup language if you do not plan to run
|
28
|
+
<tt>rake doc:app</tt>.
|
data/test/dummy/Rakefile
ADDED
File without changes
|
@@ -0,0 +1,13 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// compiled file.
|
9
|
+
//
|
10
|
+
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
|
11
|
+
// about supported directives.
|
12
|
+
//
|
13
|
+
//= require_tree .
|