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