]> git.cworth.org Git - sup/blob - lib/sup/message-chunks.rb
for message/rfc822 attachments, handle the case of no From: address (weird...)
[sup] / lib / sup / message-chunks.rb
1 ## Here we define all the "chunks" that a message is parsed
2 ## into. Chunks are used by ThreadViewMode to render a message. Chunks
3 ## are used for both MIME stuff like attachments, for Sup's parsing of
4 ## the message body into text, quote, and signature regions, and for
5 ## notices like "this message was decrypted" or "this message contains
6 ## a valid signature"---basically, anything we want to differentiate
7 ## at display time.
8 ##
9 ## A chunk can be inlineable, expandable, or viewable. If it's
10 ## inlineable, #color and #lines are called and the output is treated
11 ## as part of the message text. This is how Text and one-line Quotes
12 ## and Signatures work.
13 ##
14 ## If it's not inlineable but is expandable, #patina_color and
15 ## #patina_text are called to generate a "patina" (a one-line widget,
16 ## basically), and the user can press enter to toggle the display of
17 ## the chunk content, which is generated from #color and #lines as
18 ## above. This is how Quote, Signature, and most widgets
19 ## work. Exandable chunks can additionally define #initial_state to be
20 ## :open if they want to start expanded (default is to start collapsed).
21 ##
22 ## If it's not expandable but is viewable, a patina is displayed using
23 ###patina_color and #patina_text, but no toggling is allowed. Instead,
24 ##if #view! is defined, pressing enter on the widget calls view! and
25 ##(if that returns false) #to_s. Otherwise, enter does nothing. This
26 ##is how non-inlineable attachments work.
27
28 module Redwood
29 module Chunk
30   class Attachment
31     HookManager.register "mime-decode", <<EOS
32 Executes when decoding a MIME attachment.
33 Variables:
34    content_type: the content-type of the message
35        filename: the filename of the attachment as saved to disk (generated
36                  on the fly, so don't call more than once)
37   sibling_types: if this attachment is part of a multipart MIME attachment,
38                  an array of content-types for all attachments. Otherwise,
39                  the empty array.
40 Return value:
41   The decoded text of the attachment, or nil if not decoded.
42 EOS
43 #' stupid ruby-mode
44
45     ## raw_content is the post-MIME-decode content. this is used for
46     ## saving the attachment to disk.
47     attr_reader :content_type, :filename, :lines, :raw_content
48
49     def initialize content_type, filename, encoded_content, sibling_types
50       @content_type = content_type
51       @filename = filename
52       @raw_content = encoded_content.decode
53
54       @lines =
55         case @content_type
56         when /^text\/plain\b/
57           Message.convert_from(@raw_content, encoded_content.charset).split("\n")
58         else
59           text = HookManager.run "mime-decode", :content_type => content_type,
60                                  :filename => lambda { write_to_disk },
61                                  :sibling_types => sibling_types
62           text.split("\n") if text
63         end
64     end
65
66     def color; :none end
67     def patina_color; :attachment_color end
68     def patina_text
69       if expandable?
70         "Attachment: #{filename} (#{lines.length} lines)"
71       else
72         "Attachment: #{filename} (#{content_type})"
73       end
74     end
75
76     ## an attachment is exapndable if we've managed to decode it into
77     ## something we can display inline. otherwise, it's viewable.
78     def inlineable?; false end
79     def expandable?; !viewable? end
80     def initial_state; :open end
81     def viewable?; @lines.nil? end
82     def view!
83       path = write_to_disk
84       system "/usr/bin/run-mailcap --action=view #{@content_type}:#{path} >& /dev/null"
85       $? == 0
86     end
87
88     def write_to_disk
89       file = Tempfile.new "redwood.attachment"
90       file.print @raw_content
91       file.close
92       file.path
93     end
94
95     ## used when viewing the attachment as text
96     def to_s
97       @lines || @raw_content
98     end
99   end
100
101   class Text
102     WRAP_LEN = 80 # wrap at this width
103
104     attr_reader :lines
105     def initialize lines
106       @lines = lines.map { |l| l.chomp.wrap WRAP_LEN }.flatten # wrap
107
108       ## trim off all empty lines except one
109       lines.pop while lines.last =~ /^\s*$/ 
110     end
111
112     def inlineable?; true end
113     def expandable?; false end
114     def viewable?; false end
115     def color; :none end
116   end
117
118   class Quote
119     attr_reader :lines
120     def initialize lines
121       @lines = lines
122     end
123     
124     def inlineable?; @lines.length == 1 end
125     def expandable?; !inlineable? end
126     def viewable?; false end
127
128     def patina_color; :quote_patina_color end
129     def patina_text; "(#{lines.length} quoted lines)" end
130     def color; :quote_color end
131   end
132
133   class Signature
134     attr_reader :lines
135     def initialize lines
136       @lines = lines
137     end
138
139     def inlineable?; @lines.length == 1 end
140     def expandable?; !inlineable? end
141     def viewable?; false end
142
143     def patina_color; :sig_patina_color end
144     def patina_text; "(#{lines.length}-line signature)" end
145     def color; :sig_color end
146   end
147
148   class EnclosedMessage
149     attr_reader :lines
150     def initialize from, body
151       @from = from
152       @lines = body.split "\n"
153     end
154
155     def from
156       @from ? @from.longname : "unknown sender"
157     end
158
159     def inlineable?; false end
160     def expandable?; true end
161     def initial_state; :open end
162     def viewable?; false end
163
164     def patina_color; :generic_notice_patina_color end
165     def patina_text; "Begin enclosed message from #{from} (#{@lines.length} lines)" end
166
167     def color; :quote_color end
168   end
169
170   class CryptoNotice
171     attr_reader :lines, :status, :patina_text
172
173     def initialize status, description, lines=[]
174       @status = status
175       @patina_text = description
176       @lines = lines
177     end
178
179     def patina_color
180       case status
181       when :valid: :cryptosig_valid_color
182       when :invalid: :cryptosig_invalid_color
183       else :cryptosig_unknown_color
184       end
185     end
186     def color; patina_color end
187
188     def inlineable?; false end
189     def expandable?; !@lines.empty? end
190     def viewable?; false end
191   end
192 end
193 end