Skip to content

Commit

Permalink
Replace Treetop parser with a Ragel based parser
Browse files Browse the repository at this point in the history
  • Loading branch information
bpot committed Jan 29, 2013
1 parent a944f31 commit bc70203
Show file tree
Hide file tree
Showing 100 changed files with 32,791 additions and 13,400 deletions.
1 change: 0 additions & 1 deletion Gemfile
Expand Up @@ -2,7 +2,6 @@ source 'https://rubygems.org'

gemspec

gem "treetop", "~> 1.4.10"
gem "mime-types", "~> 1.16"
gem "tlsmail" if RUBY_VERSION <= '1.8.6'

Expand Down
35 changes: 0 additions & 35 deletions lib/load_parsers.rb

This file was deleted.

2 changes: 1 addition & 1 deletion lib/mail.rb
Expand Up @@ -76,7 +76,7 @@ def self.eager_autoload!

require 'mail/envelope'

require 'load_parsers'
require 'mail/parsers'

# Autoload header field elements and transfer encodings.
require 'mail/elements'
Expand Down
120 changes: 38 additions & 82 deletions lib/mail/elements/address.rb
@@ -1,7 +1,7 @@
# encoding: utf-8
module Mail
class Address

include Mail::Utilities

# Mail::Address handles all email addresses in Mail. It takes an email address string
Expand All @@ -22,21 +22,19 @@ class Address
# a.to_s #=> 'Mikel Lindsaar <mikel@test.lindsaar.net> (My email address)'
def initialize(value = nil)
@output_type = :decode
@tree = nil
@raw_text = value
case
when value.nil?
if value.nil?
@parsed = false
@data = nil
return
else
parse(value)
end
end

# Returns the raw imput of the passed in string, this is before it is passed
# Returns the raw input of the passed in string, this is before it is passed
# by the parser.
def raw
@raw_text
@data.raw
end

# Returns a correctly formatted address for the email going out. If given
Expand All @@ -48,15 +46,14 @@ def raw
# a.format #=> 'Mikel Lindsaar <mikel@test.lindsaar.net> (My email address)'
def format
parse unless @parsed
case
when tree.nil?
if @data.nil?
''
when display_name
elsif display_name
[quote_phrase(display_name), "<#{address}>", format_comments].compact.join(" ")
when address
elsif address
[address, format_comments].compact.join(" ")
else
tree.text_value
raw
end
end

Expand Down Expand Up @@ -106,7 +103,7 @@ def display_name=( str )
# a.local #=> 'mikel'
def local
parse unless @parsed
"#{obs_domain_list}#{get_local.strip}" if get_local
"#{@data.obs_domain_list}#{get_local.strip}" if get_local
end

# Returns the domain part (the right hand side of the @ sign in the email address) of
Expand Down Expand Up @@ -174,29 +171,24 @@ def decoded

def parse(value = nil)
@parsed = true
case
when value.nil?

case value
when NilClass
@data = nil
nil
when value.class == String
self.tree = Mail::AddressList.new(value).address_nodes.first
else
self.tree = value
when Mail::Parsers::AddressStruct
@data = value
when String
@raw_text = value
if value.blank?
@data = nil
else
address_list = Mail::Parsers::AddressListsParser.new.parse(value)
@data = address_list.addresses.first
end
end
end


def get_domain
if tree.respond_to?(:angle_addr) && tree.angle_addr.respond_to?(:addr_spec) && tree.angle_addr.addr_spec.respond_to?(:domain)
@domain_text ||= tree.angle_addr.addr_spec.domain.text_value.strip
elsif tree.respond_to?(:domain)
@domain_text ||= tree.domain.text_value.strip
elsif tree.respond_to?(:addr_spec) && tree.addr_spec.respond_to?(:domain)
tree.addr_spec.domain.text_value.strip
else
nil
end
end

def strip_all_comments(string)
unless comments.blank?
comments.each do |comment|
Expand All @@ -209,28 +201,19 @@ def strip_all_comments(string)
def strip_domain_comments(value)
unless comments.blank?
comments.each do |comment|
if get_domain && get_domain.include?("(#{comment})")
if @data.domain && @data.domain.include?("(#{comment})")
value = value.gsub("(#{comment})", '')
end
end
end
value.to_s.strip
end

def get_comments
if tree.respond_to?(:comments)
@comments = tree.comments.map { |c| unparen(c.text_value.to_str) }
else
@comments = []
end
end

def get_display_name
if tree.respond_to?(:display_name)
name = unquote(tree.display_name.text_value.strip)
str = strip_all_comments(name.to_s)
elsif comments
if domain
if @data.display_name
str = strip_all_comments(@data.display_name.to_s)
elsif @data.comments
if @data.domain
str = strip_domain_comments(format_comments)
else
str = nil
Expand Down Expand Up @@ -263,15 +246,6 @@ def get_name
end
end

# Provides access to the Treetop parse tree for this address
def tree
@tree
end

def tree=(value)
@tree = value
end

def format_comments
if comments
comment_text = comments.map {|c| escape_paren(c) }.join(' ').squeeze(" ")
Expand All @@ -280,35 +254,17 @@ def format_comments
nil
end
end

def obs_domain_list
if tree.respond_to?(:angle_addr)
obs = tree.angle_addr.elements.select { |e| e.respond_to?(:obs_domain_list) }
!obs.empty? ? obs.first.text_value : nil
else
nil
end
end


def get_local
case
when tree.respond_to?(:local_dot_atom_text)
tree.local_dot_atom_text.text_value
when tree.respond_to?(:angle_addr) && tree.angle_addr.respond_to?(:addr_spec) && tree.angle_addr.addr_spec.respond_to?(:local_part)
tree.angle_addr.addr_spec.local_part.text_value
when tree.respond_to?(:addr_spec) && tree.addr_spec.respond_to?(:local_part)
tree.addr_spec.local_part.text_value
when tree.respond_to?(:angle_addr) && tree.angle_addr.respond_to?(:addr_spec) && tree.angle_addr.addr_spec.respond_to?(:local_dot_atom_text)
# Ignore local dot atom text when in angle brackets
nil
when tree.respond_to?(:addr_spec) && tree.addr_spec.respond_to?(:local_dot_atom_text)
# Ignore local dot atom text when in angle brackets
nil
else
tree && tree.respond_to?(:local_part) ? tree.local_part.text_value : nil
end
@data && @data.local
end

def get_domain
@data && @data.domain
end


def get_comments
@data && @data.comments
end
end
end
61 changes: 19 additions & 42 deletions lib/mail/elements/address_list.rb
@@ -1,7 +1,7 @@
# encoding: utf-8
module Mail
class AddressList # :nodoc:

# Mail::AddressList is the class that parses To, From and other address fields from
# emails passed into Mail.
#
Expand All @@ -18,57 +18,34 @@ class AddressList # :nodoc:
# a.addresses #=> [#<Mail::Address:14943130 Address: |ada@test.lindsaar.net...
# a.group_names #=> ["My Group"]
def initialize(string)
if string.blank?
@address_nodes = []
return self
end
parser = Mail::AddressListsParser.new
if tree = parser.parse(string)
@address_nodes = tree.addresses
else
raise Mail::Field::ParseError.new(AddressListsParser, string, parser.failure_reason)
end
@addresses_grouped_by_group = nil
@address_list = Parsers::AddressListsParser.new.parse(string)
end

# Returns a list of address objects from the parsed line
def addresses
@addresses ||= get_addresses.map do |address_tree|
Mail::Address.new(address_tree)
@addresses ||= @address_list.addresses.map do |address_data|
Mail::Address.new(address_data)
end
end

# Returns a list of all recipient syntax trees that are not part of a group
def individual_recipients # :nodoc:
@individual_recipients ||= @address_nodes - group_recipients
end

# Returns a list of all recipient syntax trees that are part of a group
def group_recipients # :nodoc:
@group_recipients ||= @address_nodes.select { |an| an.respond_to?(:group_name) }

def addresses_grouped_by_group
return @addresses_grouped_by_group if @addresses_grouped_by_group

@addresses_grouped_by_group = {}

@address_list.addresses.each do |address_data|
if group = address_data.group
@addresses_grouped_by_group[group] ||= []
@addresses_grouped_by_group[group] << Mail::Address.new(address_data)
end
end
@addresses_grouped_by_group
end

# Returns the names as an array of strings of all groups
def group_names # :nodoc:
group_recipients.map { |g| g.group_name.text_value }
end

# Returns a list of address syntax trees
def address_nodes # :nodoc:
@address_nodes
end

private

def get_addresses
(individual_recipients + group_recipients.map { |g| get_group_addresses(g) }).flatten
end

def get_group_addresses(g)
if g.group_list.respond_to?(:addresses)
g.group_list.addresses
else
[]
end
@address_list.group_names
end
end
end
10 changes: 3 additions & 7 deletions lib/mail/elements/content_disposition_element.rb
Expand Up @@ -5,13 +5,9 @@ class ContentDispositionElement # :nodoc:
include Mail::Utilities

def initialize( string )
parser = Mail::ContentDispositionParser.new
if tree = parser.parse(cleaned(string))
@disposition_type = tree.disposition_type.text_value.downcase
@parameters = tree.parameters
else
raise Mail::Field::ParseError.new(ContentDispositionElement, string, parser.failure_reason)
end
content_disposition = Mail::Parsers::ContentDispositionParser.new.parse(cleaned(string))
@disposition_type = content_disposition.disposition_type
@parameters = content_disposition.parameters
end

def disposition_type
Expand Down
8 changes: 2 additions & 6 deletions lib/mail/elements/content_location_element.rb
Expand Up @@ -5,12 +5,8 @@ class ContentLocationElement # :nodoc:
include Mail::Utilities

def initialize( string )
parser = Mail::ContentLocationParser.new
if tree = parser.parse(string)
@location = tree.location.text_value
else
raise Mail::Field::ParseError.new(ContentLocationElement, string, parser.failure_reason)
end
content_location = Mail::Parsers::ContentLocationParser.new.parse(string)
@location = content_location.location
end

def location
Expand Down
13 changes: 3 additions & 10 deletions lib/mail/elements/content_transfer_encoding_element.rb
Expand Up @@ -4,16 +4,9 @@ class ContentTransferEncodingElement

include Mail::Utilities

def initialize( string )
parser = Mail::ContentTransferEncodingParser.new
case
when string.blank?
@encoding = ''
when tree = parser.parse(string.to_s.downcase)
@encoding = tree.encoding.text_value
else
raise Mail::Field::ParseError.new(ContentTransferEncodingElement, string, parser.failure_reason)
end
def initialize(string)
content_transfer_encoding = Mail::Parsers::ContentTransferEncodingParser.new.parse(string)
@encoding = content_transfer_encoding.encoding
end

def encoding
Expand Down

0 comments on commit bc70203

Please sign in to comment.