+require 'tempfile'
+
## Here we define all the "chunks" that a message is parsed
## into. Chunks are used by ThreadViewMode to render a message. Chunks
## are used for both MIME stuff like attachments, for Sup's parsing of
## :open if they want to start expanded (default is to start collapsed).
##
## If it's not expandable but is viewable, a patina is displayed using
-###patina_color and #patina_text, but no toggling is allowed. Instead,
-##if #view! is defined, pressing enter on the widget calls view! and
-##(if that returns false) #to_s. Otherwise, enter does nothing. This
-##is how non-inlineable attachments work.
+## #patina_color and #patina_text, but no toggling is allowed. Instead,
+## if #view! is defined, pressing enter on the widget calls view! and
+## (if that returns false) #to_s. Otherwise, enter does nothing. This
+## is how non-inlineable attachments work.
+##
+## Independent of all that, a chunk can be quotable, in which case it's
+## included as quoted text during a reply. Text, Quotes, and mime-parsed
+## attachments are quotable; Signatures are not.
+
+## monkey-patch time: make temp files have the right extension
+class Tempfile
+ def make_tmpname basename, n
+ sprintf '%d-%d-%s', $$, n, basename
+ end
+end
+
module Redwood
module Chunk
+ WRAP_LEN = 80 # wrap messages and text attachments at this width
+
class Attachment
HookManager.register "mime-decode", <<EOS
-Executes when decoding a MIME attachment.
+Decodes a MIME attachment into text form. The text will be displayed
+directly in Sup. For attachments that you wish to use a separate program
+to view (e.g. images), you should use the mime-view hook instead.
+
Variables:
content_type: the content-type of the message
- filename: the filename of the attachment as saved to disk (generated
- on the fly, so don't call more than once)
+ filename: the filename of the attachment as saved to disk
sibling_types: if this attachment is part of a multipart MIME attachment,
an array of content-types for all attachments. Otherwise,
the empty array.
Return value:
The decoded text of the attachment, or nil if not decoded.
EOS
+
+ HookManager.register "mime-view", <<EOS
+Views a non-text MIME attachment. This hook allows you to run
+third-party programs for attachments that require such a thing (e.g.
+images). To instead display a text version of the attachment directly in
+Sup, use the mime-decode hook instead.
+
+Note that by default (at least on systems that have a run-mailcap command),
+Sup uses the default mailcap handler for the attachment's MIME type. If
+you want a particular behavior to be global, you may wish to change your
+mailcap instead.
+
+Variables:
+ content_type: the content-type of the attachment
+ filename: the filename of the attachment as saved to disk
+Return value:
+ True if the viewing was successful, false otherwise. If false, calling
+ /usr/bin/run-mailcap will be tried.
+EOS
#' stupid ruby-mode
## raw_content is the post-MIME-decode content. this is used for
## saving the attachment to disk.
attr_reader :content_type, :filename, :lines, :raw_content
+ bool_reader :quotable
def initialize content_type, filename, encoded_content, sibling_types
@content_type = content_type
@filename = filename
- @raw_content = encoded_content.decode
+ @quotable = false # changed to true if we can parse it through the
+ # mime-decode hook, or if it's plain text
+ @raw_content =
+ if encoded_content.body
+ encoded_content.decode
+ else
+ "For some bizarre reason, RubyMail was unable to parse this attachment.\n"
+ end
- @lines =
+ text =
case @content_type
when /^text\/plain\b/
- Message.convert_from(@raw_content, encoded_content.charset).split("\n")
+ Iconv.easy_decode $encoding, encoded_content.charset || $encoding, @raw_content
else
- text = HookManager.run "mime-decode", :content_type => content_type,
- :filename => lambda { write_to_disk },
- :sibling_types => sibling_types
- text.split("\n") if text
+ HookManager.run "mime-decode", :content_type => content_type,
+ :filename => lambda { write_to_disk },
+ :sibling_types => sibling_types
end
+
+ @lines = nil
+ if text
+ @lines = text.gsub("\r\n", "\n").gsub(/\t/, " ").gsub(/\r/, "").split("\n")
+ @lines = lines.map {|l| l.chomp.wrap WRAP_LEN}.flatten
+ @quotable = true
+ end
end
def color; :none end
if expandable?
"Attachment: #{filename} (#{lines.length} lines)"
else
- "Attachment: #{filename} (#{content_type})"
+ "Attachment: #{filename} (#{content_type}; #{@raw_content.size.to_human_size})"
end
end
def expandable?; !viewable? end
def initial_state; :open end
def viewable?; @lines.nil? end
+ def view_default! path
+ cmd = "/usr/bin/run-mailcap --action=view '#{@content_type}:#{path}'"
+ debug "running: #{cmd.inspect}"
+ BufferManager.shell_out(cmd)
+ $? == 0
+ end
+
def view!
path = write_to_disk
- system "/usr/bin/run-mailcap --action=view #{@content_type}:#{path} >& /dev/null"
- $? == 0
+ ret = HookManager.run "mime-view", :content_type => @content_type,
+ :filename => path
+ ret || view_default!(path)
end
def write_to_disk
- file = Tempfile.new "redwood.attachment"
+ file = Tempfile.new(@filename || "sup-attachment")
file.print @raw_content
file.close
file.path
end
class Text
- WRAP_LEN = 80 # wrap at this width
attr_reader :lines
def initialize lines
@lines = lines.map { |l| l.chomp.wrap WRAP_LEN }.flatten # wrap
## trim off all empty lines except one
- lines.pop while lines.last =~ /^\s*$/
+ @lines.pop while @lines.length > 1 && @lines[-1] =~ /^\s*$/ && @lines[-2] =~ /^\s*$/
end
def inlineable?; true end
+ def quotable?; true end
def expandable?; false end
def viewable?; false end
def color; :none end
end
def inlineable?; @lines.length == 1 end
+ def quotable?; true end
def expandable?; !inlineable? end
def viewable?; false end
end
def inlineable?; @lines.length == 1 end
+ def quotable?; false end
def expandable?; !inlineable? end
def viewable?; false end
end
def inlineable?; false end
+ def quotable?; false end
def expandable?; true end
- def initial_state; :open end
+ def initial_state; :closed end
def viewable?; false end
def patina_color; :generic_notice_patina_color end
def patina_color
case status
- when :valid: :cryptosig_valid_color
- when :invalid: :cryptosig_invalid_color
+ when :valid then :cryptosig_valid_color
+ when :invalid then :cryptosig_invalid_color
else :cryptosig_unknown_color
end
end
def color; patina_color end
def inlineable?; false end
+ def quotable?; false end
def expandable?; !@lines.empty? end
def viewable?; false end
end