sup 0.7 → 0.8
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sup might be problematic. Click here for more details.
- data/CONTRIBUTORS +8 -3
- data/History.txt +19 -0
- data/README.txt +45 -44
- data/ReleaseNotes +6 -0
- data/bin/sup +36 -5
- data/bin/sup-add +0 -0
- data/bin/sup-config +0 -0
- data/bin/sup-dump +0 -0
- data/bin/sup-recover-sources +8 -12
- data/bin/sup-sync +22 -16
- data/bin/sup-sync-back +1 -1
- data/bin/sup-tweak-labels +8 -8
- data/lib/sup.rb +3 -17
- data/lib/sup/account.rb +2 -3
- data/lib/sup/buffer.rb +21 -10
- data/lib/sup/colormap.rb +30 -27
- data/lib/sup/contact.rb +1 -1
- data/lib/sup/draft.rb +1 -3
- data/lib/sup/imap.rb +1 -1
- data/lib/sup/index.rb +70 -48
- data/lib/sup/label.rb +12 -10
- data/lib/sup/logger.rb +1 -1
- data/lib/sup/maildir.rb +1 -1
- data/lib/sup/mbox.rb +13 -70
- data/lib/sup/mbox/loader.rb +26 -15
- data/lib/sup/message-chunks.rb +18 -6
- data/lib/sup/message.rb +56 -67
- data/lib/sup/mode.rb +2 -1
- data/lib/sup/modes/buffer-list-mode.rb +6 -2
- data/lib/sup/modes/compose-mode.rb +0 -1
- data/lib/sup/modes/contact-list-mode.rb +1 -1
- data/lib/sup/modes/edit-message-mode.rb +37 -9
- data/lib/sup/modes/inbox-mode.rb +34 -0
- data/lib/sup/modes/label-list-mode.rb +10 -3
- data/lib/sup/modes/reply-mode.rb +24 -13
- data/lib/sup/modes/resume-mode.rb +2 -0
- data/lib/sup/modes/scroll-mode.rb +10 -9
- data/lib/sup/modes/search-results-mode.rb +2 -2
- data/lib/sup/modes/thread-index-mode.rb +157 -38
- data/lib/sup/modes/thread-view-mode.rb +27 -11
- data/lib/sup/person.rb +22 -73
- data/lib/sup/poll.rb +18 -20
- data/lib/sup/source.rb +44 -0
- data/lib/sup/undo.rb +39 -0
- data/lib/sup/util.rb +25 -16
- metadata +46 -45
@@ -27,6 +27,7 @@ EOS
|
|
27
27
|
register_keymap do |k|
|
28
28
|
k.add :toggle_detailed_header, "Toggle detailed header", 'h'
|
29
29
|
k.add :show_header, "Show full message header", 'H'
|
30
|
+
k.add :show_message, "Show full message (raw form)", 'V'
|
30
31
|
k.add :activate_chunk, "Expand/collapse or activate item", :enter
|
31
32
|
k.add :expand_all_messages, "Expand/collapse all messages", 'E'
|
32
33
|
k.add :edit_draft, "Edit draft", 'e'
|
@@ -134,6 +135,13 @@ EOS
|
|
134
135
|
end
|
135
136
|
end
|
136
137
|
|
138
|
+
def show_message
|
139
|
+
m = @message_lines[curpos] or return
|
140
|
+
BufferManager.spawn_unless_exists("Raw message for #{m.id}") do
|
141
|
+
TextMode.new m.raw_message
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
137
145
|
def toggle_detailed_header
|
138
146
|
m = @message_lines[curpos] or return
|
139
147
|
@layout[m].state = (@layout[m].state == :detailed ? :open : :detailed)
|
@@ -149,7 +157,7 @@ EOS
|
|
149
157
|
def subscribe_to_list
|
150
158
|
m = @message_lines[curpos] or return
|
151
159
|
if m.list_subscribe && m.list_subscribe =~ /<mailto:(.*?)\?(subject=(.*?))>/
|
152
|
-
ComposeMode.spawn_nicely :from => AccountManager.account_for(m.recipient_email), :to => [
|
160
|
+
ComposeMode.spawn_nicely :from => AccountManager.account_for(m.recipient_email), :to => [Person.from_address($1)], :subj => $3
|
153
161
|
else
|
154
162
|
BufferManager.flash "Can't find List-Subscribe header for this message."
|
155
163
|
end
|
@@ -158,7 +166,7 @@ EOS
|
|
158
166
|
def unsubscribe_from_list
|
159
167
|
m = @message_lines[curpos] or return
|
160
168
|
if m.list_unsubscribe && m.list_unsubscribe =~ /<mailto:(.*?)\?(subject=(.*?))>/
|
161
|
-
ComposeMode.spawn_nicely :from => AccountManager.account_for(m.recipient_email), :to => [
|
169
|
+
ComposeMode.spawn_nicely :from => AccountManager.account_for(m.recipient_email), :to => [Person.from_address($1)], :subj => $3
|
162
170
|
else
|
163
171
|
BufferManager.flash "Can't find List-Unsubscribe header for this message."
|
164
172
|
end
|
@@ -234,12 +242,16 @@ EOS
|
|
234
242
|
## view.
|
235
243
|
def activate_chunk
|
236
244
|
chunk = @chunk_lines[curpos] or return
|
237
|
-
|
238
|
-
if
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
245
|
+
if chunk.is_a? Chunk::Text
|
246
|
+
## if the cursor is over a text region, expand/collapse the
|
247
|
+
## entire message
|
248
|
+
chunk = @message_lines[curpos]
|
249
|
+
end
|
250
|
+
layout = if chunk.is_a?(Message)
|
251
|
+
@layout[chunk]
|
252
|
+
elsif chunk.expandable?
|
253
|
+
@chunk_layout[chunk]
|
254
|
+
end
|
243
255
|
if layout
|
244
256
|
layout.state = (layout.state != :closed ? :closed : :open)
|
245
257
|
#cursor_down if layout.state == :closed # too annoying
|
@@ -247,6 +259,10 @@ EOS
|
|
247
259
|
elsif chunk.viewable?
|
248
260
|
view chunk
|
249
261
|
end
|
262
|
+
if chunk.is_a?(Message)
|
263
|
+
jump_to_message chunk
|
264
|
+
jump_to_next_open if layout.state == :closed
|
265
|
+
end
|
250
266
|
end
|
251
267
|
|
252
268
|
def edit_as_new
|
@@ -540,7 +556,7 @@ private
|
|
540
556
|
(0 ... text.length).each do |i|
|
541
557
|
@chunk_lines[@text.length + i] = m
|
542
558
|
@message_lines[@text.length + i] = m
|
543
|
-
lw = text[i].flatten.select { |x| x.is_a? String }.map { |x| x.
|
559
|
+
lw = text[i].flatten.select { |x| x.is_a? String }.map { |x| x.display_length }.sum
|
544
560
|
end
|
545
561
|
|
546
562
|
@text += text
|
@@ -561,7 +577,7 @@ private
|
|
561
577
|
(0 ... text.length).each do |i|
|
562
578
|
@chunk_lines[@text.length + i] = c
|
563
579
|
@message_lines[@text.length + i] = m
|
564
|
-
lw = text[i].flatten.select { |x| x.is_a? String }.map { |x| x.
|
580
|
+
lw = text[i].flatten.select { |x| x.is_a? String }.map { |x| x.display_length }.sum - (depth * INDENT_SPACES)
|
565
581
|
l.width = lw if lw > l.width
|
566
582
|
end
|
567
583
|
@text += text
|
@@ -635,7 +651,7 @@ private
|
|
635
651
|
|
636
652
|
def format_person_list prefix, people
|
637
653
|
ptext = people.map { |p| format_person p }
|
638
|
-
pad = " " * prefix.
|
654
|
+
pad = " " * prefix.display_length
|
639
655
|
[prefix + ptext.first + (ptext.length > 1 ? "," : "")] +
|
640
656
|
ptext[1 .. -1].map_with_index do |e, i|
|
641
657
|
pad + e + (i == ptext.length - 1 ? "" : ",")
|
data/lib/sup/person.rb
CHANGED
@@ -1,62 +1,9 @@
|
|
1
1
|
module Redwood
|
2
2
|
|
3
|
-
class PersonManager
|
4
|
-
include Singleton
|
5
|
-
|
6
|
-
def initialize fn
|
7
|
-
@fn = fn
|
8
|
-
@@people = {}
|
9
|
-
|
10
|
-
## read in stored people
|
11
|
-
IO.readlines(fn).map do |l|
|
12
|
-
l =~ /^(.*)?:\s+(\d+)\s+(.*)$/ or next
|
13
|
-
email, time, name = $1, $2, $3
|
14
|
-
@@people[email] = Person.new name, email, time, false
|
15
|
-
end if File.exists? fn
|
16
|
-
|
17
|
-
self.class.i_am_the_instance self
|
18
|
-
end
|
19
|
-
|
20
|
-
def save
|
21
|
-
File.open(@fn, "w") do |f|
|
22
|
-
@@people.each do |email, p|
|
23
|
-
next if p.email == p.name
|
24
|
-
next if p.name =~ /=/ # drop rfc2047-encoded, and lots of other useless emails. definitely a heuristic.
|
25
|
-
f.puts "#{p.email}: #{p.timestamp} #{p.name}"
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def self.people_for s, opts={}
|
31
|
-
return [] if s.nil?
|
32
|
-
s.split_on_commas.map { |ss| self.person_for ss, opts }
|
33
|
-
end
|
34
|
-
|
35
|
-
def self.person_for s, opts={}
|
36
|
-
p = Person.from_address(s) or return nil
|
37
|
-
p.definitive = true if opts[:definitive]
|
38
|
-
register p
|
39
|
-
end
|
40
|
-
|
41
|
-
def self.register p
|
42
|
-
oldp = @@people[p.email]
|
43
|
-
|
44
|
-
if oldp.nil? || p.better_than?(oldp)
|
45
|
-
@@people[p.email] = p
|
46
|
-
end
|
47
|
-
|
48
|
-
@@people[p.email].touch!
|
49
|
-
@@people[p.email]
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
## don't create these by hand. rather, go through personmanager, to
|
54
|
-
## ensure uniqueness and overriding.
|
55
3
|
class Person
|
56
|
-
attr_accessor :name, :email
|
57
|
-
bool_accessor :definitive
|
4
|
+
attr_accessor :name, :email
|
58
5
|
|
59
|
-
def initialize name, email
|
6
|
+
def initialize name, email
|
60
7
|
raise ArgumentError, "email can't be nil" unless email
|
61
8
|
|
62
9
|
if name
|
@@ -67,26 +14,10 @@ class Person
|
|
67
14
|
end
|
68
15
|
|
69
16
|
@email = email.gsub(/^\s+|\s+$/, "").gsub(/\s+/, " ").downcase
|
70
|
-
@definitive = definitive
|
71
|
-
@timestamp = timestamp
|
72
|
-
end
|
73
|
-
|
74
|
-
## heuristic: whether the name attached to this email is "real", i.e.
|
75
|
-
## we should bother to store it.
|
76
|
-
def generic?
|
77
|
-
@email =~ /no\-?reply/
|
78
|
-
end
|
79
|
-
|
80
|
-
def better_than? o
|
81
|
-
return false if o.definitive? || generic?
|
82
|
-
return true if definitive?
|
83
|
-
o.name.nil? || (name && name.length > o.name.length && name =~ /[a-z]/)
|
84
17
|
end
|
85
18
|
|
86
19
|
def to_s; "#@name <#@email>" end
|
87
20
|
|
88
|
-
def touch!; @timestamp = Time.now.to_i end
|
89
|
-
|
90
21
|
# def == o; o && o.email == email; end
|
91
22
|
# alias :eql? :==
|
92
23
|
# def hash; [name, email].hash; end
|
@@ -146,8 +77,20 @@ class Person
|
|
146
77
|
return nil if s.nil?
|
147
78
|
|
148
79
|
## try and parse an email address and name
|
149
|
-
name, email =
|
150
|
-
|
80
|
+
name, email = case s
|
81
|
+
when /(.+?) ((\S+?)@\S+) \3/
|
82
|
+
## ok, this first match cause is insane, but bear with me. email
|
83
|
+
## addresses are stored in the to/from/etc fields of the index in a
|
84
|
+
## weird format: "name address first-part-of-address", i.e. spaces
|
85
|
+
## separating those three bits, and no <>'s. this is the output of
|
86
|
+
## #indexable_content. here, we reverse-engineer that format to extract
|
87
|
+
## a valid address.
|
88
|
+
##
|
89
|
+
## we store things this way to allow searches on a to/from/etc field to
|
90
|
+
## match any of those parts. a more robust solution would be to store a
|
91
|
+
## separate, non-indexed field with the proper headers. but this way we
|
92
|
+
## save precious bits, and it's backwards-compatible with older indexes.
|
93
|
+
[$1, $2]
|
151
94
|
when /["'](.*?)["'] <(.*?)>/, /([^,]+) <(.*?)>/
|
152
95
|
a, b = $1, $2
|
153
96
|
[a.gsub('\"', '"'), b]
|
@@ -162,6 +105,12 @@ class Person
|
|
162
105
|
Person.new name, email
|
163
106
|
end
|
164
107
|
|
108
|
+
def self.from_address_list ss
|
109
|
+
return [] if ss.nil?
|
110
|
+
ss.split_on_commas.map { |s| self.from_address s }
|
111
|
+
end
|
112
|
+
|
113
|
+
## see comments in self.from_address
|
165
114
|
def indexable_content
|
166
115
|
[name, email, email.split(/@/).first].join(" ")
|
167
116
|
end
|
data/lib/sup/poll.rb
CHANGED
@@ -40,7 +40,7 @@ EOS
|
|
40
40
|
end
|
41
41
|
|
42
42
|
def buffer
|
43
|
-
b, new = BufferManager.spawn_unless_exists("
|
43
|
+
b, new = BufferManager.spawn_unless_exists("poll for new messages", :hidden => true, :system => true) { PollMode.new }
|
44
44
|
b
|
45
45
|
end
|
46
46
|
|
@@ -86,7 +86,7 @@ EOS
|
|
86
86
|
Index.usual_sources.each do |source|
|
87
87
|
# yield "source #{source} is done? #{source.done?} (cur_offset #{source.cur_offset} >= #{source.end_offset})"
|
88
88
|
begin
|
89
|
-
yield "Loading from #{source}... " unless source.done? || source.has_errors?
|
89
|
+
yield "Loading from #{source}... " unless source.done? || (source.respond_to?(:has_errors?) && source.has_errors?)
|
90
90
|
rescue SourceError => e
|
91
91
|
Redwood::log "problem getting messages from #{source}: #{e.message}"
|
92
92
|
Redwood::report_broken_sources :force_to_top => true
|
@@ -97,13 +97,13 @@ EOS
|
|
97
97
|
numi = 0
|
98
98
|
add_messages_from source do |m, offset, entry|
|
99
99
|
## always preserve the labels on disk.
|
100
|
-
m.labels = entry[:label].
|
100
|
+
m.labels = ((m.labels - [:unread, :inbox]) + entry[:label].symbolistize).uniq if entry
|
101
101
|
yield "Found message at #{offset} with labels {#{m.labels * ', '}}"
|
102
102
|
unless entry
|
103
103
|
num += 1
|
104
|
-
from_and_subj << [m.from.longname, m.subj]
|
104
|
+
from_and_subj << [m.from && m.from.longname, m.subj]
|
105
105
|
if m.has_label?(:inbox) && ([:spam, :deleted, :killed] & m.labels).empty?
|
106
|
-
from_and_subj_inbox << [m.from.longname, m.subj]
|
106
|
+
from_and_subj_inbox << [m.from && m.from.longname, m.subj]
|
107
107
|
numi += 1
|
108
108
|
end
|
109
109
|
end
|
@@ -137,31 +137,29 @@ EOS
|
|
137
137
|
def add_messages_from source, opts={}
|
138
138
|
begin
|
139
139
|
return if source.done? || source.has_errors?
|
140
|
-
|
140
|
+
|
141
141
|
source.each do |offset, labels|
|
142
142
|
if source.has_errors?
|
143
143
|
Redwood::log "error loading messages from #{source}: #{source.error.message}"
|
144
144
|
return
|
145
145
|
end
|
146
|
-
|
146
|
+
|
147
147
|
labels.each { |l| LabelManager << l }
|
148
148
|
labels = labels + (source.archived? ? [] : [:inbox])
|
149
149
|
|
150
|
-
|
151
|
-
|
152
|
-
if m.source_marked_read?
|
153
|
-
m.remove_label :unread
|
154
|
-
labels.delete :unread
|
155
|
-
end
|
150
|
+
m = Message.new :source => source, :source_info => offset, :labels => labels
|
151
|
+
m.load_from_source!
|
156
152
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
Index.sync_message m, docid, entry, opts
|
161
|
-
UpdateManager.relay self, :added, m unless entry
|
162
|
-
rescue MessageFormatError => e
|
163
|
-
Redwood::log "ignoring erroneous message at #{source}##{offset}: #{e.message}"
|
153
|
+
if m.source_marked_read?
|
154
|
+
m.remove_label :unread
|
155
|
+
labels.delete :unread
|
164
156
|
end
|
157
|
+
|
158
|
+
docid, entry = Index.load_entry_for_id m.id
|
159
|
+
HookManager.run "before-add-message", :message => m
|
160
|
+
m = yield(m, offset, entry) or next if block_given?
|
161
|
+
times = Index.sync_message m, false, docid, entry, opts
|
162
|
+
UpdateManager.relay self, :added, m unless entry
|
165
163
|
end
|
166
164
|
rescue SourceError => e
|
167
165
|
Redwood::log "problem getting messages from #{source}: #{e.message}"
|
data/lib/sup/source.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require "sup/rfc2047"
|
2
|
+
|
1
3
|
module Redwood
|
2
4
|
|
3
5
|
class SourceError < StandardError
|
@@ -99,7 +101,49 @@ class Source
|
|
99
101
|
end
|
100
102
|
end
|
101
103
|
|
104
|
+
## read a raw email header from a filehandle (or anything that responds to
|
105
|
+
## #gets), and turn it into a hash of key-value pairs.
|
106
|
+
##
|
107
|
+
## WARNING! THIS IS A SPEED-CRITICAL SECTION. Everything you do here will have
|
108
|
+
## a significant effect on Sup's processing speed of email from ALL sources.
|
109
|
+
## Little things like string interpolation, regexp interpolation, += vs <<,
|
110
|
+
## all have DRAMATIC effects. BE CAREFUL WHAT YOU DO!
|
111
|
+
def self.parse_raw_email_header f
|
112
|
+
header = {}
|
113
|
+
last = nil
|
114
|
+
|
115
|
+
while(line = f.gets)
|
116
|
+
case line
|
117
|
+
## these three can occur multiple times, and we want the first one
|
118
|
+
when /^(Delivered-To|X-Original-To|Envelope-To):\s*(.*?)\s*$/i; header[last = $1.downcase] ||= $2
|
119
|
+
## mark this guy specially. not sure why i care.
|
120
|
+
when /^([^:\s]+):\s*(.*?)\s*$/i; header[last = $1.downcase] = $2
|
121
|
+
when /^\r*$/; break
|
122
|
+
else
|
123
|
+
if last
|
124
|
+
header[last] << " " unless header[last].empty?
|
125
|
+
header[last] << line.strip
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
%w(subject from to cc bcc).each do |k|
|
131
|
+
v = header[k] or next
|
132
|
+
next unless Rfc2047.is_encoded? v
|
133
|
+
header[k] = begin
|
134
|
+
Rfc2047.decode_to $encoding, v
|
135
|
+
rescue Errno::EINVAL, Iconv::InvalidEncoding, Iconv::IllegalSequence => e
|
136
|
+
#Redwood::log "warning: error decoding RFC 2047 header (#{e.class.name}): #{e.message}"
|
137
|
+
v
|
138
|
+
end
|
139
|
+
end
|
140
|
+
header
|
141
|
+
end
|
142
|
+
|
102
143
|
protected
|
144
|
+
|
145
|
+
## convenience function
|
146
|
+
def parse_raw_email_header f; self.class.parse_raw_email_header f end
|
103
147
|
|
104
148
|
def Source.expand_filesystem_uri uri
|
105
149
|
uri.gsub "~", File.expand_path("~")
|
data/lib/sup/undo.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
module Redwood
|
2
|
+
|
3
|
+
## Implements a single undo list for the Sup instance
|
4
|
+
##
|
5
|
+
## The basic idea is to keep a list of lambdas to undo
|
6
|
+
## things. When an action is called (such as 'archive'),
|
7
|
+
## a lambda is registered with UndoManager that will
|
8
|
+
## undo the archival action
|
9
|
+
|
10
|
+
class UndoManager
|
11
|
+
include Singleton
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@@actionlist = []
|
15
|
+
self.class.i_am_the_instance self
|
16
|
+
end
|
17
|
+
|
18
|
+
def register desc, *actions, &b
|
19
|
+
actions = [*actions.flatten]
|
20
|
+
actions << b if b
|
21
|
+
raise ArgumentError, "need at least one action" unless actions.length > 0
|
22
|
+
@@actionlist.push :desc => desc, :actions => actions
|
23
|
+
end
|
24
|
+
|
25
|
+
def undo
|
26
|
+
unless @@actionlist.empty?
|
27
|
+
actionset = @@actionlist.pop
|
28
|
+
actionset[:actions].each { |action| action.call }
|
29
|
+
BufferManager.flash "undid #{actionset[:desc]}"
|
30
|
+
else
|
31
|
+
BufferManager.flash "nothing more to undo!"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def clear
|
36
|
+
@@actionlist = []
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/sup/util.rb
CHANGED
@@ -172,6 +172,8 @@ class Object
|
|
172
172
|
end
|
173
173
|
|
174
174
|
class String
|
175
|
+
def display_length; scan(/./u).size end
|
176
|
+
|
175
177
|
def camel_to_hyphy
|
176
178
|
self.gsub(/([a-z])([A-Z0-9])/, '\1-\2').downcase
|
177
179
|
end
|
@@ -188,11 +190,6 @@ class String
|
|
188
190
|
ret
|
189
191
|
end
|
190
192
|
|
191
|
-
## one of the few things i miss from perl
|
192
|
-
def ucfirst
|
193
|
-
self[0 .. 0].upcase + self[1 .. -1]
|
194
|
-
end
|
195
|
-
|
196
193
|
## a very complicated regex found on teh internets to split on
|
197
194
|
## commas, unless they occurr within double quotes.
|
198
195
|
def split_on_commas
|
@@ -276,6 +273,11 @@ class String
|
|
276
273
|
def normalize_whitespace
|
277
274
|
gsub(/\t/, " ").gsub(/\r/, "")
|
278
275
|
end
|
276
|
+
|
277
|
+
## takes a space-separated list of words, and returns an array of symbols.
|
278
|
+
## typically used in Sup for translating Ferret's representation of a list
|
279
|
+
## of labels (a string) to an array of label symbols.
|
280
|
+
def symbolistize; split.map { |x| x.intern } end
|
279
281
|
end
|
280
282
|
|
281
283
|
class Numeric
|
@@ -403,6 +405,10 @@ class Array
|
|
403
405
|
|
404
406
|
def last= e; self[-1] = e end
|
405
407
|
def nonempty?; !empty? end
|
408
|
+
|
409
|
+
def to_set_of_symbols
|
410
|
+
map { |x| x.is_a?(Symbol) ? x : x.intern }.uniq
|
411
|
+
end
|
406
412
|
end
|
407
413
|
|
408
414
|
class Time
|
@@ -620,17 +626,20 @@ end
|
|
620
626
|
|
621
627
|
class Iconv
|
622
628
|
def self.easy_decode target, charset, text
|
623
|
-
return text if charset =~ /^(x-unknown|unknown[-_]?8bit|ascii[-_]?7[-_]?bit)$/i
|
629
|
+
return text if charset =~ /^(x-unknown|unknown[-_ ]?8bit|ascii[-_ ]?7[-_ ]?bit)$/i
|
624
630
|
charset = case charset
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
631
|
+
when /UTF[-_ ]?8/i: "utf-8"
|
632
|
+
when /(iso[-_ ])?latin[-_ ]?1$/i: "ISO-8859-1"
|
633
|
+
when /iso[-_ ]?8859[-_ ]?15/i: 'ISO-8859-15'
|
634
|
+
when /unicode[-_ ]1[-_ ]1[-_ ]utf[-_]7/i: "utf-7"
|
635
|
+
else charset
|
636
|
+
end
|
637
|
+
|
638
|
+
begin
|
639
|
+
Iconv.iconv(target + "//IGNORE", charset, text + " ").join[0 .. -2]
|
640
|
+
rescue Errno::EINVAL, Iconv::InvalidEncoding, Iconv::IllegalSequence => e
|
641
|
+
Redwood::log "warning: error (#{e.class.name}) decoding text from #{charset} to #{target}: #{text[0 ... 20]}"
|
642
|
+
text
|
643
|
+
end
|
635
644
|
end
|
636
645
|
end
|