Class: GitCommitNotifier::Emailer

Inherits:
Object
  • Object
show all
Defined in:
lib/git_commit_notifier/emailer.rb

Overview

Represents email sender.

Constant Summary collapse

DEFAULT_STYLESHEET_PATH =

Default CSS stylesheet file path

File.join(File.dirname(__FILE__), *'../../template/styles.css'.split('/')).freeze
TEMPLATE =

Default ERB template file path

File.join(File.dirname(__FILE__), *'../../template/email.html.erb'.split('/')).freeze
PARAMETERS =

Instance variable names

%w[
  project_path
  recipient
  from_address
  from_alias
  reply_to_address
  commit_date
  current_date
  offset_date
  subject
  text_message
  html_message
  repo_name
  ref_name
  old_rev
  new_rev
  message_link
].freeze
CHARS_NEEDING_QUOTING =

A quick-and-dirty regexp for determining whether a string contains any characters that need escaping.

/[^\x0a\x0d\x20-\x7e]/

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config, options = {}) ⇒ Emailer

Returns a new instance of Emailer.



39
40
41
42
43
44
# File 'lib/git_commit_notifier/emailer.rb', line 39

def initialize(config, options = {})
  GitCommitNotifier::Emailer.config = config || {}
  PARAMETERS.each do |name|
    instance_variable_set("@#{name}".to_sym, options[name.to_sym])
  end
end

Class Attribute Details

.configObject

[Hash] Gets or sets config.



48
49
50
# File 'lib/git_commit_notifier/emailer.rb', line 48

def config
  @config
end

Class Method Details

.reset_stylesheetNilClass

Note:

Useful for tests.

Resets CSS stylesheet source.

Returns:

  • (NilClass)

    nil



84
85
86
# File 'lib/git_commit_notifier/emailer.rb', line 84

def reset_stylesheet
  @stylesheet = nil
end

.reset_templateNilClass

Note:

Useful for tests.

Resets compiled template.

Returns:

  • (NilClass)

    nil



53
54
55
# File 'lib/git_commit_notifier/emailer.rb', line 53

def reset_template
  @template = nil
end

.stylesheetString

Gets or reads CSS stylesheet.

Returns:

  • (String)

    Stylesheet source code.



97
98
99
# File 'lib/git_commit_notifier/emailer.rb', line 97

def stylesheet
  @stylesheet ||= stylesheet_source
end

.stylesheet_sourceString

Reads CSS stylesheet source code.

Returns:

  • (String)

    Stylesheet source code.



90
91
92
93
# File 'lib/git_commit_notifier/emailer.rb', line 90

def stylesheet_source
  stylesheet = config['stylesheet'] || DEFAULT_STYLESHEET_PATH
  IO.read(stylesheet)
end

.templateObject

Note:

Erubis used as template engine if present; ERB otherwise.

Gets or reads compiled template.

Returns:

  • (Object)

    Compiled template.



67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/git_commit_notifier/emailer.rb', line 67

def template
  unless @template
    source = template_source
    begin
      require 'erubis'
       @template = Erubis::Eruby.new(source)
    rescue LoadError
      require 'erb'
      @template = ERB.new(source)
    end
  end
  @template
end

.template_sourceString

Reads template source code from file system.

Returns:

  • (String)

    Template source code.



59
60
61
62
# File 'lib/git_commit_notifier/emailer.rb', line 59

def template_source
  template_file = config['custom_template'] || TEMPLATE
  IO.read(template_file)
end

Instance Method Details

#boundaryString

Gets or creates email part boundary

Returns:

  • (String)

    Email part boundary.



123
124
125
126
127
128
# File 'lib/git_commit_notifier/emailer.rb', line 123

def boundary
  return @boundary if @boundary
  srand
  seed = "#{rand(10000)}#{Time.now}"
  @boundary = Digest::SHA1.hexdigest(seed)
end

#configHash

Note:

Helper that represents class method in instance scope.

Gets config.

Returns:

  • (Hash)

    Configuration

See Also:



35
36
37
# File 'lib/git_commit_notifier/emailer.rb', line 35

def config
  GitCommitNotifier::Emailer.config
end

#encode_quoted_printable_message(text) ⇒ Object

Convert a message into quoted printable encoding, limiting line length to 76 characters per spec. Encoding messages in this way ensures that they won't violate rules for maximum line length, which can result in the MTA breaking lines at inconvenient points, such as in the middle of UTF8 characters.



271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/git_commit_notifier/emailer.rb', line 271

def encode_quoted_printable_message(text)
  str = ''
  # Character encoding of output string can be plain US-ASCII since quoted-printable is plain ASCII
  str.force_encoding("US-ASCII")  if str.respond_to?(:force_encoding)
  StringIO.open(str, "w") do |output|
    line_max = 76
    line_len = 0

    input = StringIO.new(text, "r")
    input.each_byte do |b|
      case (b)
      when 9, 32..60, 62..126
        if line_len >= line_max - 1
          output << "=\r\n"
          line_len = 0
        end
        output << b.chr
        line_len += 1
      else
        if line_len >= line_max - 3
          output << "=\r\n"
          line_len = 0
        end
        output << "=%02X" % b
        line_len += 3
      end
    end

    output << "=\r\n" if line_len > 0
    output.string
  end
end

#mail_html_messageString

Gets HTML-formatted message.

Returns:

  • (String)

    HTML-formatted message.



104
105
106
107
108
109
110
111
# File 'lib/git_commit_notifier/emailer.rb', line 104

def mail_html_message
  html = GitCommitNotifier::Emailer.template.result(binding)
  if config['expand_css'].nil? || config['expand_css']
    premailer = Premailer.new(html, :with_html_string => true, :adapter => :nokogiri)
    html = premailer.to_inline_css
  end
  html
end

#perform_delivery_debug(content) ⇒ NilClass

Performs email delivery in debug mode (to STDOUT).

Returns:

  • (NilClass)

    nil



132
133
134
135
136
137
# File 'lib/git_commit_notifier/emailer.rb', line 132

def perform_delivery_debug(content)
  content.each do |line|
    puts line
  end
  nil
end

#perform_delivery_nntp(content, nntp_settings) ⇒ NilClass

Performs email delivery through NNTP.

Returns:

  • (NilClass)

    nil



187
188
189
190
191
192
193
# File 'lib/git_commit_notifier/emailer.rb', line 187

def perform_delivery_nntp(content, nntp_settings)
  require 'nntp'
  Net::NNTP.start(nntp_settings['address'], nntp_settings['port']) do |nntp|
      nntp.post content
  end
  nil
end

#perform_delivery_sendmail(content, options = nil) ⇒ NilClass

Performs email delivery through Sendmail.

Returns:

  • (NilClass)

    nil



170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/git_commit_notifier/emailer.rb', line 170

def perform_delivery_sendmail(content, options = nil)
  sendmail_settings = {
    'location' => "/usr/sbin/sendmail",
    'arguments' => "-i -t"
  }.merge(options || {})
  command = "#{sendmail_settings['location']} #{sendmail_settings['arguments']}"
  IO.popen(command, "w+") do |f|
    content.each do |line|
        f.print(line, "\r\n")
    end
    f.flush
  end
  nil
end

#perform_delivery_smtp(content, smtp_settings) ⇒ NilClass

Performs email delivery through SMTP.

Returns:

  • (NilClass)

    nil



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/git_commit_notifier/emailer.rb', line 141

def perform_delivery_smtp(content, smtp_settings)
  settings = { }
  %w(address port domain user_name password authentication enable_tls).each do |key|
    val = smtp_settings[key].to_s.empty? ? nil : smtp_settings[key]
    settings.merge!({ key => val})
  end

  main_smtp = Net::SMTP.new settings['address'], settings['port']

  main_smtp.enable_starttls  if settings['enable_tls']
  main_smtp.start( settings['domain'],
                  settings['user_name'], settings['password'], settings['authentication']) do |smtp|

    recipients = @recipient.dup
    recipients.force_encoding('ASCII-8BIT') if recipients.respond_to?(:force_encoding)
    recipients = recipients.split(",").map(&:strip)
    smtp.open_message_stream(@from_address, recipients) do |f|
      content.each do |line|
        line.force_encoding('ASCII-8BIT') if line.respond_to?(:force_encoding)
        f.print(line)
        f.print("\r\n")
      end
    end
  end
  nil
end

#quote_if_necessary(text, charset) ⇒ Object

Quote the given text if it contains any "illegal" characters



325
326
327
328
329
330
331
# File 'lib/git_commit_notifier/emailer.rb', line 325

def quote_if_necessary(text, charset)
  text = text.dup.force_encoding(Encoding::ASCII_8BIT) if text.respond_to?(:force_encoding)

  (text =~ CHARS_NEEDING_QUOTING) ?
    quoted_printable(text, charset) :
    text
end

#quoted_printable(text, charset) ⇒ Object

Convert the given text into quoted printable format, with an instruction that the text be eventually interpreted in the given charset.



306
307
308
309
310
# File 'lib/git_commit_notifier/emailer.rb', line 306

def quoted_printable(text, charset)
  text = text.gsub( /[^a-z ]/i ) { quoted_printable_encode($&) }.
              gsub( / /, "_" )
  "=?#{charset}?Q?#{text}?="
end

#quoted_printable_encode(character) ⇒ Object

Convert the given character to quoted printable format, taking into account multi-byte characters (if executing with $KCODE="u", for instance)



314
315
316
317
318
# File 'lib/git_commit_notifier/emailer.rb', line 314

def quoted_printable_encode(character)
  result = ""
  character.each_byte { |b| result << "=%02X" % b }
  result
end

#sendNilClass

Creates email message and sends it using configured delivery method.

Returns:

  • (NilClass)

    nil



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/git_commit_notifier/emailer.rb', line 197

def send
  to_tag = config['delivery_method'] == 'nntp' ? 'Newsgroups' : 'To'
  quoted_from_alias = !@from_alias.nil? ? quote_if_necessary("#{@from_alias}",'utf-8') : nil
  from = (@from_alias.nil? || @from_alias.empty?) ? @from_address : "#{quoted_from_alias} <#{@from_address}>"
  reply_to = (@from_alias.nil? || !config['reply_to_author']) ? @reply_to_address : "#{@from_alias} <#{@reply_to_address}>"

  plaintext = if config['add_plaintext'].nil? || config['add_plaintext']
    @text_message
  else
    "Plain text part omitted. Consider setting add_plaintext in configuration."
  end

  content = []
  content << "From: #{from}"  unless from.nil?
  content << "Reply-To: #{reply_to}"  unless reply_to.nil?

  # Setting the email date from the commit date is undesired by those
  # who sort their email by send date instead of receive date.
  # We currently use @offset_date, which starts with the time the notifier was started,
  # and adds a second of time per commit. This should result in the commits arriving
  # in order by their processing sequence, even when they are all processed within the
  # same second
  #date = @commit_date    # Date of the commit
  #date = @current_date   # Date we processed this commit
  date = @offset_date     # Date notifier started, plus 1 second per commit processed
  date = Time.new.rfc2822 if date.nil?    # Fallback to current date if date is nil

  content.concat [
      "#{to_tag}: #{quote_if_necessary(@recipient, 'utf-8')}",
      "Date: #{date}",
      "Subject: #{quote_if_necessary(@subject, 'utf-8')}",
      "X-Mailer: git-commit-notifier",
      "X-Git-Repository: #{@repo_name}",
      "X-Git-Refname: #{@ref_name}",
      "X-Git-Oldrev: #{@old_rev}",
      "X-Git-Newrev: #{@new_rev}",
      "Mime-Version: 1.0",
      "Content-Type: multipart/alternative; boundary=#{boundary}",
      "",
      "--#{boundary}",
      "Content-Type: text/plain; charset=utf-8",
      "Content-Transfer-Encoding: quoted-printable",
      "Content-Disposition: inline",
      "",
      encode_quoted_printable_message(plaintext),
      "--#{boundary}",
      "Content-Type: text/html; charset=utf-8",
      "Content-Transfer-Encoding: quoted-printable",
      "Content-Disposition: inline",
      "",
      encode_quoted_printable_message(mail_html_message),
      "--#{boundary}--"]

  if @recipient.empty?
    puts content.join("\n")
    return
  end

  case config['delivery_method'].to_sym
  when :smtp then perform_delivery_smtp(content, config['smtp_server'])
  when :nntp then perform_delivery_nntp(content, config['nntp_settings'])
  when :debug then perform_delivery_debug(content)
  else # sendmail
    perform_delivery_sendmail(content, config['sendmail_options'])
  end
  nil
end

#stylesheet_stringString

Note:

This is helper to provide data from class context.

Gets stylesheet string.

Returns:

  • (String)

    Stylesheet source code.

See Also:



117
118
119
# File 'lib/git_commit_notifier/emailer.rb', line 117

def stylesheet_string
  GitCommitNotifier::Emailer.stylesheet
end