mail_address 1.2.19 → 1.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 13603b8b09a30e1641652023214e199d838af06d4d7022c5d0c97a53798f495f
4
- data.tar.gz: 1c6ca7323e50be0d24572415bbc90afe7c5f7f0b205a92fb4a03c84594def2f2
3
+ metadata.gz: 3ac1b18916f09e9ab8d6b774632f191f7eb3cc941df6e96267f4f72aec2fdfe5
4
+ data.tar.gz: 19ec24086e0f203ed3c9a18052d82acebd4831d968b25fdc5124ec8ed582302f
5
5
  SHA512:
6
- metadata.gz: 97d7c77f4e28b7c5bb38b2a035356e54147c0d0abbbedd7b028140283656b3a67289eadc5c8406782f8dd4b03f0479e54324402f4c84aa8fc7259ddfe4d5d7bb
7
- data.tar.gz: 65c4d48be8dd2f856803a0825018200c736fcd25084505c7b5deb06a80e3743929630060f6a72e23d89257608469ce6a19c6ddfad6a221b0a9564c2a7de54048
6
+ metadata.gz: cb6fecb18ae04b8aa664b870c1c90751ad612c6a78b216c6760df79565b652e5822dd6d29ee0586e47891aaaa8fd70925d38eb6177c0217491df3bf5673f253e
7
+ data.tar.gz: 81cd4080f262badace8c5254f91934b7086304f5977308c28461cf87156c47ff4dd65486ff1a78977204dbf33eab0ce9d48630da96bb8b31ce3d9e3dcad2bedc
@@ -0,0 +1,27 @@
1
+ name: Test
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+ branches: [master]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ ruby-version: ['2.7', '3.0', '3.1', '3.2', '3.3']
16
+
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+
20
+ - name: Set up Ruby ${{ matrix.ruby-version }}
21
+ uses: ruby/setup-ruby@v1
22
+ with:
23
+ ruby-version: ${{ matrix.ruby-version }}
24
+ bundler-cache: true
25
+
26
+ - name: Run tests
27
+ run: bundle exec rake spec
data/CLAUDE.md ADDED
@@ -0,0 +1,43 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ MailAddress は Perl の `Mail::Address` モジュールを Ruby に移植したメールアドレスパーサー gem。RFC非準拠のアドレス(docomo/au などの日本のキャリアメールで使われる `..` や末尾 `.`)もパース可能。ランタイム依存なし(Pure Ruby)。
8
+
9
+ ## Commands
10
+
11
+ ```bash
12
+ # テスト実行(デフォルトの rake タスク)
13
+ bundle exec rake spec
14
+
15
+ # 特定ファイルのテスト
16
+ bundle exec rspec spec/mail_address_spec.rb
17
+
18
+ # 特定行のテスト
19
+ bundle exec rspec spec/mail_address_spec.rb:10
20
+ ```
21
+
22
+ リンターは未設定。
23
+
24
+ ## Architecture
25
+
26
+ ### コア構造
27
+
28
+ - **`lib/mail_address.rb`** — エントリポイント。`MailAddress.parse`, `MailAddress.parse_first`, `MailAddress.parse_simple` を提供
29
+ - **`lib/mail_address/address.rb`** — `MailAddress::Address` クラス。パース結果を保持(`phrase`, `address`, `original`)し、`name`, `host`, `user`, `format` メソッドを提供
30
+ - **`lib/mail_address/mail_address.rb`** — メインのパースロジック。トークン化と複雑なアドレスフォーマットの処理
31
+ - **`lib/mail_address/simple_parser.rb`** — Google Closure Library ベースのシンプルパーサー(スペース/カンマ区切りのアドレス向け)
32
+
33
+ ### パース戦略
34
+
35
+ 2つのパーサーを使い分ける:
36
+ - **メインパーサー** (`parse`) — RFC準拠・非準拠の複雑なフォーマットを処理。括弧コメント、クォート、MIME エンコードなどに対応
37
+ - **シンプルパーサー** (`parse_simple` / `g_parse`) — 空白・カンマ区切りの単純なリスト向け
38
+
39
+ ### コーディング規約
40
+
41
+ - プライベートメソッドは `_` プレフィックス(例: `_tokenize`, `_complete`)
42
+ - 定数は `UPPER_CASE`
43
+ - テストは RSpec 3 の `expect` 構文を使用
@@ -27,7 +27,7 @@ module MailAddress
27
27
 
28
28
  email_address = enquote ? quoted_address : @address
29
29
 
30
- if !@phrase.nil? && @phrase.length > 0
30
+ if !@phrase.nil? && !@phrase.empty?
31
31
  # if @phrase.match(/\A\(/) && @phrase.match(/\)\z/)
32
32
  # addr.push(email_address) if !@address.nil? && @address.length > 0
33
33
  # addr.push(@phrase)
@@ -35,11 +35,11 @@ module MailAddress
35
35
  addr.push(
36
36
  @phrase.match(/^(?:#{ATEXT})+$/) ? @phrase
37
37
  : @phrase.match(/(?<!\\)"/) ? @phrase
38
- : %Q("#{@phrase}")
38
+ : "\"#{@phrase}\""
39
39
  )
40
- addr.push "<#{email_address}>" if !@address.nil? && @address.length > 0
40
+ addr.push "<#{email_address}>" if !@address.nil? && !@address.empty?
41
41
  # end
42
- elsif !@address.nil? && @address.length > 0
42
+ elsif !@address.nil? && !@address.empty?
43
43
  addr.push(email_address)
44
44
  end
45
45
  addr.join(' ')
@@ -48,19 +48,19 @@ module MailAddress
48
48
  def name
49
49
  phrase = @phrase.dup
50
50
  name = Address._extract_name(phrase)
51
- name.length > 0 ? name : nil
51
+ !name.empty? ? name : nil
52
52
  end
53
53
 
54
54
  def host
55
- addr = @address || '';
55
+ addr = @address || ''
56
56
  i = addr.rindex('@')
57
- i.nil? ? nil : addr[i + 1 .. -1]
57
+ addr[(i + 1)..] if i
58
58
  end
59
59
 
60
60
  def user
61
- addr = @address || '';
61
+ addr = @address || ''
62
62
  i = addr.rindex('@')
63
- i.nil? ? addr : addr[0 ... i]
63
+ i ? addr[0...i] : addr
64
64
  end
65
65
 
66
66
  def quoted_address
@@ -78,18 +78,16 @@ module MailAddress
78
78
  # given a comment, attempt to extract a person's name
79
79
  def self._extract_name(name)
80
80
  # This function can be called as method as well
81
- return '' if name.nil? || name == ''
81
+ return '' if name.nil? || name.empty?
82
82
 
83
83
  # Using encodings, too hard. See Mail::Message::Field::Full.
84
- return '' if name.match(/\=\?.*?\?\=/)
84
+ return '' if name.match?(/\=\?.*?\?\=/)
85
85
 
86
86
  # trim whitespace
87
- name.sub!(/^\s+/, '')
88
- name.sub!(/\s+$/, '')
89
- name.sub!(/\s+/, ' ')
87
+ name = name.strip.gsub(/\s+/, ' ')
90
88
 
91
89
  # Disregard numeric names (e.g. 123456.1234@compuserve.com)
92
- return "" if name.match(/^[\d ]+$/)
90
+ return '' if name.match?(/^[\d ]+$/)
93
91
 
94
92
  name.sub!(/^\((.*)\)$/, '\1') # remove outermost parenthesis
95
93
  name.sub!(/^"(.*)"$/, '\1') # remove outer quotation marks
@@ -10,12 +10,12 @@ module MailAddress
10
10
  line = lines.join('').strip
11
11
 
12
12
  # empty or <> or < or >
13
- if line.empty? || line.match(/\A[<>;, \\]+\z/)
13
+ if line.empty? || line.match?(/\A[<>;, \\]+\z/)
14
14
  return [ MailAddress::Address.new(line, nil, line) ]
15
15
  end
16
16
 
17
17
  # undisclosed-recipient
18
- if line.match(/undisclosed[ \-]recipients?: ?;?/i)
18
+ if line.match?(/undisclosed[ \-]recipients?: ?;?/i)
19
19
  return [ MailAddress::Address.new(line, nil, line) ]
20
20
  end
21
21
 
@@ -27,7 +27,7 @@ module MailAddress
27
27
  len = tokens.length
28
28
  _next = _find_next idx, tokens, len
29
29
 
30
- for idx in 0 ... len do
30
+ (0...len).each do |idx|
31
31
 
32
32
  token = tokens[idx]
33
33
  substr = token[0, 1]
@@ -55,7 +55,8 @@ module MailAddress
55
55
  if depth > 0
56
56
  # raise "Unmatched '<>' in line"
57
57
  o = MailAddress::Address.new(original, nil, original)
58
- phrase.clear; address.clear
58
+ phrase.clear
59
+ address.clear
59
60
  else
60
61
  o = _complete(phrase, address, original)
61
62
  end
@@ -70,7 +71,7 @@ module MailAddress
70
71
  address.push(token)
71
72
  elsif (_next == '<')
72
73
  phrase.push(token)
73
- elsif ( token.match(/^[.\@:;]/) || address.empty? || address[-1].match(/^[.\@:;]/) )
74
+ elsif ( token.match?(/^[.\@:;]/) || address.empty? || address[-1].match?(/^[.\@:;]/) )
74
75
  token.strip!
75
76
  address.push(token)
76
77
  else
@@ -90,10 +91,9 @@ module MailAddress
90
91
  line.sub!(/\A\s+/, '')
91
92
  line.gsub!(/[\r\n]+/,' ')
92
93
 
93
- while (line != '')
94
- tmp = nil
94
+ until line.empty?
95
95
  if (
96
- line.match(/"[^"]+"/) && line.sub!(/\A(\\?"(?:[^"\\]+|\\.)*")(\s*)/, '') || # "..."
96
+ line.match?(/"[^"]+"/) && line.sub!(/\A(\\?"(?:[^"\\]+|\\.)*")(\s*)/, '') || # "..."
97
97
  line.sub!(/\A([^\s()<>\@,;:\\".\[\]]+)(\s*)/, '') ||
98
98
  line.sub!(/\A([()<>\@,;:\\".\[\]])(\s*)/, '')
99
99
  )
@@ -127,7 +127,7 @@ module MailAddress
127
127
  end
128
128
 
129
129
  def self._complete (phrase, address, original)
130
- phrase.length > 0 || address.length > 0 or return nil
130
+ return nil if phrase.empty? && address.empty?
131
131
 
132
132
  name = phrase.join('').strip
133
133
 
@@ -140,7 +140,8 @@ module MailAddress
140
140
  name = name.gsub(ESCAPED_BACKSLASHES_, '\\')
141
141
 
142
142
  new_address = MailAddress::Address.new(name, address.join(''), original)
143
- phrase.clear; address.clear
143
+ phrase.clear
144
+ address.clear
144
145
  new_address
145
146
  end
146
147
 
@@ -12,10 +12,10 @@ module MailAddress
12
12
  # CHARS_REQUIRE_QUOTES_ = SPECIAL_CHARS + ADDRESS_SEPARATORS_
13
13
  ESCAPED_DOUBLE_QUOTES_ = /\\\"/
14
14
  ESCAPED_BACKSLASHES_ = /\\\\/
15
- QUOTED_REGEX_STR_ = '[+a-zA-Z0-9_.!#$%&\'*\\/=?^`{|}~-]+'
15
+ QUOTED_REGEX_STR_ = '[+a-zA-Z0-9_.!#$%&\'*\\/=?^`{|}~\-]+'
16
16
  UNQUOTED_REGEX_STR_ = '"' + QUOTED_REGEX_STR_ + '"'
17
17
  LOCAL_PART_REGEXP_STR_ = '(?:' + QUOTED_REGEX_STR_ + '|' + UNQUOTED_REGEX_STR_ + ')'
18
- DOMAIN_PART_REGEXP_STR_ = '([a-zA-Z0-9-_]+\\.)+[a-zA-Z0-9]{2,63}'
18
+ DOMAIN_PART_REGEXP_STR_ = '([a-zA-Z0-9\-_]+\\.)+[a-zA-Z0-9]{2,63}'
19
19
  EMAIL_ADDRESS_ = Regexp.new('\\A' + LOCAL_PART_REGEXP_STR_ + '@' + DOMAIN_PART_REGEXP_STR_ + '\\z')
20
20
 
21
21
  def self.parse_simple(str)
@@ -25,13 +25,13 @@ module MailAddress
25
25
  # Remove non-UNIX-style newlines that would otherwise cause getToken_ to
26
26
  # choke. Remove multiple consecutive whitespace characters for the same
27
27
  # reason.
28
- str = self.collapse_whitespace(str)
28
+ str = collapse_whitespace(str)
29
29
  i = 0
30
- while (i < str.length)
30
+ while i < str.length
31
31
  token = get_token(str, i)
32
- if self.is_address_separator(token) || (token == ' ' && self.is_valid(self.parse_internal(email)))
33
- if !self.is_empty_or_whitespace(email)
34
- result.push(self.parse_internal(email))
32
+ if is_address_separator(token) || (token == ' ' && is_valid(parse_internal(email)))
33
+ if !is_empty_or_whitespace(email)
34
+ result.push(parse_internal(email))
35
35
  end
36
36
  email = ''
37
37
  i += 1
@@ -42,34 +42,34 @@ module MailAddress
42
42
  end
43
43
 
44
44
  # Add the final token.
45
- if (!self.is_empty_or_whitespace(email))
46
- result.push(self.parse_internal(email))
45
+ if !is_empty_or_whitespace(email)
46
+ result.push(parse_internal(email))
47
47
  end
48
- return result
48
+ result
49
49
  end
50
50
 
51
51
  def self.parse_internal(addr)
52
52
  name = ''
53
53
  address = ''
54
54
  i = 0
55
- while (i < addr.length)
55
+ while i < addr.length
56
56
  token = get_token(addr, i)
57
- if (token[0] == '<' && token.index('>'))
57
+ if token[0] == '<' && token.index('>')
58
58
  end_i = token.index('>')
59
59
  address = token[1, end_i - 1]
60
- elsif (address == '')
60
+ elsif address == ''
61
61
  name << token
62
62
  end
63
63
  i += token.length
64
64
  end
65
65
 
66
66
  # Check if it's a simple email address of the form "jlim@google.com".
67
- if (address == '' && name.index('@'))
67
+ if address == '' && name.index('@')
68
68
  address = name
69
69
  name = ''
70
70
  end
71
71
 
72
- name = self.collapse_whitespace(name)
72
+ name = collapse_whitespace(name)
73
73
  name = name[1 .. -2] if name.start_with?('\'') && name.end_with?('\'')
74
74
  name = name[1 .. -2] if name.start_with?('"') && name.end_with?('"')
75
75
 
@@ -89,7 +89,7 @@ module MailAddress
89
89
  p = OPENERS_.index(ch)
90
90
  return ch unless p
91
91
 
92
- if (self.is_escaped_dbl_quote(str, pos))
92
+ if is_escaped_dbl_quote(str, pos)
93
93
  # If an opener is an escaped quote we do not treat it as a real opener
94
94
  # and keep accumulating the token.
95
95
  return ch
@@ -99,19 +99,18 @@ module MailAddress
99
99
 
100
100
  # If the closer is a quote we go forward skipping escaped quotes until we
101
101
  # hit the real closing one.
102
- while (end_pos && end_pos >= 0 && self.is_escaped_dbl_quote(str, end_pos))
102
+ while end_pos && end_pos >= 0 && is_escaped_dbl_quote(str, end_pos)
103
103
  end_pos = str.index(closer_char, end_pos + 1)
104
104
  end
105
105
 
106
- token = (end_pos && end_pos >= 0) ? str[pos .. end_pos] : ch
107
- return token
106
+ (end_pos && end_pos >= 0) ? str[pos .. end_pos] : ch
108
107
  end
109
108
 
110
109
  def self.is_escaped_dbl_quote(str, pos)
111
110
  return false if str[pos] != '"'
112
111
  slash_count = 0
113
112
 
114
- for idx in (pos - 1).downto(0)
113
+ (pos - 1).downto(0) do |idx|
115
114
  break unless str[idx] == '\\'
116
115
  slash_count += 1
117
116
  end
@@ -123,7 +122,7 @@ module MailAddress
123
122
  end
124
123
 
125
124
  def self.is_empty_or_whitespace(str)
126
- /\A[\s\xc2\xa0]*\z/ =~ str
125
+ str.match?(/\A[\s\xc2\xa0]*\z/)
127
126
  end
128
127
 
129
128
  def self.is_address_separator(ch)
@@ -1,3 +1,3 @@
1
1
  module MailAddress
2
- VERSION = "1.2.19"
2
+ VERSION = "1.3.0"
3
3
  end
data/lib/mail_address.rb CHANGED
@@ -4,5 +4,4 @@ module MailAddress
4
4
  require "mail_address/address"
5
5
  require "mail_address/mail_address"
6
6
  require "mail_address/simple_parser"
7
- require "mail_address/version"
8
7
  end
data/mail_address.gemspec CHANGED
@@ -1,5 +1,4 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
1
+ lib = File.expand_path('lib', __dir__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
3
  require 'mail_address/version'
5
4
 
@@ -8,8 +7,8 @@ Gem::Specification.new do |spec|
8
7
  spec.version = MailAddress::VERSION
9
8
  spec.authors = ["Kizashi Nagata"]
10
9
  spec.email = ["kizashi1122@gmail.com"]
11
- spec.summary = %q{Simple Mail Address Parser}
12
- spec.description = %q{A practical mail address parser implemented based on Perl Module Mail::Address.}
10
+ spec.summary = "Simple Mail Address Parser"
11
+ spec.description = "A practical mail address parser implemented based on Perl Module Mail::Address."
13
12
  spec.homepage = "https://github.com/kizashi1122/mail_address"
14
13
  spec.license = "MIT"
15
14
 
data/spec/spec_helper.rb CHANGED
@@ -1,13 +1,18 @@
1
1
  require 'simplecov'
2
- require 'coveralls'
3
- Coveralls.wear!
4
2
 
5
- SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([
6
- SimpleCov::Formatter::HTMLFormatter,
7
- Coveralls::SimpleCov::Formatter
8
- ])
9
- SimpleCov.start do
10
- add_filter '.bundle/'
3
+ begin
4
+ require 'coveralls'
5
+ Coveralls.wear! do
6
+ add_filter '.bundle/'
7
+ end
8
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([
9
+ SimpleCov::Formatter::HTMLFormatter,
10
+ Coveralls::SimpleCov::Formatter
11
+ ])
12
+ rescue LoadError
13
+ SimpleCov.start do
14
+ add_filter '.bundle/'
15
+ end
11
16
  end
12
17
 
13
18
  require 'rubygems'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mail_address
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.19
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kizashi Nagata
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-07-20 00:00:00.000000000 Z
11
+ date: 2026-02-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -80,9 +80,10 @@ extensions: []
80
80
  extra_rdoc_files: []
81
81
  files:
82
82
  - ".coveralls.yml"
83
+ - ".github/workflows/test.yml"
83
84
  - ".gitignore"
84
85
  - ".rspec"
85
- - ".travis.yml"
86
+ - CLAUDE.md
86
87
  - Gemfile
87
88
  - LICENSE.txt
88
89
  - README.md
@@ -116,7 +117,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
116
117
  - !ruby/object:Gem::Version
117
118
  version: '0'
118
119
  requirements: []
119
- rubygems_version: 3.1.2
120
+ rubygems_version: 3.1.6
120
121
  signing_key:
121
122
  specification_version: 4
122
123
  summary: Simple Mail Address Parser
data/.travis.yml DELETED
@@ -1,3 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.0.0