sup 0.1 → 0.2
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/History.txt +9 -0
- data/Manifest.txt +5 -1
- data/Rakefile +2 -1
- data/bin/sup +27 -10
- data/bin/sup-add +2 -1
- data/bin/sup-sync-back +51 -23
- data/doc/FAQ.txt +29 -37
- data/doc/Hooks.txt +38 -0
- data/doc/{UserGuide.txt → NewUserGuide.txt} +27 -21
- data/doc/TODO +91 -57
- data/lib/sup.rb +17 -1
- data/lib/sup/buffer.rb +80 -16
- data/lib/sup/colormap.rb +0 -2
- data/lib/sup/contact.rb +3 -2
- data/lib/sup/crypto.rb +110 -0
- data/lib/sup/draft.rb +2 -6
- data/lib/sup/hook.rb +131 -0
- data/lib/sup/imap.rb +27 -16
- data/lib/sup/index.rb +38 -14
- data/lib/sup/keymap.rb +0 -2
- data/lib/sup/label.rb +30 -9
- data/lib/sup/logger.rb +12 -1
- data/lib/sup/maildir.rb +48 -3
- data/lib/sup/mbox.rb +1 -1
- data/lib/sup/mbox/loader.rb +22 -12
- data/lib/sup/mbox/ssh-loader.rb +1 -1
- data/lib/sup/message-chunks.rb +198 -0
- data/lib/sup/message.rb +154 -115
- data/lib/sup/modes/compose-mode.rb +18 -0
- data/lib/sup/modes/contact-list-mode.rb +1 -1
- data/lib/sup/modes/edit-message-mode.rb +112 -31
- data/lib/sup/modes/file-browser-mode.rb +1 -1
- data/lib/sup/modes/inbox-mode.rb +1 -1
- data/lib/sup/modes/label-list-mode.rb +8 -6
- data/lib/sup/modes/label-search-results-mode.rb +4 -1
- data/lib/sup/modes/log-mode.rb +1 -1
- data/lib/sup/modes/reply-mode.rb +18 -16
- data/lib/sup/modes/search-results-mode.rb +1 -1
- data/lib/sup/modes/thread-index-mode.rb +61 -33
- data/lib/sup/modes/thread-view-mode.rb +111 -102
- data/lib/sup/person.rb +5 -1
- data/lib/sup/poll.rb +36 -7
- data/lib/sup/sent.rb +1 -0
- data/lib/sup/source.rb +7 -3
- data/lib/sup/textfield.rb +48 -34
- data/lib/sup/thread.rb +9 -5
- data/lib/sup/util.rb +16 -22
- metadata +7 -3
data/lib/sup/keymap.rb
CHANGED
data/lib/sup/label.rb
CHANGED
@@ -5,12 +5,12 @@ class LabelManager
|
|
5
5
|
|
6
6
|
## labels that have special semantics. user will be unable to
|
7
7
|
## add/remove these via normal label mechanisms.
|
8
|
-
RESERVED_LABELS = [ :starred, :spam, :draft, :unread, :killed, :sent, :deleted ]
|
8
|
+
RESERVED_LABELS = [ :starred, :spam, :draft, :unread, :killed, :sent, :deleted, :inbox ]
|
9
9
|
|
10
10
|
## labels which it nonetheless makes sense to search for by
|
11
|
-
LISTABLE_RESERVED_LABELS = [ :starred, :spam, :draft, :sent, :killed, :deleted ]
|
11
|
+
LISTABLE_RESERVED_LABELS = [ :starred, :spam, :draft, :sent, :killed, :deleted, :inbox ]
|
12
12
|
|
13
|
-
## labels that will
|
13
|
+
## labels that will typically be hidden from the user
|
14
14
|
HIDDEN_RESERVED_LABELS = [ :starred, :unread ]
|
15
15
|
|
16
16
|
def initialize fn
|
@@ -28,17 +28,38 @@ class LabelManager
|
|
28
28
|
self.class.i_am_the_instance self
|
29
29
|
end
|
30
30
|
|
31
|
-
## all listable (user-defined
|
31
|
+
## all listable (just user-defined at the moment) labels, ordered
|
32
32
|
## nicely and converted to pretty strings. use #label_for to recover
|
33
33
|
## the original label.
|
34
|
-
def
|
35
|
-
|
36
|
-
|
34
|
+
def listable_labels
|
35
|
+
## uniq's only necessary here because of certain upgrade issues
|
36
|
+
(LISTABLE_RESERVED_LABELS + @labels.keys).uniq
|
37
|
+
end
|
38
|
+
|
39
|
+
## all apply-able (user-defined and system listable) labels, ordered
|
40
|
+
## nicely and converted to pretty strings. use #label_for to recover
|
41
|
+
## the original label.
|
42
|
+
def applyable_labels
|
43
|
+
@labels.keys
|
37
44
|
end
|
38
45
|
|
39
46
|
## reverse the label->string mapping, for convenience!
|
40
|
-
def
|
41
|
-
|
47
|
+
def string_for l
|
48
|
+
if RESERVED_LABELS.include? l
|
49
|
+
l.to_s.ucfirst
|
50
|
+
else
|
51
|
+
l.to_s
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def label_for s
|
56
|
+
l = s.intern
|
57
|
+
l2 = s.downcase.intern
|
58
|
+
if RESERVED_LABELS.include? l2
|
59
|
+
l2
|
60
|
+
else
|
61
|
+
l
|
62
|
+
end
|
42
63
|
end
|
43
64
|
|
44
65
|
def << t
|
data/lib/sup/logger.rb
CHANGED
@@ -25,7 +25,18 @@ class Logger
|
|
25
25
|
def log s
|
26
26
|
# $stderr.puts s
|
27
27
|
make_buf
|
28
|
-
|
28
|
+
prefix = "#{Time.now}: "
|
29
|
+
padding = " " * prefix.length
|
30
|
+
first = true
|
31
|
+
s.split(/[\r\n]/).each do |l|
|
32
|
+
l = l.chomp
|
33
|
+
if first
|
34
|
+
first = false
|
35
|
+
@mode << "#{prefix}#{l}\n"
|
36
|
+
else
|
37
|
+
@mode << "#{padding}#{l}\n"
|
38
|
+
end
|
39
|
+
end
|
29
40
|
$stderr.puts "[#{Time.now}] #{s.chomp}" unless BufferManager.instantiated? && @mode.buffer
|
30
41
|
end
|
31
42
|
|
data/lib/sup/maildir.rb
CHANGED
@@ -11,13 +11,15 @@ module Redwood
|
|
11
11
|
class Maildir < Source
|
12
12
|
SCAN_INTERVAL = 30 # seconds
|
13
13
|
|
14
|
+
## remind me never to use inheritance again.
|
14
15
|
yaml_properties :uri, :cur_offset, :usual, :archived, :id, :labels
|
15
16
|
def initialize uri, last_date=nil, usual=true, archived=false, id=nil, labels=[]
|
16
17
|
super uri, last_date, usual, archived, id
|
17
|
-
uri = URI(uri)
|
18
|
+
uri = URI(Source.expand_filesystem_uri(uri))
|
18
19
|
|
19
20
|
raise ArgumentError, "not a maildir URI" unless uri.scheme == "maildir"
|
20
21
|
raise ArgumentError, "maildir URI cannot have a host: #{uri.host}" if uri.host
|
22
|
+
raise ArgumentError, "mbox URI must have a path component" unless uri.path
|
21
23
|
|
22
24
|
@dir = uri.path
|
23
25
|
@labels = (labels || []).freeze
|
@@ -27,10 +29,14 @@ class Maildir < Source
|
|
27
29
|
@mutex = Mutex.new
|
28
30
|
end
|
29
31
|
|
32
|
+
def file_path; @dir end
|
30
33
|
def self.suggest_labels_for path; [] end
|
34
|
+
def is_source_for? uri; super || (URI(Source.expand_filesystem_uri(uri)) == URI(self.uri)); end
|
31
35
|
|
32
36
|
def check
|
33
37
|
scan_mailbox
|
38
|
+
return unless start_offset
|
39
|
+
|
34
40
|
start = @ids.index(cur_offset || start_offset) or raise OutOfSyncSourceError, "Unknown message id #{cur_offset || start_offset}." # couldn't find the most recent email
|
35
41
|
end
|
36
42
|
|
@@ -55,7 +61,7 @@ class Maildir < Source
|
|
55
61
|
ret
|
56
62
|
end
|
57
63
|
|
58
|
-
def
|
64
|
+
def raw_message id
|
59
65
|
scan_mailbox
|
60
66
|
with_file_for(id) { |f| f.readlines.join }
|
61
67
|
end
|
@@ -88,12 +94,14 @@ class Maildir < Source
|
|
88
94
|
|
89
95
|
def each
|
90
96
|
scan_mailbox
|
97
|
+
return unless start_offset
|
98
|
+
|
91
99
|
start = @ids.index(cur_offset || start_offset) or raise OutOfSyncSourceError, "Unknown message id #{cur_offset || start_offset}." # couldn't find the most recent email
|
92
100
|
|
93
101
|
start.upto(@ids.length - 1) do |i|
|
94
102
|
id = @ids[i]
|
95
103
|
self.cur_offset = id
|
96
|
-
yield id, @labels + (
|
104
|
+
yield id, @labels + (seen?(id) ? [] : [:unread]) + (trashed?(id) ? [:deleted] : []) + (flagged?(id) ? [:starred] : [])
|
97
105
|
end
|
98
106
|
end
|
99
107
|
|
@@ -109,6 +117,20 @@ class Maildir < Source
|
|
109
117
|
|
110
118
|
def pct_done; 100.0 * (@ids.index(cur_offset) || 0).to_f / (@ids.length - 1).to_f; end
|
111
119
|
|
120
|
+
def draft? msg; maildir_data(msg)[2].include? "D"; end
|
121
|
+
def flagged? msg; maildir_data(msg)[2].include? "F"; end
|
122
|
+
def passed? msg; maildir_data(msg)[2].include? "P"; end
|
123
|
+
def replied? msg; maildir_data(msg)[2].include? "R"; end
|
124
|
+
def seen? msg; maildir_data(msg)[2].include? "S"; end
|
125
|
+
def trashed? msg; maildir_data(msg)[2].include? "T"; end
|
126
|
+
|
127
|
+
def mark_draft msg; maildir_mark_file msg, "D" unless draft? msg; end
|
128
|
+
def mark_flagged msg; maildir_mark_file msg, "F" unless flagged? msg; end
|
129
|
+
def mark_passed msg; maildir_mark_file msg, "P" unless passed? msg; end
|
130
|
+
def mark_replied msg; maildir_mark_file msg, "R" unless replied? msg; end
|
131
|
+
def mark_seen msg; maildir_mark_file msg, "S" unless seen? msg; end
|
132
|
+
def mark_trashed msg; maildir_mark_file msg, "T" unless trashed? msg; end
|
133
|
+
|
112
134
|
private
|
113
135
|
|
114
136
|
def make_id fn
|
@@ -124,6 +146,29 @@ private
|
|
124
146
|
raise FatalSourceError, "Problem reading file for id #{id.inspect}: #{fn.inspect}: #{e.message}."
|
125
147
|
end
|
126
148
|
end
|
149
|
+
|
150
|
+
def maildir_data msg
|
151
|
+
fn = File.basename @ids_to_fns[msg]
|
152
|
+
fn =~ %r{^([^:,]+):([12]),([DFPRST]*)$}
|
153
|
+
[($1 || fn), ($2 || "2"), ($3 || "")]
|
154
|
+
end
|
155
|
+
|
156
|
+
## not thread-safe on msg
|
157
|
+
def maildir_mark_file msg, flag
|
158
|
+
orig_path = @ids_to_fns[msg]
|
159
|
+
orig_base, orig_fn = File.split(orig_path)
|
160
|
+
new_base = orig_base.slice(0..-4) + 'cur'
|
161
|
+
tmp_base = orig_base.slice(0..-4) + 'tmp'
|
162
|
+
md_base, md_ver, md_flags = maildir_data msg
|
163
|
+
md_flags += flag; md_flags = md_flags.split(//).sort.join.squeeze
|
164
|
+
new_path = File.join new_base, "#{md_base}:#{md_ver},#{md_flags}"
|
165
|
+
tmp_path = File.join tmp_base, "#{md_base}:#{md_ver},#{md_flags}"
|
166
|
+
File.link orig_path, tmp_path
|
167
|
+
File.unlink orig_path
|
168
|
+
File.link tmp_path, new_path
|
169
|
+
File.unlink tmp_path
|
170
|
+
@ids_to_fns[msg] = new_path
|
171
|
+
end
|
127
172
|
end
|
128
173
|
|
129
174
|
end
|
data/lib/sup/mbox.rb
CHANGED
@@ -56,7 +56,7 @@ module MBox
|
|
56
56
|
begin
|
57
57
|
Rfc2047.decode_to $encoding, v
|
58
58
|
rescue Errno::EINVAL, Iconv::InvalidEncoding, Iconv::IllegalSequence => e
|
59
|
-
Redwood::log "warning: error decoding RFC 2047 header: #{e.message}"
|
59
|
+
Redwood::log "warning: error decoding RFC 2047 header (#{e.class.name}): #{e.message}"
|
60
60
|
v
|
61
61
|
end
|
62
62
|
end
|
data/lib/sup/mbox/loader.rb
CHANGED
@@ -6,24 +6,30 @@ module MBox
|
|
6
6
|
|
7
7
|
class Loader < Source
|
8
8
|
yaml_properties :uri, :cur_offset, :usual, :archived, :id, :labels
|
9
|
-
def initialize uri_or_fp, start_offset=nil, usual=true, archived=false, id=nil, labels=[]
|
10
|
-
super uri_or_fp, start_offset, usual, archived, id
|
11
9
|
|
10
|
+
## uri_or_fp is horrific. need to refactor.
|
11
|
+
def initialize uri_or_fp, start_offset=nil, usual=true, archived=false, id=nil, labels=[]
|
12
12
|
@mutex = Mutex.new
|
13
|
-
@labels = (labels || []).freeze
|
13
|
+
@labels = ((labels || []) - LabelManager::RESERVED_LABELS).uniq.freeze
|
14
14
|
|
15
15
|
case uri_or_fp
|
16
16
|
when String
|
17
|
-
uri = URI(uri_or_fp)
|
17
|
+
uri = URI(Source.expand_filesystem_uri(uri_or_fp))
|
18
18
|
raise ArgumentError, "not an mbox uri" unless uri.scheme == "mbox"
|
19
|
-
raise ArgumentError, "mbox
|
19
|
+
raise ArgumentError, "mbox URI ('#{uri}') cannot have a host: #{uri.host}" if uri.host
|
20
|
+
raise ArgumentError, "mbox URI must have a path component" unless uri.path
|
20
21
|
@f = File.open uri.path
|
22
|
+
@path = uri.path
|
21
23
|
else
|
22
24
|
@f = uri_or_fp
|
25
|
+
@path = uri_or_fp.path
|
23
26
|
end
|
27
|
+
|
28
|
+
super uri_or_fp, start_offset, usual, archived, id
|
24
29
|
end
|
25
30
|
|
26
|
-
def file_path;
|
31
|
+
def file_path; @path end
|
32
|
+
def is_source_for? uri; super || (self.uri.is_a?(String) && (URI(Source.expand_filesystem_uri(uri)) == URI(Source.expand_filesystem_uri(self.uri)))) end
|
27
33
|
|
28
34
|
def self.suggest_labels_for path
|
29
35
|
## heuristic: use the filename as a label, unless the file
|
@@ -62,7 +68,11 @@ class Loader < Source
|
|
62
68
|
@f.seek offset
|
63
69
|
begin
|
64
70
|
RMail::Mailbox::MBoxReader.new(@f).each_message do |input|
|
65
|
-
|
71
|
+
m = RMail::Parser.read(input)
|
72
|
+
if m.body && m.body.is_a?(String)
|
73
|
+
m.body.gsub!(/^>From /, "From ")
|
74
|
+
end
|
75
|
+
return m
|
66
76
|
end
|
67
77
|
rescue RMail::Parser::Error => e
|
68
78
|
raise FatalSourceError, "error parsing mbox file: #{e.message}"
|
@@ -81,19 +91,19 @@ class Loader < Source
|
|
81
91
|
ret
|
82
92
|
end
|
83
93
|
|
84
|
-
def
|
94
|
+
def raw_message offset
|
85
95
|
ret = ""
|
86
|
-
|
96
|
+
each_raw_message_line(offset) { |l| ret += l }
|
87
97
|
ret
|
88
98
|
end
|
89
99
|
|
90
100
|
## apparently it's a million times faster to call this directly if
|
91
101
|
## we're just moving messages around on disk, than reading things
|
92
|
-
## into memory with
|
102
|
+
## into memory with raw_message.
|
93
103
|
##
|
94
104
|
## i hoped never to have to move shit around on disk but
|
95
105
|
## sup-sync-back has to do it.
|
96
|
-
def
|
106
|
+
def each_raw_message_line offset
|
97
107
|
@mutex.synchronize do
|
98
108
|
@f.seek offset
|
99
109
|
yield @f.gets
|
@@ -137,7 +147,7 @@ class Loader < Source
|
|
137
147
|
end
|
138
148
|
|
139
149
|
self.cur_offset = next_offset
|
140
|
-
[returned_offset, @labels]
|
150
|
+
[returned_offset, (@labels + [:unread]).uniq]
|
141
151
|
end
|
142
152
|
end
|
143
153
|
|
data/lib/sup/mbox/ssh-loader.rb
CHANGED
@@ -65,7 +65,7 @@ class SSHLoader < Source
|
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
68
|
-
[:start_offset, :load_header, :load_message, :raw_header, :
|
68
|
+
[:start_offset, :load_header, :load_message, :raw_header, :raw_message].each do |meth|
|
69
69
|
define_method(meth) { |*a| safely { @loader.send meth, *a } }
|
70
70
|
end
|
71
71
|
end
|
@@ -0,0 +1,198 @@
|
|
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 =
|
53
|
+
if encoded_content.body
|
54
|
+
encoded_content.decode
|
55
|
+
else
|
56
|
+
"For some bizarre reason, RubyMail was unable to parse this attachment.\n"
|
57
|
+
end
|
58
|
+
|
59
|
+
@lines =
|
60
|
+
case @content_type
|
61
|
+
when /^text\/plain\b/
|
62
|
+
Message.convert_from(@raw_content, encoded_content.charset).split("\n")
|
63
|
+
else
|
64
|
+
text = HookManager.run "mime-decode", :content_type => content_type,
|
65
|
+
:filename => lambda { write_to_disk },
|
66
|
+
:sibling_types => sibling_types
|
67
|
+
text.split("\n") if text
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def color; :none end
|
72
|
+
def patina_color; :attachment_color end
|
73
|
+
def patina_text
|
74
|
+
if expandable?
|
75
|
+
"Attachment: #{filename} (#{lines.length} lines)"
|
76
|
+
else
|
77
|
+
"Attachment: #{filename} (#{content_type})"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
## an attachment is exapndable if we've managed to decode it into
|
82
|
+
## something we can display inline. otherwise, it's viewable.
|
83
|
+
def inlineable?; false end
|
84
|
+
def expandable?; !viewable? end
|
85
|
+
def initial_state; :open end
|
86
|
+
def viewable?; @lines.nil? end
|
87
|
+
def view!
|
88
|
+
path = write_to_disk
|
89
|
+
system "/usr/bin/run-mailcap --action=view #{@content_type}:#{path} >& /dev/null"
|
90
|
+
$? == 0
|
91
|
+
end
|
92
|
+
|
93
|
+
def write_to_disk
|
94
|
+
file = Tempfile.new "redwood.attachment"
|
95
|
+
file.print @raw_content
|
96
|
+
file.close
|
97
|
+
file.path
|
98
|
+
end
|
99
|
+
|
100
|
+
## used when viewing the attachment as text
|
101
|
+
def to_s
|
102
|
+
@lines || @raw_content
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
class Text
|
107
|
+
WRAP_LEN = 80 # wrap at this width
|
108
|
+
|
109
|
+
attr_reader :lines
|
110
|
+
def initialize lines
|
111
|
+
@lines = lines.map { |l| l.chomp.wrap WRAP_LEN }.flatten # wrap
|
112
|
+
|
113
|
+
## trim off all empty lines except one
|
114
|
+
lines.pop while lines.last =~ /^\s*$/
|
115
|
+
end
|
116
|
+
|
117
|
+
def inlineable?; true end
|
118
|
+
def expandable?; false end
|
119
|
+
def viewable?; false end
|
120
|
+
def color; :none end
|
121
|
+
end
|
122
|
+
|
123
|
+
class Quote
|
124
|
+
attr_reader :lines
|
125
|
+
def initialize lines
|
126
|
+
@lines = lines
|
127
|
+
end
|
128
|
+
|
129
|
+
def inlineable?; @lines.length == 1 end
|
130
|
+
def expandable?; !inlineable? end
|
131
|
+
def viewable?; false end
|
132
|
+
|
133
|
+
def patina_color; :quote_patina_color end
|
134
|
+
def patina_text; "(#{lines.length} quoted lines)" end
|
135
|
+
def color; :quote_color end
|
136
|
+
end
|
137
|
+
|
138
|
+
class Signature
|
139
|
+
attr_reader :lines
|
140
|
+
def initialize lines
|
141
|
+
@lines = lines
|
142
|
+
end
|
143
|
+
|
144
|
+
def inlineable?; @lines.length == 1 end
|
145
|
+
def expandable?; !inlineable? end
|
146
|
+
def viewable?; false end
|
147
|
+
|
148
|
+
def patina_color; :sig_patina_color end
|
149
|
+
def patina_text; "(#{lines.length}-line signature)" end
|
150
|
+
def color; :sig_color end
|
151
|
+
end
|
152
|
+
|
153
|
+
class EnclosedMessage
|
154
|
+
attr_reader :lines
|
155
|
+
def initialize from, body
|
156
|
+
@from = from
|
157
|
+
@lines = body.split "\n"
|
158
|
+
end
|
159
|
+
|
160
|
+
def from
|
161
|
+
@from ? @from.longname : "unknown sender"
|
162
|
+
end
|
163
|
+
|
164
|
+
def inlineable?; false end
|
165
|
+
def expandable?; true end
|
166
|
+
def initial_state; :open end
|
167
|
+
def viewable?; false end
|
168
|
+
|
169
|
+
def patina_color; :generic_notice_patina_color end
|
170
|
+
def patina_text; "Begin enclosed message from #{from} (#{@lines.length} lines)" end
|
171
|
+
|
172
|
+
def color; :quote_color end
|
173
|
+
end
|
174
|
+
|
175
|
+
class CryptoNotice
|
176
|
+
attr_reader :lines, :status, :patina_text
|
177
|
+
|
178
|
+
def initialize status, description, lines=[]
|
179
|
+
@status = status
|
180
|
+
@patina_text = description
|
181
|
+
@lines = lines
|
182
|
+
end
|
183
|
+
|
184
|
+
def patina_color
|
185
|
+
case status
|
186
|
+
when :valid: :cryptosig_valid_color
|
187
|
+
when :invalid: :cryptosig_invalid_color
|
188
|
+
else :cryptosig_unknown_color
|
189
|
+
end
|
190
|
+
end
|
191
|
+
def color; patina_color end
|
192
|
+
|
193
|
+
def inlineable?; false end
|
194
|
+
def expandable?; !@lines.empty? end
|
195
|
+
def viewable?; false end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|