sup 1.0 → 1.2

Sign up to get free protection for your applications and to get access to all the features.
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