sup 1.0 → 1.2

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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/checks.yml +58 -0
  3. data/.rubocop.yml +5 -0
  4. data/CONTRIBUTORS +5 -2
  5. data/Gemfile +5 -1
  6. data/History.txt +33 -0
  7. data/Manifest.txt +171 -0
  8. data/README.md +9 -4
  9. data/Rakefile +40 -1
  10. data/bin/sup-add +4 -8
  11. data/bin/sup-sync-back-maildir +1 -1
  12. data/contrib/nix/Gemfile +22 -0
  13. data/contrib/nix/Gemfile.lock +80 -0
  14. data/contrib/nix/README +7 -0
  15. data/contrib/nix/gem-install-shell.nix +12 -0
  16. data/contrib/nix/gemset.nix +339 -0
  17. data/contrib/nix/ruby2.4-Gemfile.lock +81 -0
  18. data/contrib/nix/ruby2.4-gemset.nix +309 -0
  19. data/contrib/nix/ruby2.4-shell.nix +30 -0
  20. data/contrib/nix/ruby2.5-Gemfile.lock +81 -0
  21. data/contrib/nix/ruby2.5-gemset.nix +309 -0
  22. data/contrib/nix/ruby2.5-shell.nix +30 -0
  23. data/contrib/nix/ruby2.6-Gemfile.lock +83 -0
  24. data/contrib/nix/ruby2.6-gemset.nix +319 -0
  25. data/contrib/nix/ruby2.6-shell.nix +30 -0
  26. data/contrib/nix/ruby2.7-shell.nix +23 -0
  27. data/contrib/nix/ruby3.0-shell.nix +23 -0
  28. data/contrib/nix/ruby3.1-shell.nix +23 -0
  29. data/contrib/nix/ruby3.2-shell.nix +23 -0
  30. data/contrib/nix/ruby3.3-shell.nix +23 -0
  31. data/contrib/nix/test-all-rubies.sh +6 -0
  32. data/doc/Hooks.txt +1 -1
  33. data/ext/mkrf_conf_xapian.rb +12 -6
  34. data/lib/sup/colormap.rb +1 -1
  35. data/lib/sup/crypto.rb +1 -1
  36. data/lib/sup/hook.rb +1 -1
  37. data/lib/sup/index.rb +4 -4
  38. data/lib/sup/keymap.rb +1 -1
  39. data/lib/sup/maildir.rb +5 -5
  40. data/lib/sup/mbox.rb +5 -5
  41. data/lib/sup/message.rb +8 -7
  42. data/lib/sup/message_chunks.rb +27 -19
  43. data/lib/sup/modes/completion_mode.rb +0 -1
  44. data/lib/sup/modes/console_mode.rb +1 -1
  45. data/lib/sup/modes/file_browser_mode.rb +2 -2
  46. data/lib/sup/modes/label_list_mode.rb +1 -1
  47. data/lib/sup/modes/search_list_mode.rb +2 -2
  48. data/lib/sup/modes/thread_view_mode.rb +1 -2
  49. data/lib/sup/rfc2047.rb +21 -6
  50. data/lib/sup/source.rb +8 -2
  51. data/lib/sup/textfield.rb +0 -1
  52. data/lib/sup/thread.rb +20 -21
  53. data/lib/sup/util.rb +31 -53
  54. data/lib/sup/version.rb +1 -1
  55. data/lib/sup.rb +12 -8
  56. data/man/sup-add.1 +39 -39
  57. data/man/sup-config.1 +31 -27
  58. data/man/sup-dump.1 +34 -35
  59. data/man/sup-import-dump.1 +36 -32
  60. data/man/sup-psych-ify-config-files.1 +29 -25
  61. data/man/sup-recover-sources.1 +32 -28
  62. data/man/sup-sync-back-maildir.1 +34 -30
  63. data/man/sup-sync.1 +40 -36
  64. data/man/sup-tweak-labels.1 +36 -32
  65. data/man/sup.1 +41 -37
  66. data/shell.nix +1 -0
  67. data/sup.gemspec +6 -4
  68. data/test/dummy_source.rb +21 -15
  69. data/test/fixtures/embedded-message.eml +34 -0
  70. data/test/fixtures/non-ascii-header-in-nested-message.eml +36 -0
  71. data/test/fixtures/non-ascii-header.eml +8 -0
  72. data/test/fixtures/rfc2047-header-encoding.eml +15 -0
  73. data/test/fixtures/text-attachments-with-charset.eml +15 -1
  74. data/test/fixtures/utf8-header.eml +17 -0
  75. data/test/integration/test_maildir.rb +3 -0
  76. data/test/integration/test_mbox.rb +4 -1
  77. data/test/integration/test_sup-add.rb +83 -0
  78. data/test/integration/test_sup-sync-back-maildir.rb +40 -0
  79. data/test/test_crypto.rb +44 -0
  80. data/test/test_header_parsing.rb +11 -3
  81. data/test/test_helper.rb +7 -4
  82. data/test/test_message.rb +124 -32
  83. data/test/test_messages_dir.rb +13 -15
  84. data/test/unit/test_horizontal_selector.rb +4 -4
  85. data/test/unit/test_locale_fiddler.rb +1 -1
  86. data/test/unit/util/test_query.rb +1 -1
  87. data/test/unit/util/test_string.rb +3 -3
  88. data/test/unit/util/test_uri.rb +2 -2
  89. metadata +69 -18
  90. data/.travis.yml +0 -18
  91. data/bin/sup-psych-ify-config-files +0 -21
  92. data/test/integration/test_label_service.rb +0 -18
  93. data/test/test_yaml_migration.rb +0 -85
data/lib/sup/message.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # encoding: UTF-8
2
2
 
3
3
  require 'time'
4
+ require 'string-scrub' if /^2\.0\./ =~ RUBY_VERSION
4
5
 
5
6
  module Redwood
6
7
 
@@ -71,9 +72,9 @@ class Message
71
72
  return unless v
72
73
  return v unless v.is_a? String
73
74
  return unless v.size < MAX_HEADER_VALUE_SIZE # avoid regex blowup on spam
74
- d = v.dup
75
- d = d.transcode($encoding, 'ASCII')
76
- Rfc2047.decode_to $encoding, d
75
+ ## Header values should be either 7-bit with RFC2047-encoded words
76
+ ## or UTF-8 as per RFC6532. Replace any invalid high bytes with U+FFFD.
77
+ Rfc2047.decode_to $encoding, v.dup.force_encoding(Encoding::UTF_8).scrub
77
78
  end
78
79
 
79
80
  def parse_header encoded_header
@@ -104,7 +105,7 @@ class Message
104
105
  when String
105
106
  begin
106
107
  Time.parse date
107
- rescue ArgumentError => e
108
+ rescue ArgumentError
108
109
  #debug "faking mangled date header for #{@id} (orig #{header['date'].inspect} gave error: #{e.message})"
109
110
  Time.now
110
111
  end
@@ -270,7 +271,7 @@ class Message
270
271
  message_to_chunks rmsg
271
272
  rescue SourceError, SocketError, RMail::EncodingUnsupportedError => e
272
273
  warn_with_location "problem reading message #{id}"
273
- debug "could not load message: #{location.inspect}, exception: #{e.inspect}"
274
+ debug "could not load message, exception: #{e.inspect}"
274
275
 
275
276
  [Chunk::Text.new(error_message.split("\n"))]
276
277
 
@@ -309,7 +310,7 @@ EOS
309
310
  end
310
311
 
311
312
  def each_raw_message_line &b
312
- location.each_raw_message_line &b
313
+ location.each_raw_message_line(&b)
313
314
  end
314
315
 
315
316
  def sync_back
@@ -759,7 +760,7 @@ private
759
760
 
760
761
  def warn_with_location msg
761
762
  warn msg
762
- warn "Message is in #{location.source.uri} at #{location.info}"
763
+ warn "Message is in #{@locations}"
763
764
  end
764
765
  end
765
766
 
@@ -128,7 +128,16 @@ EOS
128
128
 
129
129
  text = case @content_type
130
130
  when /^text\/plain\b/
131
- @raw_content.force_encoding(encoded_content.charset || 'US-ASCII')
131
+ if /^UTF-7$/i =~ encoded_content.charset
132
+ @raw_content.decode_utf7
133
+ else
134
+ begin
135
+ charset = Encoding.find(encoded_content.charset || 'US-ASCII')
136
+ rescue ArgumentError
137
+ charset = 'US-ASCII'
138
+ end
139
+ @raw_content.force_encoding(charset)
140
+ end
132
141
  else
133
142
  HookManager.run "mime-decode", :content_type => @content_type,
134
143
  :filename => lambda { write_to_disk },
@@ -274,24 +283,19 @@ EOS
274
283
  class EnclosedMessage
275
284
  attr_reader :lines
276
285
  def initialize from, to, cc, date, subj
277
- @from = from ? "unknown sender" : from.full_address
278
- @to = to ? "" : to.map { |p| p.full_address }.join(", ")
279
- @cc = cc ? "" : cc.map { |p| p.full_address }.join(", ")
280
- if date
281
- @date = date.rfc822
282
- else
283
- @date = ""
284
- end
285
-
286
+ @from = !from ? "unknown sender" : from.full_address
287
+ @to = !to ? "" : to.map { |p| p.full_address }.join(", ")
288
+ @cc = !cc ? "" : cc.map { |p| p.full_address }.join(", ")
289
+ @date = !date ? "" : date.rfc822
286
290
  @subj = subj
287
-
288
- @lines = "\nFrom: #{from}\n"
289
- @lines += "To: #{to}\n"
290
- if !cc.empty?
291
- @lines += "Cc: #{cc}\n"
292
- end
293
- @lines += "Date: #{date}\n"
294
- @lines += "Subject: #{subj}\n\n"
291
+ @lines = [
292
+ "From: #{@from}",
293
+ "To: #{@to}",
294
+ "Cc: #{@cc}",
295
+ "Date: #{@date}",
296
+ "Subject: #{@subj}"
297
+ ]
298
+ @lines.delete_if{ |line| line == 'Cc: ' }
295
299
  end
296
300
 
297
301
  def inlineable?; false end
@@ -302,7 +306,11 @@ EOS
302
306
  def viewable?; false end
303
307
 
304
308
  def patina_color; :generic_notice_patina_color end
305
- def patina_text; "Begin enclosed message sent on #{@date}" end
309
+ def patina_text
310
+ "Begin enclosed message" + (
311
+ @date == "" ? "" : " sent on #{@date}"
312
+ )
313
+ end
306
314
 
307
315
  def color; :quote_color end
308
316
  end
@@ -26,7 +26,6 @@ class CompletionMode < ScrollMode
26
26
  private
27
27
 
28
28
  def update_lines
29
- width = buffer.content_width
30
29
  max_length = @list.max_of { |s| s.length }
31
30
  num_per = [1, buffer.content_width / (max_length + INTERSTITIAL.length)].max
32
31
  @lines = [@header].compact
@@ -11,7 +11,7 @@ class Console
11
11
  end
12
12
 
13
13
  def query(query)
14
- Enumerator.new(Index.instance, :each_message, Index.parse_query(query))
14
+ Index.instance.enum_for :each_message, Index.parse_query(query)
15
15
  end
16
16
 
17
17
  def add_labels(query, *labels)
@@ -43,7 +43,7 @@ protected
43
43
  end
44
44
 
45
45
  def view
46
- name, f = @files[curpos - RESERVED_ROWS]
46
+ _name, f = @files[curpos - RESERVED_ROWS]
47
47
  return unless f && f.file?
48
48
 
49
49
  begin
@@ -54,7 +54,7 @@ protected
54
54
  end
55
55
 
56
56
  def select_file_or_follow_directory
57
- name, f = @files[curpos - RESERVED_ROWS]
57
+ _name, f = @files[curpos - RESERVED_ROWS]
58
58
  return unless f
59
59
 
60
60
  if f.directory? && f.to_s != "."
@@ -129,7 +129,7 @@ protected
129
129
  end
130
130
 
131
131
  def select_label
132
- label, num_unread = @labels[curpos]
132
+ label, _num_unread = @labels[curpos]
133
133
  return unless label
134
134
  LabelSearchResultsMode.spawn_nicely label
135
135
  end
@@ -131,13 +131,13 @@ protected
131
131
  end
132
132
 
133
133
  def select_search
134
- name, num_unread = @searches[curpos]
134
+ name, _num_unread = @searches[curpos]
135
135
  return unless name
136
136
  SearchResultsMode.spawn_from_query SearchManager.search_string_for(name)
137
137
  end
138
138
 
139
139
  def delete_selected_search
140
- name, num_unread = @searches[curpos]
140
+ name, _num_unread = @searches[curpos]
141
141
  return unless name
142
142
  reload if SearchManager.delete name
143
143
  end
@@ -241,7 +241,7 @@ EOS
241
241
 
242
242
  begin
243
243
  u = URI.parse($1)
244
- rescue URI::InvalidURIError => e
244
+ rescue URI::InvalidURIError
245
245
  BufferManager.flash("Invalid unsubscribe link")
246
246
  return
247
247
  end
@@ -868,7 +868,6 @@ private
868
868
  (0 ... text.length).each do |i|
869
869
  @chunk_lines[@text.length + i] = m
870
870
  @message_lines[@text.length + i] = m
871
- lw = text[i].flatten.select { |x| x.is_a? String }.map { |x| x.display_length }.sum
872
871
  end
873
872
 
874
873
  @text += text
data/lib/sup/rfc2047.rb CHANGED
@@ -17,7 +17,7 @@
17
17
  # This file is distributed under the same terms as Ruby.
18
18
 
19
19
  module Rfc2047
20
- WORD = %r{=\?([!\#$%&'*+-/0-9A-Z\\^\`a-z{|}~]+)\?([BbQq])\?([!->@-~]+)\?=} # :nodoc: 'stupid ruby-mode
20
+ WORD = %r{=\?([!\#$%&'*+-/0-9A-Z\\^\`a-z{|}~]+)\?([BbQq])\?([!->@-~ ]+)\?=} # :nodoc: 'stupid ruby-mode
21
21
  WORDSEQ = %r{(#{WORD.source})\s+(?=#{WORD.source})}
22
22
 
23
23
  def Rfc2047.is_encoded? s; s =~ WORD end
@@ -28,16 +28,17 @@ module Rfc2047
28
28
  # converted to the target encoding, it is left in its encoded form.
29
29
  def Rfc2047.decode_to(target, from)
30
30
  from = from.gsub(WORDSEQ, '\1')
31
- out = from.gsub(WORD) do
31
+ from.gsub(WORD) do
32
32
  |word|
33
33
  charset, encoding, text = $1, $2, $3
34
34
 
35
35
  # B64 or QP decode, as necessary:
36
36
  case encoding
37
37
  when 'b', 'B'
38
- #puts text
39
- text = text.unpack('m*')[0]
40
- #puts text.dump
38
+ ## Padding is optional in RFC 2047 words. Add some extra padding
39
+ ## before decoding the base64, otherwise on Ruby 2.0 the final byte
40
+ ## might be discarded.
41
+ text = (text + '===').unpack('m*')[0]
41
42
 
42
43
  when 'q', 'Q'
43
44
  # RFC 2047 has a variant of quoted printable where a ' ' character
@@ -50,7 +51,21 @@ module Rfc2047
50
51
  # WORD.
51
52
  end
52
53
 
53
- text.transcode(target, charset)
54
+ # Handle UTF-7 specially because Ruby doesn't actually support it as
55
+ # a normal character encoding.
56
+ if charset == 'UTF-7'
57
+ begin
58
+ next text.decode_utf7.encode(target)
59
+ rescue ArgumentError, EncodingError
60
+ next word
61
+ end
62
+ end
63
+
64
+ begin
65
+ text.force_encoding(charset).encode(target)
66
+ rescue ArgumentError, EncodingError
67
+ word
68
+ end
54
69
  end
55
70
  end
56
71
  end
data/lib/sup/source.rb CHANGED
@@ -102,7 +102,7 @@ class Source
102
102
  end
103
103
 
104
104
  def synchronize &block
105
- @poll_lock.synchronize &block
105
+ @poll_lock.synchronize(&block)
106
106
  end
107
107
 
108
108
  def try_lock
@@ -153,7 +153,7 @@ class Source
153
153
  next unless Rfc2047.is_encoded? v
154
154
  header[k] = begin
155
155
  Rfc2047.decode_to $encoding, v
156
- rescue Errno::EINVAL, Iconv::InvalidEncoding, Iconv::IllegalSequence => e
156
+ rescue Errno::EINVAL, Iconv::InvalidEncoding, Iconv::IllegalSequence
157
157
  #debug "warning: error decoding RFC 2047 header (#{e.class.name}): #{e.message}"
158
158
  v
159
159
  end
@@ -169,6 +169,12 @@ protected
169
169
  def Source.expand_filesystem_uri uri
170
170
  uri.gsub "~", File.expand_path("~")
171
171
  end
172
+
173
+ def Source.encode_path_for_uri path
174
+ path.gsub(Regexp.new("[#{Regexp.quote(URI_ENCODE_CHARS)}]")) { |c|
175
+ c.each_byte.map { |x| sprintf("%%%02X", x) }.join
176
+ }
177
+ end
172
178
  end
173
179
 
174
180
  ## if you have a @labels instance variable, include this
data/lib/sup/textfield.rb CHANGED
@@ -119,7 +119,6 @@ class TextField
119
119
  Ncurses::Form::REQ_END_FIELD
120
120
  when Ncurses::KEY_UP, Ncurses::KEY_DOWN
121
121
  unless !@i || @history.empty?
122
- value = get_cursed_value
123
122
  #debug "history before #{@history.inspect}"
124
123
  @i = @i + (c.is_keycode?(Ncurses::KEY_UP) ? -1 : 1)
125
124
  @i = 0 if @i < 0
data/lib/sup/thread.rb CHANGED
@@ -86,22 +86,22 @@ class Thread
86
86
  end
87
87
  end
88
88
 
89
- def first; each { |m, *o| return m if m }; nil; end
90
- def has_message?; any? { |m, *o| m.is_a? Message }; end
91
- def dirty?; any? { |m, *o| m && m.dirty? }; end
92
- def date; map { |m, *o| m.date if m }.compact.max; end
89
+ def first; each { |m, *| return m if m }; nil; end
90
+ def has_message?; any? { |m, *| m.is_a? Message }; end
91
+ def dirty?; any? { |m, *| m && m.dirty? }; end
92
+ def date; map { |m, *| m.date if m }.compact.max; end
93
93
  def snippet
94
- with_snippets = select { |m, *o| m && m.snippet && !m.snippet.empty? }
95
- first_unread, * = with_snippets.select { |m, *o| m.has_label?(:unread) }.sort_by { |m, *o| m.date }.first
94
+ with_snippets = select { |m, *| m && m.snippet && !m.snippet.empty? }
95
+ first_unread, * = with_snippets.select { |m, *| m.has_label?(:unread) }.sort_by { |m, *| m.date }.first
96
96
  return first_unread.snippet if first_unread
97
- last_read, * = with_snippets.sort_by { |m, *o| m.date }.last
97
+ last_read, * = with_snippets.sort_by { |m, *| m.date }.last
98
98
  return last_read.snippet if last_read
99
99
  ""
100
100
  end
101
- def authors; map { |m, *o| m.from if m }.compact.uniq; end
101
+ def authors; map { |m, *| m.from if m }.compact.uniq; end
102
102
 
103
- def apply_label t; each { |m, *o| m && m.add_label(t) }; end
104
- def remove_label t; each { |m, *o| m && m.remove_label(t) }; end
103
+ def apply_label t; each { |m, *| m && m.add_label(t) }; end
104
+ def remove_label t; each { |m, *| m && m.remove_label(t) }; end
105
105
 
106
106
  def toggle_label label
107
107
  if has_label? label
@@ -113,24 +113,24 @@ class Thread
113
113
  end
114
114
  end
115
115
 
116
- def set_labels l; each { |m, *o| m && m.labels = l }; end
117
- def has_label? t; any? { |m, *o| m && m.has_label?(t) }; end
118
- def each_dirty_message; each { |m, *o| m && m.dirty? && yield(m) }; end
116
+ def set_labels l; each { |m, *| m && m.labels = l }; end
117
+ def has_label? t; any? { |m, *| m && m.has_label?(t) }; end
118
+ def each_dirty_message; each { |m, *| m && m.dirty? && yield(m) }; end
119
119
 
120
120
  def direct_participants
121
- map { |m, *o| [m.from] + m.to if m }.flatten.compact.uniq
121
+ map { |m, *| [m.from] + m.to if m }.flatten.compact.uniq
122
122
  end
123
123
 
124
124
  def participants
125
- map { |m, *o| [m.from] + m.to + m.cc + m.bcc if m }.flatten.compact.uniq
125
+ map { |m, *| [m.from] + m.to + m.cc + m.bcc if m }.flatten.compact.uniq
126
126
  end
127
127
 
128
- def size; map { |m, *o| m ? 1 : 0 }.sum; end
129
- def subj; argfind { |m, *o| m && m.subj }; end
130
- def labels; inject(Set.new) { |s, (m, *o)| m ? s | m.labels : s } end
128
+ def size; map { |m, *| m ? 1 : 0 }.sum; end
129
+ def subj; argfind { |m, *| m && m.subj }; end
130
+ def labels; inject(Set.new) { |s, (m, *)| m ? s | m.labels : s } end
131
131
  def labels= l
132
132
  raise ArgumentError, "not a set" unless l.is_a?(Set)
133
- each { |m, *o| m && m.labels = l.dup }
133
+ each { |m, *| m && m.labels = l.dup }
134
134
  end
135
135
 
136
136
  def latest_message
@@ -360,7 +360,7 @@ class ThreadSet
360
360
  ## merges in a pre-loaded thread
361
361
  def add_thread t
362
362
  raise "duplicate" if @threads.values.member? t
363
- t.each { |m, *o| add_message m }
363
+ t.each { |m, *| add_message m }
364
364
  end
365
365
 
366
366
  ## merges two threads together. both must be members of this threadset.
@@ -407,7 +407,6 @@ class ThreadSet
407
407
  #puts "adding: #{message.id}, refs #{message.refs.inspect}"
408
408
 
409
409
  el.message = message
410
- oldroot = el.root
411
410
 
412
411
  ## link via references:
413
412
  (message.refs + [el.id]).inject(nil) do |prev, ref_id|
data/lib/sup/util.rb CHANGED
@@ -4,14 +4,16 @@ require 'thread'
4
4
  require 'lockfile'
5
5
  require 'mime/types'
6
6
  require 'pathname'
7
+ require 'rmail'
7
8
  require 'set'
8
9
  require 'enumerator'
9
10
  require 'benchmark'
10
11
  require 'unicode'
11
12
  require 'unicode/display_width'
12
13
  require 'fileutils'
14
+ require 'string-scrub' if /^2\.0\./ =~ RUBY_VERSION
13
15
 
14
- class Lockfile
16
+ module ExtendedLockfile
15
17
  def gen_lock_id
16
18
  Hash[
17
19
  'host' => "#{ Socket.gethostname }",
@@ -37,6 +39,7 @@ class Lockfile
37
39
 
38
40
  def touch_yourself; touch path end
39
41
  end
42
+ Lockfile.send :prepend, ExtendedLockfile
40
43
 
41
44
  class File
42
45
  # platform safe file.link which attempts a copy if hard-linking fails
@@ -106,7 +109,7 @@ module RMail
106
109
  end
107
110
  end
108
111
 
109
- class Serialize
112
+ module CustomizedSerialize
110
113
  ## Don't add MIME-Version headers on serialization. Sup sometimes want's to serialize
111
114
  ## message parts where these headers are not needed and messing with the message on
112
115
  ## serialization breaks gpg signatures. The commented section shows the original RMail
@@ -118,48 +121,7 @@ module RMail
118
121
  # end
119
122
  end
120
123
  end
121
-
122
- class Header
123
-
124
- # Convert to ASCII before trying to match with regexp
125
- class Field
126
-
127
- class << self
128
- def parse(field)
129
- field = field.dup.to_s
130
- field = field.fix_encoding!.ascii
131
- if field =~ EXTRACT_FIELD_NAME_RE
132
- [ $1, $'.chomp ]
133
- else
134
- [ "", Field.value_strip(field) ]
135
- end
136
- end
137
- end
138
- end
139
-
140
- ## Be more cautious about invalid content-type headers
141
- ## the original RMail code calls
142
- ## value.strip.split(/\s*;\s*/)[0].downcase
143
- ## without checking if split returned an element
144
-
145
- # This returns the full content type of this message converted to
146
- # lower case.
147
- #
148
- # If there is no content type header, returns the passed block is
149
- # executed and its return value is returned. If no block is passed,
150
- # the value of the +default+ argument is returned.
151
- def content_type(default = nil)
152
- if value = self['content-type'] and ct = value.strip.split(/\s*;\s*/)[0]
153
- return ct.downcase
154
- else
155
- if block_given?
156
- yield
157
- else
158
- default
159
- end
160
- end
161
- end
162
- end
124
+ Serialize.send :prepend, CustomizedSerialize
163
125
  end
164
126
 
165
127
  class Module
@@ -354,11 +316,8 @@ class String
354
316
  # first try to encode to utf-8 from whatever current encoding
355
317
  encode!('UTF-8', :invalid => :replace, :undef => :replace)
356
318
 
357
- # do this anyway in case string is set to be UTF-8, encoding to
358
- # something else (UTF-16 which can fully represent UTF-8) and back
359
- # ensures invalid chars are replaced.
360
- encode!('UTF-16', 'UTF-8', :invalid => :replace, :undef => :replace)
361
- encode!('UTF-8', 'UTF-16', :invalid => :replace, :undef => :replace)
319
+ # ensure invalid chars are replaced
320
+ scrub!
362
321
 
363
322
  fail "Could not create valid UTF-8 string out of: '#{self.to_s}'." unless valid_encoding?
364
323
 
@@ -392,8 +351,23 @@ class String
392
351
  self
393
352
  end
394
353
 
354
+ ## Decodes UTF-7 and returns the resulting decoded string as UTF-8.
355
+ ##
356
+ ## Ruby doesn't supply a UTF-7 encoding natively. There is
357
+ ## Net::IMAP::decode_utf7 which only handles the IMAP "modified UTF-7"
358
+ ## encoding. This implementation is inspired by that one but handles
359
+ ## standard UTF-7 shift characters and not the IMAP-specific variation.
360
+ def decode_utf7
361
+ gsub(/\+([^-]+)?-/) {
362
+ if $1
363
+ ($1 + "===").unpack("m")[0].encode(Encoding::UTF_8, Encoding::UTF_16BE)
364
+ else
365
+ "+"
366
+ end
367
+ }
368
+ end
369
+
395
370
  def normalize_whitespace
396
- fix_encoding!
397
371
  gsub(/\t/, " ").gsub(/\r/, "")
398
372
  end
399
373
 
@@ -405,7 +379,7 @@ class String
405
379
 
406
380
  unless method_defined? :each
407
381
  def each &b
408
- each_line &b
382
+ each_line(&b)
409
383
  end
410
384
  end
411
385
 
@@ -507,7 +481,9 @@ module Enumerable
507
481
  ret
508
482
  end
509
483
 
510
- def sum; inject(0) { |x, y| x + y }; end
484
+ if not method_defined? :sum
485
+ def sum; inject(0) { |x, y| x + y }; end
486
+ end
511
487
 
512
488
  def map_to_hash
513
489
  ret = {}
@@ -563,7 +539,9 @@ class Array
563
539
  inject([]) { |a, e| a + e }
564
540
  end
565
541
 
566
- def to_h; Hash[*flatten]; end
542
+ if not method_defined? :to_h
543
+ def to_h; Hash[*flatten_one_level]; end
544
+ end
567
545
  def rest; self[1..-1]; end
568
546
 
569
547
  def to_boolean_h; Hash[*map { |x| [x, true] }.flatten]; end
data/lib/sup/version.rb CHANGED
@@ -8,5 +8,5 @@ def git_suffix
8
8
  end
9
9
 
10
10
  module Redwood
11
- VERSION = "1.0"
11
+ VERSION = "1.2"
12
12
  end
data/lib/sup.rb CHANGED
@@ -26,7 +26,7 @@ class Module
26
26
  props = props.map { |p| p.to_s }
27
27
 
28
28
  path = name.gsub(/::/, "/")
29
- yaml_tag "!#{Redwood::YAML_DOMAIN},#{Redwood::YAML_DATE}/#{path}"
29
+ yaml_tag "tag:#{Redwood::YAML_DOMAIN},#{Redwood::YAML_DATE}/#{path}"
30
30
 
31
31
  define_method :init_with do |coder|
32
32
  initialize(*coder.map.values_at(*props))
@@ -38,9 +38,6 @@ class Module
38
38
  hash
39
39
  end
40
40
  end
41
-
42
- # Legacy
43
- Psych.load_tags["!#{Redwood::LEGACY_YAML_DOMAIN},#{Redwood::YAML_DATE}/#{path}"] = self
44
41
  end
45
42
  end
46
43
 
@@ -139,10 +136,17 @@ module Redwood
139
136
 
140
137
  def load_yaml_obj fn, compress=false
141
138
  o = if File.exist? fn
142
- if compress
143
- Zlib::GzipReader.open(fn) { |f| YAML::load f }
139
+ raw_contents = if compress
140
+ Zlib::GzipReader.open(fn) { |f| f.read }
141
+ else
142
+ File::open(fn) { |f| f.read }
143
+ end
144
+ ## fix up malformed tag URIs created by earlier versions of sup
145
+ raw_contents.gsub!(/!supmua.org,2006-10-01\/(\S*)$/) { |m| "!<tag:supmua.org,2006-10-01/#{$1}>" }
146
+ if YAML.respond_to?(:unsafe_load) # Ruby 3.1+
147
+ YAML::unsafe_load raw_contents
144
148
  else
145
- YAML::load_file fn
149
+ YAML::load raw_contents
146
150
  end
147
151
  end
148
152
  if o.is_a?(Array)
@@ -349,7 +353,7 @@ EOM
349
353
  name ||= ENV["USER"]
350
354
  email = ENV["USER"] + "@" +
351
355
  begin
352
- Socket.gethostbyname(Socket.gethostname).first
356
+ Addrinfo.getaddrinfo(Socket.gethostname, 'smtp').first.getnameinfo.first
353
357
  rescue SocketError
354
358
  Socket.gethostname
355
359
  end