email_reply_trimmer 0.2.0 → 0.4.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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +104 -52
  3. data/Rakefile +0 -3
  4. data/email_reply_trimmer.gemspec +1 -1
  5. data/lib/email_reply_trimmer/delimiter_matcher.rb +10 -0
  6. data/lib/email_reply_trimmer/embedded_email_matcher.rb +1 -1
  7. data/lib/email_reply_trimmer.rb +9 -4
  8. data/test/elided/bullet_list_reply.txt +0 -0
  9. data/test/elided/complete_german_quote_at_end.txt +3 -0
  10. data/test/elided/delimiter_before_forwarded_message.txt +6 -0
  11. data/test/elided/delimiter_inside_new_reply.txt +2 -0
  12. data/test/elided/inline_images_before_reply_text.txt +2 -0
  13. data/test/elided/lone_bullet_vs_separator.txt +2 -0
  14. data/test/elided/reply_after_selected_german_quote.txt +1 -0
  15. data/test/elided/separate_inline_images_before_reply_text.txt +2 -0
  16. data/test/emails/bullet_list_reply.txt +8 -0
  17. data/test/emails/complete_german_quote_at_end.txt +10 -0
  18. data/test/emails/delimiter_before_forwarded_message.txt +8 -0
  19. data/test/emails/delimiter_inside_new_reply.txt +13 -0
  20. data/test/emails/inline_images_before_reply_text.txt +15 -0
  21. data/test/emails/lone_bullet_vs_separator.txt +9 -0
  22. data/test/emails/reply_after_selected_german_quote.txt +9 -0
  23. data/test/emails/separate_inline_images_before_reply_text.txt +12 -0
  24. data/test/test_email_reply_trimmer.rb +1 -0
  25. data/test/trimmed/bullet_list_reply.txt +8 -0
  26. data/test/trimmed/complete_german_quote_at_end.txt +6 -0
  27. data/test/trimmed/delimiter_before_forwarded_message.txt +1 -0
  28. data/test/trimmed/delimiter_inside_new_reply.txt +10 -0
  29. data/test/trimmed/inline_images_before_reply_text.txt +12 -0
  30. data/test/trimmed/lone_bullet_vs_separator.txt +6 -0
  31. data/test/trimmed/reply_after_selected_german_quote.txt +8 -0
  32. data/test/trimmed/separate_inline_images_before_reply_text.txt +9 -0
  33. metadata +29 -11
  34. data/devenv.lock +0 -184
  35. data/devenv.nix +0 -4
  36. data/devenv.yaml +0 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9914eb793e4bbfa4f291097707acb2af27f491507b0e52b0e1433df3c1fb1f0d
4
- data.tar.gz: 2c519cd4b6fe2db1dc3b12201c8a5bd540fbf274537610069b73d34bb3e0566f
3
+ metadata.gz: 54aecf374022b3de478e77efe03466144d1ea805c4cc9c0735706a579cf5451e
4
+ data.tar.gz: 38073ca3beed260c32563c1cdffdf58a2572107492b1fd648af86e554dbf3fa4
5
5
  SHA512:
6
- metadata.gz: b0687f10183b5472c816d433c3f654dd085b8d615ef223c54e97e61e893c47752ed20eb5b1354c7d184ab84075de3c3fa492c76191b1fc0cfdcfc48a1d661b34
7
- data.tar.gz: 3d740b872f6116bd0c59d2d1b19e405191f1c25c619e5564a602eebed3691323761292d7a063f7f224bacd668f2ede622eac085f7a5b7bad3b90570ac3b018b9
6
+ metadata.gz: 32eecb6c61e7f6f06d32d16d01332d3fa1a78f31b3189f826f2fe630ffccd549486ff3b77e45b5ac7a60836b1cf7cbb91b7ed2c9c9a742dafd290e19aceb007c
7
+ data.tar.gz: 7eb4ec66f4ebeebd685e8dd2661e844db2a0abb8dd8a3eccc9a3711713034a7fdde3d37d5456dc32f5bbf772ce0eeba86abf7b4dd02653d00fd36a65ca499e38
data/Gemfile.lock CHANGED
@@ -1,98 +1,150 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- email_reply_trimmer (0.2.0)
4
+ email_reply_trimmer (0.4.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
- activesupport (8.0.0)
9
+ activesupport (8.1.3.1)
10
10
  base64
11
- benchmark (>= 0.3)
12
11
  bigdecimal
13
12
  concurrent-ruby (~> 1.0, >= 1.3.1)
14
13
  connection_pool (>= 2.2.5)
15
14
  drb
16
15
  i18n (>= 1.6, < 2)
16
+ json
17
17
  logger (>= 1.4.2)
18
18
  minitest (>= 5.1)
19
19
  securerandom (>= 0.3)
20
20
  tzinfo (~> 2.0, >= 2.0.5)
21
21
  uri (>= 0.13.1)
22
- ast (2.4.2)
23
- base64 (0.2.0)
24
- benchmark (0.4.0)
25
- bigdecimal (3.1.8)
26
- concurrent-ruby (1.3.4)
27
- connection_pool (2.4.1)
28
- drb (2.2.1)
29
- i18n (1.14.6)
22
+ ast (2.4.3)
23
+ base64 (0.3.0)
24
+ bigdecimal (4.1.2)
25
+ concurrent-ruby (1.3.8)
26
+ connection_pool (3.0.2)
27
+ drb (2.2.3)
28
+ i18n (1.15.2)
30
29
  concurrent-ruby (~> 1.0)
31
- json (2.8.2)
32
- language_server-protocol (3.17.0.3)
33
- logger (1.6.1)
34
- minitest (5.25.2)
35
- parallel (1.26.3)
36
- parser (3.3.6.0)
30
+ json (2.21.2)
31
+ language_server-protocol (3.17.0.6)
32
+ lint_roller (1.1.0)
33
+ logger (1.7.0)
34
+ minitest (5.27.0)
35
+ parallel (2.1.0)
36
+ parser (3.3.12.0)
37
37
  ast (~> 2.4.1)
38
38
  racc
39
+ prism (1.9.0)
39
40
  racc (1.8.1)
40
- rack (3.1.8)
41
+ rack (3.2.6)
41
42
  rainbow (3.1.1)
42
- rake (12.3.3)
43
- regexp_parser (2.9.2)
44
- rubocop (1.69.0)
43
+ rake (13.4.2)
44
+ regexp_parser (2.12.0)
45
+ rubocop (1.88.2)
45
46
  json (~> 2.3)
46
- language_server-protocol (>= 3.17.0)
47
- parallel (~> 1.10)
47
+ language_server-protocol (~> 3.17.0.2)
48
+ lint_roller (~> 1.1.0)
49
+ parallel (>= 1.10)
48
50
  parser (>= 3.3.0.2)
49
51
  rainbow (>= 2.2.2, < 4.0)
50
- regexp_parser (>= 2.4, < 3.0)
51
- rubocop-ast (>= 1.36.1, < 2.0)
52
+ regexp_parser (>= 2.9.3, < 3.0)
53
+ rubocop-ast (>= 1.49.0, < 2.0)
52
54
  ruby-progressbar (~> 1.7)
53
55
  unicode-display_width (>= 2.4.0, < 4.0)
54
- rubocop-ast (1.36.2)
55
- parser (>= 3.3.1.0)
56
- rubocop-capybara (2.21.0)
57
- rubocop (~> 1.41)
58
- rubocop-discourse (3.8.6)
56
+ rubocop-ast (1.50.0)
57
+ parser (>= 3.3.7.2)
58
+ prism (~> 1.7)
59
+ rubocop-capybara (3.0.0)
60
+ lint_roller (~> 1.1)
61
+ rubocop (~> 1.81)
62
+ rubocop-discourse (3.18.0)
59
63
  activesupport (>= 6.1)
60
- rubocop (>= 1.59.0)
61
- rubocop-capybara (>= 2.0.0)
62
- rubocop-factory_bot (>= 2.0.0)
63
- rubocop-rails (>= 2.25.0)
64
+ lint_roller (>= 1.1.0)
65
+ rubocop-capybara (>= 2.23.0)
66
+ rubocop-discourse-base (>= 1.0.0)
67
+ rubocop-factory_bot (>= 2.27.0)
68
+ rubocop-rails (>= 2.30.3)
64
69
  rubocop-rspec (>= 3.0.1)
65
- rubocop-rspec_rails (>= 2.30.0)
66
- rubocop-factory_bot (2.26.1)
67
- rubocop (~> 1.61)
68
- rubocop-rails (2.27.0)
70
+ rubocop-rspec_rails (>= 2.31.0)
71
+ rubocop-discourse-base (1.0.0)
72
+ rubocop (>= 1.80.0)
73
+ rubocop-factory_bot (2.28.0)
74
+ lint_roller (~> 1.1)
75
+ rubocop (~> 1.72, >= 1.72.1)
76
+ rubocop-rails (2.36.0)
69
77
  activesupport (>= 4.2.0)
78
+ lint_roller (~> 1.1)
70
79
  rack (>= 1.1)
71
- rubocop (>= 1.52.0, < 2.0)
72
- rubocop-ast (>= 1.31.1, < 2.0)
73
- rubocop-rspec (3.2.0)
74
- rubocop (~> 1.61)
75
- rubocop-rspec_rails (2.30.0)
76
- rubocop (~> 1.61)
77
- rubocop-rspec (~> 3, >= 3.0.1)
80
+ rubocop (>= 1.75.0, < 2.0)
81
+ rubocop-ast (>= 1.44.0, < 2.0)
82
+ rubocop-rspec (3.10.2)
83
+ lint_roller (~> 1.1)
84
+ regexp_parser (>= 2.0)
85
+ rubocop (~> 1.86, >= 1.86.2)
86
+ rubocop-rspec_rails (2.32.0)
87
+ lint_roller (~> 1.1)
88
+ rubocop (~> 1.72, >= 1.72.1)
89
+ rubocop-rspec (~> 3.5)
78
90
  ruby-progressbar (1.13.0)
79
- securerandom (0.3.2)
91
+ securerandom (0.4.1)
80
92
  tzinfo (2.0.6)
81
93
  concurrent-ruby (~> 1.0)
82
- unicode-display_width (3.1.2)
83
- unicode-emoji (~> 4.0, >= 4.0.4)
84
- unicode-emoji (4.0.4)
85
- uri (1.0.2)
94
+ unicode-display_width (3.2.0)
95
+ unicode-emoji (~> 4.1)
96
+ unicode-emoji (4.2.0)
97
+ uri (1.1.1)
86
98
 
87
99
  PLATFORMS
100
+ arm64-darwin-23
88
101
  ruby
89
102
 
90
103
  DEPENDENCIES
91
104
  email_reply_trimmer!
92
105
  minitest (~> 5)
93
- rake (~> 12)
106
+ rake (~> 13)
94
107
  rubocop
95
108
  rubocop-discourse
96
109
 
110
+ CHECKSUMS
111
+ activesupport (8.1.3.1) sha256=85458765f25ea48b9019c46b6bb3fa5683197bf4280d9f06710a6e8d7a831376
112
+ ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
113
+ base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
114
+ bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd
115
+ concurrent-ruby (1.3.8) sha256=b2f1be836e968ccc78ccfce277ea79c72a88633f22306782c16ff23fb415d1e1
116
+ connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a
117
+ drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373
118
+ email_reply_trimmer (0.4.0)
119
+ i18n (1.15.2) sha256=00f9eb62412fe593b2a65a97daa75300d37abb8f7202ec748e94b6d46a9dd1b5
120
+ json (2.21.2) sha256=1f1d3b7cf2b3ba1a69beca0bb6db13d5438b80bff3cd54cdaaa620b9b07c1c6a
121
+ language_server-protocol (3.17.0.6) sha256=5ef2c0c138f8267e1bc631d3328347d354f96724b0af22f2c79516120443b7f0
122
+ lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
123
+ logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
124
+ minitest (5.27.0) sha256=2d3b17f8a36fe7801c1adcffdbc38233b938eb0b4966e97a6739055a45fa77d5
125
+ parallel (2.1.0) sha256=b35258865c2e31134c5ecb708beaaf6772adf9d5efae28e93e99260877b09356
126
+ parser (3.3.12.0) sha256=21a6d7f755d5a24dfbdc6e6b772e4e879a52e7631a88bc5a3a134606052c9828
127
+ prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
128
+ racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
129
+ rack (3.2.6) sha256=5ed78e1f73b2e25679bec7d45ee2d4483cc4146eb1be0264fc4d94cb5ef212c2
130
+ rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
131
+ rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
132
+ regexp_parser (2.12.0) sha256=35a916a1d63190ab5c9009457136ae5f3c0c7512d60291d0d1378ba18ce08ebb
133
+ rubocop (1.88.2) sha256=8def251c90cd955feb4daa3edc0ab56893250c4ce90ef81e6c80c03f9a939bbf
134
+ rubocop-ast (1.50.0) sha256=b9ca88300da0803ee222ad20cdb30494c0a784eed06fdc35d254b06d662788db
135
+ rubocop-capybara (3.0.0) sha256=7a64655238acda7f8f3c87e37ac825a64c615a79c17c253f1a28270dc3768c4b
136
+ rubocop-discourse (3.18.0) sha256=bdb31f13cbb1ed82443b5a53b36efbce3b52a77b7a0f3ee7b421ce81d937ce0b
137
+ rubocop-discourse-base (1.0.0) sha256=a4121f0f2a8e32c3259fee22106af9fd35372cbeac14b0c69673bdee79b472b7
138
+ rubocop-factory_bot (2.28.0) sha256=4b17fc02124444173317e131759d195b0d762844a71a29fe8139c1105d92f0cb
139
+ rubocop-rails (2.36.0) sha256=105a5f5b0001ff2ef28a3af90da50862464e0775e8d0016d599079c0ca408bdb
140
+ rubocop-rspec (3.10.2) sha256=0b3e2ecc592cd10ecbf0095bb58d1e357905276e069643523cc19eb7495f65e2
141
+ rubocop-rspec_rails (2.32.0) sha256=4a0d641c72f6ebb957534f539d9d0a62c47abd8ce0d0aeee1ef4701e892a9100
142
+ ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
143
+ securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1
144
+ tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b
145
+ unicode-display_width (3.2.0) sha256=0cdd96b5681a5949cdbc2c55e7b420facae74c4aaf9a9815eee1087cb1853c42
146
+ unicode-emoji (4.2.0) sha256=519e69150f75652e40bf736106cfbc8f0f73aa3fb6a65afe62fefa7f80b0f80f
147
+ uri (1.1.1) sha256=379fa58d27ffb1387eaada68c749d1426738bd0f654d812fcc07e7568f5c57c6
148
+
97
149
  BUNDLED WITH
98
- 2.5.22
150
+ 4.0.10
data/Rakefile CHANGED
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- name = Dir["*.gemspec"].first.split(".").first
4
- version = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*['"](?'version'\d+\.\d+\.\d+)['"]/, "version"]
5
-
6
3
  task default: :test
7
4
 
8
5
  require "rake/testtask"
@@ -19,7 +19,7 @@ Gem::Specification.new do |s|
19
19
  s.files = Dir["**/*"].reject { |path| File.directory?(path) || path =~ /.*\.gem$/ }
20
20
  s.test_files = s.files.select { |path| path =~ /^test\/.+_test\.rb$/ }
21
21
 
22
- s.add_development_dependency 'rake', '~> 12'
22
+ s.add_development_dependency 'rake', '~> 13'
23
23
  s.add_development_dependency 'minitest', '~> 5'
24
24
  s.add_development_dependency 'rubocop'
25
25
  s.add_development_dependency 'rubocop-discourse'
@@ -4,7 +4,17 @@ class DelimiterMatcher
4
4
  DELIMITER_CHARACTERS = "-_,=+~#*ᐧ—"
5
5
  DELIMITER_REGEX = /^[[:blank:]]*[#{Regexp.escape(DELIMITER_CHARACTERS)}]+[[:blank:]]*$/
6
6
 
7
+ # A single "-" or "*" on its own line (ignoring surrounding blanks) is the
8
+ # way many mail clients render a plain-text bullet list item, not a reply
9
+ # or signature separator. Real separators built from these characters use
10
+ # at least two of them ("--", "***", "-----"), so excluding the lone case
11
+ # avoids discarding bullet-list content while leaving genuine separators
12
+ # (and every other delimiter character) untouched.
13
+ BULLET_MARKER_REGEX = /\A[[:blank:]]*[-*][[:blank:]]*\z/
14
+
7
15
  def self.match?(line)
16
+ return false if BULLET_MARKER_REGEX.match?(line)
17
+
8
18
  line =~ DELIMITER_REGEX
9
19
  end
10
20
 
@@ -87,7 +87,7 @@ class EmbeddedEmailMatcher
87
87
  # 2013/10/2 camilohollanda <info@discourse.org>
88
88
  # вт, 5 янв. 2016 г. в 23:39, Erlend Sogge Heggen <info@discourse.org>:
89
89
  # ср, 1 апр. 2015, 18:29, Denis Didkovsky <info@discourse.org>:
90
- DATE_SOMEONE_EMAIL_REGEX = /\d{4}.{1,80}\s?<[^@<>]+@[^@<>.]+\.[^@<>]+>:?$/
90
+ DATE_SOMEONE_EMAIL_REGEX = /(?!.*<img\b)\d{4}.{1,80}\s?<[^@<>]+@[^@<>.]+\.[^@<>]+>:?$/i
91
91
 
92
92
  # codinghorror via Discourse Meta wrote:
93
93
  # codinghorror via Discourse Meta <info@discourse.org> schrieb:
@@ -8,7 +8,7 @@ require_relative "email_reply_trimmer/quote_matcher"
8
8
  require 'securerandom'
9
9
 
10
10
  class EmailReplyTrimmer
11
- VERSION = "0.2.0"
11
+ VERSION = "0.4.0"
12
12
 
13
13
  DELIMITER = "d"
14
14
  EMBEDDED = "b"
@@ -47,8 +47,13 @@ class EmailReplyTrimmer
47
47
  # remove everything after the first delimiter
48
48
  if pattern =~ /d/
49
49
  index = pattern =~ /d/
50
- pattern = pattern[0...index]
51
- lines = lines[0...index]
50
+ underscore_separator_before_embedded_email =
51
+ lines[index] =~ /\A_+\z/ && pattern[(index + 1)..-1] =~ /b/
52
+
53
+ unless underscore_separator_before_embedded_email
54
+ pattern = pattern[0...index]
55
+ lines = lines[0...index]
56
+ end
52
57
  end
53
58
 
54
59
  # remove all mobile signatures
@@ -75,7 +80,7 @@ class EmailReplyTrimmer
75
80
 
76
81
  # if there is an embedded email marker, followed by a huge quote
77
82
  # then take everything up to that marker
78
- if pattern =~ /te*b[eqbh]*([te]*)$/ && $1.count("t") < 7
83
+ if pattern =~ /te*b[eqbh]*([te]*)$/ && $1.count("t") < 7 && pattern !~ /bq[eqbh]*t/
79
84
  index = pattern =~ /te*b[eqbh]*[te]*$/
80
85
  pattern = pattern[0..index]
81
86
  lines = lines[0..index]
File without changes
@@ -0,0 +1,3 @@
1
+ Am 13.05.2026 um 09:05 schrieb Vorname Nachname:
2
+ > Hier steht der alte Nachrichtentext.
3
+ > Dieser Text gehört vollständig zur alten Nachricht.
@@ -0,0 +1,6 @@
1
+ ----- Original Message -----
2
+ From: Firstname Lastname <name@example.test>
3
+ Date: July 28, 2026
4
+ Subject: Example
5
+
6
+ This is the previous message.
@@ -0,0 +1,2 @@
1
+ On July 28, 2026, Firstname Lastname wrote:
2
+ > This is the previous quoted message.
@@ -0,0 +1,2 @@
1
+ On July 28, 2026, Firstname Lastname wrote:
2
+ > This is the previous quoted message.
@@ -0,0 +1,2 @@
1
+ --
2
+ Signature line that should be trimmed.
@@ -0,0 +1 @@
1
+ Am 13.05.2026 um 09:05 schrieb Vorname Nachname:
@@ -0,0 +1,2 @@
1
+ On July 28, 2026, Firstname Lastname wrote:
2
+ > This is the previous quoted message.
@@ -0,0 +1,8 @@
1
+ I looked at the proposal and would like the following changes:
2
+
3
+ -
4
+
5
+ Rename the first field on the form.
6
+ *
7
+
8
+ Add a short description below it.
@@ -0,0 +1,10 @@
1
+ Hallo Vorname,
2
+
3
+ hier ist meine neue Antwort.
4
+
5
+ Viele Grüße
6
+ Vorname Nachname
7
+
8
+ Am 13.05.2026 um 09:05 schrieb Vorname Nachname:
9
+ > Hier steht der alte Nachrichtentext.
10
+ > Dieser Text gehört vollständig zur alten Nachricht.
@@ -0,0 +1,8 @@
1
+ This is my new reply.
2
+
3
+ ----- Original Message -----
4
+ From: Firstname Lastname <name@example.test>
5
+ Date: July 28, 2026
6
+ Subject: Example
7
+
8
+ This is the previous message.
@@ -0,0 +1,13 @@
1
+ Hello Firstname,
2
+
3
+ This is the first part of the new reply.
4
+
5
+ ________________________________________________________________________________
6
+
7
+ This is the second part of the new reply.
8
+
9
+ Best regards,
10
+ Firstname Lastname
11
+
12
+ On July 28, 2026, Firstname Lastname wrote:
13
+ > This is the previous quoted message.
@@ -0,0 +1,15 @@
1
+ Hello Firstname,
2
+
3
+ This is text before the inline image.
4
+
5
+ **Screenshot**
6
+
7
+ Image reference 2064 <img apple-inline="yes" id="A34F880A-3D2D-41B4-A4EE-22EA00C25DB3" src="cid:EBAE619D-7B1D-47E7-A652-BDB94969843A@example.test" class="">
8
+
9
+ This is text after the inline image.
10
+
11
+ Best regards,
12
+ Firstname Lastname
13
+
14
+ On July 28, 2026, Firstname Lastname wrote:
15
+ > This is the previous quoted message.
@@ -0,0 +1,9 @@
1
+ Here is my reply with a bullet list:
2
+
3
+ -
4
+ First item.
5
+ *
6
+ Second item.
7
+
8
+ --
9
+ Signature line that should be trimmed.
@@ -0,0 +1,9 @@
1
+ Hallo Vorname,
2
+
3
+ Am 13.05.2026 um 09:05 schrieb Vorname Nachname:
4
+ > Hier steht der zitierte Text.
5
+
6
+ Hier ist der Antworttext. Dieser wurde bisher abgeschnitten.
7
+
8
+ Viele Grüße
9
+ Vorname Nachname
@@ -0,0 +1,12 @@
1
+ Hello Firstname,
2
+
3
+ This is text before the inline images.
4
+
5
+ <img src="cid:first-image@example.test">
6
+ <img src="cid:second-image@example.test">
7
+ <img src="cid:third-image@example.test">
8
+
9
+ This is text after the inline images.
10
+
11
+ On July 28, 2026, Firstname Lastname wrote:
12
+ > This is the previous quoted message.
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  require "minitest/autorun"
3
+ require "timeout"
3
4
  require "email_reply_trimmer"
4
5
 
5
6
  class TestEmailReplyTrimmer < Minitest::Test
@@ -0,0 +1,8 @@
1
+ I looked at the proposal and would like the following changes:
2
+
3
+ -
4
+
5
+ Rename the first field on the form.
6
+ *
7
+
8
+ Add a short description below it.
@@ -0,0 +1,6 @@
1
+ Hallo Vorname,
2
+
3
+ hier ist meine neue Antwort.
4
+
5
+ Viele Grüße
6
+ Vorname Nachname
@@ -0,0 +1 @@
1
+ This is my new reply.
@@ -0,0 +1,10 @@
1
+ Hello Firstname,
2
+
3
+ This is the first part of the new reply.
4
+
5
+ ________________________________________________________________________________
6
+
7
+ This is the second part of the new reply.
8
+
9
+ Best regards,
10
+ Firstname Lastname
@@ -0,0 +1,12 @@
1
+ Hello Firstname,
2
+
3
+ This is text before the inline image.
4
+
5
+ **Screenshot**
6
+
7
+ Image reference 2064 <img apple-inline="yes" id="A34F880A-3D2D-41B4-A4EE-22EA00C25DB3" src="cid:EBAE619D-7B1D-47E7-A652-BDB94969843A@example.test" class="">
8
+
9
+ This is text after the inline image.
10
+
11
+ Best regards,
12
+ Firstname Lastname
@@ -0,0 +1,6 @@
1
+ Here is my reply with a bullet list:
2
+
3
+ -
4
+ First item.
5
+ *
6
+ Second item.
@@ -0,0 +1,8 @@
1
+ Hallo Vorname,
2
+
3
+ > Hier steht der zitierte Text.
4
+
5
+ Hier ist der Antworttext. Dieser wurde bisher abgeschnitten.
6
+
7
+ Viele Grüße
8
+ Vorname Nachname
@@ -0,0 +1,9 @@
1
+ Hello Firstname,
2
+
3
+ This is text before the inline images.
4
+
5
+ <img src="cid:first-image@example.test">
6
+ <img src="cid:second-image@example.test">
7
+ <img src="cid:third-image@example.test">
8
+
9
+ This is text after the inline images.
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: email_reply_trimmer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Régis Hanol
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-11-29 00:00:00.000000000 Z
10
+ date: 2026-08-14 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rake
@@ -16,14 +15,14 @@ dependencies:
16
15
  requirements:
17
16
  - - "~>"
18
17
  - !ruby/object:Gem::Version
19
- version: '12'
18
+ version: '13'
20
19
  type: :development
21
20
  prerelease: false
22
21
  version_requirements: !ruby/object:Gem::Requirement
23
22
  requirements:
24
23
  - - "~>"
25
24
  - !ruby/object:Gem::Version
26
- version: '12'
25
+ version: '13'
27
26
  - !ruby/object:Gem::Dependency
28
27
  name: minitest
29
28
  requirement: !ruby/object:Gem::Requirement
@@ -79,9 +78,6 @@ files:
79
78
  - LICENSE
80
79
  - README.md
81
80
  - Rakefile
82
- - devenv.lock
83
- - devenv.nix
84
- - devenv.yaml
85
81
  - email_reply_trimmer.gemspec
86
82
  - lib/email_reply_trimmer.rb
87
83
  - lib/email_reply_trimmer/delimiter_matcher.rb
@@ -101,6 +97,10 @@ files:
101
97
  - test/before/forwarded_gmail.txt
102
98
  - test/before/forwarded_message.txt
103
99
  - test/elided/block_code_spacers.txt
100
+ - test/elided/bullet_list_reply.txt
101
+ - test/elided/complete_german_quote_at_end.txt
102
+ - test/elided/delimiter_before_forwarded_message.txt
103
+ - test/elided/delimiter_inside_new_reply.txt
104
104
  - test/elided/delimiters.txt
105
105
  - test/elided/dual_embedded.txt
106
106
  - test/elided/email_headers_1.txt
@@ -154,10 +154,14 @@ files:
154
154
  - test/elided/forwarded_apple.txt
155
155
  - test/elided/forwarded_gmail.txt
156
156
  - test/elided/forwarded_message.txt
157
+ - test/elided/inline_images_before_reply_text.txt
158
+ - test/elided/lone_bullet_vs_separator.txt
157
159
  - test/elided/normalize_line_endings.txt
158
160
  - test/elided/quote_and_text.txt
159
161
  - test/elided/quote_only.txt
162
+ - test/elided/reply_after_selected_german_quote.txt
160
163
  - test/elided/retains_spaces_and_formatting.txt
164
+ - test/elided/separate_inline_images_before_reply_text.txt
161
165
  - test/elided/signatures.txt
162
166
  - test/elided/spam_1.txt
163
167
  - test/elided/spam_2.txt
@@ -165,6 +169,10 @@ files:
165
169
  - test/elided/text_only.txt
166
170
  - test/elided/usenet.txt
167
171
  - test/emails/block_code_spacers.txt
172
+ - test/emails/bullet_list_reply.txt
173
+ - test/emails/complete_german_quote_at_end.txt
174
+ - test/emails/delimiter_before_forwarded_message.txt
175
+ - test/emails/delimiter_inside_new_reply.txt
168
176
  - test/emails/delimiters.txt
169
177
  - test/emails/dual_embedded.txt
170
178
  - test/emails/email_headers_1.txt
@@ -218,10 +226,14 @@ files:
218
226
  - test/emails/forwarded_apple.txt
219
227
  - test/emails/forwarded_gmail.txt
220
228
  - test/emails/forwarded_message.txt
229
+ - test/emails/inline_images_before_reply_text.txt
230
+ - test/emails/lone_bullet_vs_separator.txt
221
231
  - test/emails/normalize_line_endings.txt
222
232
  - test/emails/quote_and_text.txt
223
233
  - test/emails/quote_only.txt
234
+ - test/emails/reply_after_selected_german_quote.txt
224
235
  - test/emails/retains_spaces_and_formatting.txt
236
+ - test/emails/separate_inline_images_before_reply_text.txt
225
237
  - test/emails/signatures.txt
226
238
  - test/emails/spam_1.txt
227
239
  - test/emails/spam_2.txt
@@ -242,6 +254,10 @@ files:
242
254
  - test/test_email_matcher.rb
243
255
  - test/test_email_reply_trimmer.rb
244
256
  - test/trimmed/block_code_spacers.txt
257
+ - test/trimmed/bullet_list_reply.txt
258
+ - test/trimmed/complete_german_quote_at_end.txt
259
+ - test/trimmed/delimiter_before_forwarded_message.txt
260
+ - test/trimmed/delimiter_inside_new_reply.txt
245
261
  - test/trimmed/delimiters.txt
246
262
  - test/trimmed/dual_embedded.txt
247
263
  - test/trimmed/email_headers_1.txt
@@ -295,10 +311,14 @@ files:
295
311
  - test/trimmed/forwarded_apple.txt
296
312
  - test/trimmed/forwarded_gmail.txt
297
313
  - test/trimmed/forwarded_message.txt
314
+ - test/trimmed/inline_images_before_reply_text.txt
315
+ - test/trimmed/lone_bullet_vs_separator.txt
298
316
  - test/trimmed/normalize_line_endings.txt
299
317
  - test/trimmed/quote_and_text.txt
300
318
  - test/trimmed/quote_only.txt
319
+ - test/trimmed/reply_after_selected_german_quote.txt
301
320
  - test/trimmed/retains_spaces_and_formatting.txt
321
+ - test/trimmed/separate_inline_images_before_reply_text.txt
302
322
  - test/trimmed/signatures.txt
303
323
  - test/trimmed/spam_1.txt
304
324
  - test/trimmed/spam_2.txt
@@ -309,7 +329,6 @@ homepage: https://github.com/discourse/email_reply_trimmer
309
329
  licenses:
310
330
  - MIT
311
331
  metadata: {}
312
- post_install_message:
313
332
  rdoc_options: []
314
333
  require_paths:
315
334
  - lib
@@ -324,8 +343,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
324
343
  - !ruby/object:Gem::Version
325
344
  version: '0'
326
345
  requirements: []
327
- rubygems_version: 3.4.19
328
- signing_key:
346
+ rubygems_version: 4.0.10
329
347
  specification_version: 4
330
348
  summary: Library to trim replies from plain text email.
331
349
  test_files: []
data/devenv.lock DELETED
@@ -1,184 +0,0 @@
1
- {
2
- "nodes": {
3
- "devenv": {
4
- "locked": {
5
- "dir": "src/modules",
6
- "lastModified": 1732830318,
7
- "owner": "cachix",
8
- "repo": "devenv",
9
- "rev": "51abcb75d471a215c800937d4e30dc765d305c6d",
10
- "type": "github"
11
- },
12
- "original": {
13
- "dir": "src/modules",
14
- "owner": "cachix",
15
- "repo": "devenv",
16
- "type": "github"
17
- }
18
- },
19
- "flake-compat": {
20
- "flake": false,
21
- "locked": {
22
- "lastModified": 1732722421,
23
- "owner": "edolstra",
24
- "repo": "flake-compat",
25
- "rev": "9ed2ac151eada2306ca8c418ebd97807bb08f6ac",
26
- "type": "github"
27
- },
28
- "original": {
29
- "owner": "edolstra",
30
- "repo": "flake-compat",
31
- "type": "github"
32
- }
33
- },
34
- "flake-compat_2": {
35
- "flake": false,
36
- "locked": {
37
- "lastModified": 1732722421,
38
- "owner": "edolstra",
39
- "repo": "flake-compat",
40
- "rev": "9ed2ac151eada2306ca8c418ebd97807bb08f6ac",
41
- "type": "github"
42
- },
43
- "original": {
44
- "owner": "edolstra",
45
- "repo": "flake-compat",
46
- "type": "github"
47
- }
48
- },
49
- "flake-utils": {
50
- "inputs": {
51
- "systems": "systems"
52
- },
53
- "locked": {
54
- "lastModified": 1731533236,
55
- "owner": "numtide",
56
- "repo": "flake-utils",
57
- "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
58
- "type": "github"
59
- },
60
- "original": {
61
- "owner": "numtide",
62
- "repo": "flake-utils",
63
- "type": "github"
64
- }
65
- },
66
- "gitignore": {
67
- "inputs": {
68
- "nixpkgs": [
69
- "pre-commit-hooks",
70
- "nixpkgs"
71
- ]
72
- },
73
- "locked": {
74
- "lastModified": 1709087332,
75
- "owner": "hercules-ci",
76
- "repo": "gitignore.nix",
77
- "rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
78
- "type": "github"
79
- },
80
- "original": {
81
- "owner": "hercules-ci",
82
- "repo": "gitignore.nix",
83
- "type": "github"
84
- }
85
- },
86
- "nixpkgs": {
87
- "locked": {
88
- "lastModified": 1732617236,
89
- "owner": "nixos",
90
- "repo": "nixpkgs",
91
- "rev": "af51545ec9a44eadf3fe3547610a5cdd882bc34e",
92
- "type": "github"
93
- },
94
- "original": {
95
- "owner": "nixos",
96
- "ref": "nixpkgs-unstable",
97
- "repo": "nixpkgs",
98
- "type": "github"
99
- }
100
- },
101
- "nixpkgs-ruby": {
102
- "inputs": {
103
- "flake-compat": "flake-compat",
104
- "flake-utils": "flake-utils",
105
- "nixpkgs": [
106
- "nixpkgs"
107
- ]
108
- },
109
- "locked": {
110
- "lastModified": 1730958464,
111
- "owner": "bobvanderlinden",
112
- "repo": "nixpkgs-ruby",
113
- "rev": "93bd040be2856ba0e44a33db6360e8c9c0c09aa1",
114
- "type": "github"
115
- },
116
- "original": {
117
- "owner": "bobvanderlinden",
118
- "repo": "nixpkgs-ruby",
119
- "type": "github"
120
- }
121
- },
122
- "nixpkgs-stable": {
123
- "locked": {
124
- "lastModified": 1732749044,
125
- "owner": "NixOS",
126
- "repo": "nixpkgs",
127
- "rev": "0c5b4ecbed5b155b705336aa96d878e55acd8685",
128
- "type": "github"
129
- },
130
- "original": {
131
- "owner": "NixOS",
132
- "ref": "nixos-24.05",
133
- "repo": "nixpkgs",
134
- "type": "github"
135
- }
136
- },
137
- "pre-commit-hooks": {
138
- "inputs": {
139
- "flake-compat": "flake-compat_2",
140
- "gitignore": "gitignore",
141
- "nixpkgs": [
142
- "nixpkgs"
143
- ],
144
- "nixpkgs-stable": "nixpkgs-stable"
145
- },
146
- "locked": {
147
- "lastModified": 1732021966,
148
- "owner": "cachix",
149
- "repo": "pre-commit-hooks.nix",
150
- "rev": "3308484d1a443fc5bc92012435d79e80458fe43c",
151
- "type": "github"
152
- },
153
- "original": {
154
- "owner": "cachix",
155
- "repo": "pre-commit-hooks.nix",
156
- "type": "github"
157
- }
158
- },
159
- "root": {
160
- "inputs": {
161
- "devenv": "devenv",
162
- "nixpkgs": "nixpkgs",
163
- "nixpkgs-ruby": "nixpkgs-ruby",
164
- "pre-commit-hooks": "pre-commit-hooks"
165
- }
166
- },
167
- "systems": {
168
- "locked": {
169
- "lastModified": 1681028828,
170
- "owner": "nix-systems",
171
- "repo": "default",
172
- "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
173
- "type": "github"
174
- },
175
- "original": {
176
- "owner": "nix-systems",
177
- "repo": "default",
178
- "type": "github"
179
- }
180
- }
181
- },
182
- "root": "root",
183
- "version": 7
184
- }
data/devenv.nix DELETED
@@ -1,4 +0,0 @@
1
- {
2
- languages.ruby.enable = true;
3
- languages.ruby.version = "3.2";
4
- }
data/devenv.yaml DELETED
@@ -1,8 +0,0 @@
1
- inputs:
2
- nixpkgs-ruby:
3
- url: github:bobvanderlinden/nixpkgs-ruby
4
- inputs:
5
- nixpkgs:
6
- follows: nixpkgs
7
- nixpkgs:
8
- url: github:nixos/nixpkgs/nixpkgs-unstable