I'm very sorry, but it seems that an error occurred in Sup.
Please accept my sincere apologies. If you don't mind, please
send the backtrace below and a brief report of the circumstances
-to user wmorgan-sup at site masanjin dot net so that I might
-address this problem. Thank you!
+to wmorgan-sup at masanjin dot nets so that I might address this
+problem. Thank you!
Sincerely,
William
#!/usr/bin/env ruby
+require 'uri'
require 'rubygems'
-require 'highline'
+require 'highline/import'
require "sup"
+
Thread.abort_on_exception = true # make debugging possible
class Float
exit
end
+## for sources that require login information, prompt the user for
+## that. also provide a list of previously-defined login info to
+## choose from, if any.
+def get_login_info uri, sources
+ uri = URI(uri)
+ accounts = sources.map do |s|
+ next unless s.respond_to?(:username)
+ suri = URI(s.uri)
+ [suri.host, s.username, s.password]
+ end.compact.uniq.sort_by { |h, u, p| h == uri.host ? 0 : 1 }
+
+ username, password = nil, nil
+ unless accounts.empty?
+ say "Would you like to use the same account as for a previous source?"
+ choose do |menu|
+ accounts.each do |host, olduser, oldpw|
+ menu.choice("Use the account info for #{olduser}@#{host}") { username, password = olduser, oldpw }
+ end
+ menu.choice("Use a new account") { }
+ end
+ end
+
+ unless username && password
+ username = ask("Username for #{uri.host}: ");
+ password = ask("Password for #{uri.host}: ") { |q| q.echo = false }
+ end
+
+ [username, password]
+end
+
+
educate_user if ARGV.member? '--help'
archive = ARGV.delete "--archive"
educate_user
end
+$terminal.wrap_at = :auto
Redwood::start
-
index = Redwood::Index.new
index.load
-h = HighLine.new
-
-sources = ARGV.map do |fn|
- fn = "mbox://#{fn}" unless fn =~ %r!://!
- source = index.source_for fn
+sources = ARGV.map do |uri|
+ uri = "mbox://#{uri}" unless uri =~ %r!://!
+ source = index.source_for uri
unless source
source =
- case fn
+ case uri
when %r!^mbox\+ssh://!
- username = h.ask("Username for #{fn}: ");
- password = h.ask("Password for #{fn}: ") { |q| q.echo = false }
- puts # why?
- Redwood::MBox::SSHLoader.new(fn, username, password, nil, !unusual, !!archive)
+ say "For SSH connections, if you will use public key authentication, you may leave the username and password blank."
+ say "\n"
+ username, password = get_login_info uri, index.sources
+ Redwood::MBox::SSHLoader.new(uri, username, password, nil, !unusual, !!archive)
when %r!^imaps?://!
- username = h.ask("Username for #{fn}: ");
- password = h.ask("Password for #{fn}: ") { |q| q.echo = false }
- puts # why?
- Redwood::IMAP.new(fn, username, password, nil, !unusual, !!archive)
+ username, password = get_login_info uri, sources
+ Redwood::IMAP.new(uri, username, password, nil, !unusual, !!archive)
else
- Redwood::MBox::Loader.new(fn, nil, !unusual, !!archive)
+ Redwood::MBox::Loader.new(uri, nil, !unusual, !!archive)
end
index.add_source source
end
begin
sources.each do |source|
if source.broken?
- puts "error loading messages from #{source}: #{source.broken_msg}"
+ $stderr.puts "error loading messages from #{source}: #{source.broken_msg}"
next
end
next if source.done?
module Redwood
+## could be a bottleneck, but doesn't seem to significantly slow
+## things down.
+
+class SafeNcurses
+ def self.method_missing meth, *a, &b
+ @mutex ||= Mutex.new
+ @mutex.synchronize { Ncurses.send meth, *a, &b }
+ end
+end
+
class Buffer
attr_reader :mode, :x, :y, :width, :height, :title
bool_reader :dirty
def content_height; @height - 1; end
def content_width; @width; end
- def resize rows, cols
+ def resize rows, cols
+ return if rows == @width && cols == @height
@width = cols
@height = rows
mode.resize rows, cols
def completely_redraw_screen
return if @freeze
- Ncurses.clear
+ SafeNcurses.clear
@dirty = true
draw_screen
end
def handle_resize
return if @freeze
- rows, cols = Ncurses.rows, Ncurses.cols
- @buffers.each { |b| b.resize rows - 1, cols }
+ rows, cols = SafeNcurses.rows, SafeNcurses.cols
+ @buffers.each { |b| b.resize rows - minibuf_lines, cols }
completely_redraw_screen
flash "resized to #{rows}x#{cols}"
end
## (currently we only have one buffer visible at a time).
## TODO: reenable this if we allow multiple buffers
false && @buffers.inject(@dirty) do |dirty, buf|
- dirty ? buf.draw : buf.redraw
- dirty || buf.dirty?
+ buf.resize SafeNcurses.rows - minibuf_lines, SafeNcurses.cols
+ @dirty ? buf.draw : buf.redraw
end
## quick hack
- true && (@dirty ? @buffers.last.draw : @buffers.last.redraw)
-
+ if true
+ buf = @buffers.last
+ buf.resize SafeNcurses.rows - minibuf_lines, SafeNcurses.cols
+ @dirty ? buf.draw : buf.redraw
+ end
+
draw_minibuf unless skip_minibuf
@dirty = false
- Ncurses.doupdate
+ SafeNcurses.doupdate
end
## gets the mode from the block, which is only called if the buffer
realtitle = title
num = 2
while @name_map.member? realtitle
- realtitle = "#{title} #{num}"
+ realtitle = "#{title} <#{num}>"
num += 1
end
Redwood::log "spawning buffer \"#{realtitle}\""
- width = opts[:width] || Ncurses.cols
- height = opts[:height] || Ncurses.rows - 1
+ width = opts[:width] || SafeNcurses.cols
+ height = opts[:height] || SafeNcurses.rows - 1
## since we are currently only doing multiple full-screen modes,
## use stdscr for each window. once we become more sophisticated,
##
## w = Ncurses::WINDOW.new(height, width, (opts[:top] || 0),
## (opts[:left] || 0))
- w = Ncurses.stdscr
- raise "nil window" unless w
-
+ w = SafeNcurses.stdscr
b = Buffer.new w, mode, width, height, :title => realtitle
mode.buffer = b
@name_map[realtitle] = b
end
def ask domain, question, default=nil
- @textfields[domain] ||= TextField.new Ncurses.stdscr, Ncurses.rows - 1, 0,
- Ncurses.cols
+ @textfields[domain] ||= TextField.new SafeNcurses.stdscr, SafeNcurses.rows - 1, 0,
+ SafeNcurses.cols
tf = @textfields[domain]
## this goddamn ncurses form shit is a fucking 1970's
ret = nil
@freeze = true
tf.position_cursor
- Ncurses.refresh
- while tf.handle_input(Ncurses.nonblocking_getch); end
+ SafeNcurses.refresh
+ while tf.handle_input(SafeNcurses.nonblocking_getch); end
@freeze = false
ret = tf.value
accept = accept.split(//).map { |x| x[0] } if accept
flash question
- Ncurses.curs_set 1
- Ncurses.move Ncurses.rows - 1, question.length + 1
- Ncurses.refresh
+ SafeNcurses.curs_set 1
+ SafeNcurses.move SafeNcurses.rows - 1, question.length + 1
+ SafeNcurses.refresh
ret = nil
done = false
@freeze = true
until done
- key = Ncurses.nonblocking_getch
+ key = SafeNcurses.nonblocking_getch
if key == Ncurses::KEY_CANCEL
done = true
elsif (accept && accept.member?(key)) || !accept
end
end
@freeze = false
- Ncurses.curs_set 0
+ SafeNcurses.curs_set 0
erase_flash
draw_screen
- Ncurses.curs_set 0
+ SafeNcurses.curs_set 0
ret
end
end
end
+ def minibuf_lines; [(@flash ? 1 : 0) + @minibuf_stack.compact.size, 1].max; end
+
def draw_minibuf
- s = @flash || @minibuf_stack.reverse.find { |x| x } || ""
-
- Ncurses.attrset Colormap.color_for(:none)
- Ncurses.mvaddstr Ncurses.rows - 1, 0, s + (" " * [Ncurses.cols - s.length,
- 0].max)
+ SafeNcurses.attrset Colormap.color_for(:none)
+ m = @minibuf_stack.compact
+ m << @flash if @flash
+ m << "" if m.empty?
+ m.each_with_index do |s, i|
+ SafeNcurses.mvaddstr SafeNcurses.rows - i - 1, 0, s + (" " * [SafeNcurses.cols - s.length, 0].max)
+ end
end
def say s, id=nil
@minibuf_stack[id] = s
unless @freeze
draw_screen
- Ncurses.refresh
+ SafeNcurses.refresh
end
if block_given?
- yield
- clear id
- return
+ begin
+ yield
+ ensure
+ clear id
+ end
end
id
end
@flash = s
unless @freeze
draw_screen
- Ncurses.refresh
+ SafeNcurses.refresh
end
end
+ ## a little tricky because we can't just delete_at id because ids
+ ## are relative (they're positions into the array).
def clear id
@minibuf_stack[id] = nil
if id == @minibuf_stack.length - 1
@minibuf_stack.delete_at i
end
end
+
unless @freeze
draw_screen
- Ncurses.refresh
+ SafeNcurses.refresh
end
end
def shell_out command
@freeze = true
- Ncurses.endwin
+ SafeNcurses.endwin
system command
- Ncurses.refresh
- Ncurses.curs_set 0
+ SafeNcurses.refresh
+ SafeNcurses.curs_set 0
@freeze = false
end
end
require 'uri'
require 'net/imap'
require 'stringio'
+require 'time'
## fucking imap fucking sucks. what the FUCK kind of committee of
## dunces designed this shit.
-## you see, imap touts 'unique ids' for messages, which are to be used
-## for cross-session identification. great, just what sup needs! only,
-## it turns out the uids can be invalidated every time some arbitrary
-## 'uidvalidity' value changes on the server, and 'uidvalidity' has no
-## restrictions. it can change any time you log in. it can change
-## EVERY time you log in. of course the imap spec "strongly
+## imap talks about 'unique ids' for messages, to be used for
+## cross-session identification. great---just what sup needs! except
+## it turns out the uids can be invalidated every time the
+## 'uidvalidity' value changes on the server, and 'uidvalidity' can
+## change without restriction. it can change any time you log in. it
+## can change EVERY time you log in. of course the imap spec "strongly
## recommends" that it never change, but there's nothing to stop
-## people from just setting it to the current time, and in fact that's
-## exactly what the one imap server i have at my disposal does. thus
-## the so-called uids are absolutely useless and imap provides no
-## cross-session way of uniquely identifying a message. but thanks for
-## the "strong recommendation", guys!
-
-## right now i'm using the 'internal date' and the size of each
-## message to uniquely identify it, and i have to scan over the entire
-## mailbox each time i open it to map those things to message ids, and
-## we'll just hope that there are no collisions. ho ho! that's a
-## perfectly reasonable solution!
-
-## fuck you imap committee. you managed to design something as shitty
+## people from just setting it to the current timestamp, and in fact
+## that's exactly what the one imap server i have at my disposal
+## does. thus the so-called uids are absolutely useless and imap
+## provides no cross-session way of uniquely identifying a
+## message. but thanks for the "strong recommendation", guys!
+
+## so right now i'm using the 'internal date' and the size of each
+## message to uniquely identify it, and i scan over the entire mailbox
+## each time i open it to map those things to message ids. that can be
+## slow for large mailboxes, and we'll just have to hope that there
+## are no collisions. ho ho! a perfectly reasonable solution!
+
+## fuck you, imap committee. you managed to design something as shitty
## as mbox but goddamn THIRTY YEARS LATER.
module Redwood
class IMAP < Source
attr_reader_cloned :labels
-
+ attr_accessor :username, :password
+
def initialize uri, username, password, last_idate=nil, usual=true, archived=false, id=nil
raise ArgumentError, "username and password must be specified" unless username && password
raise ArgumentError, "not an imap uri" unless uri =~ %r!imaps?://!
@labels = [:unread]
@labels << :inbox unless archived?
@labels << mailbox.intern unless mailbox =~ /inbox/i || mailbox.nil?
+ @mutex = Mutex.new
end
def connect
Redwood::log "connecting to #{@parsed_uri.host} port #{ssl? ? 993 : 143}, ssl=#{ssl?} ..."
sid = BufferManager.say "Connecting to IMAP server #{host}..." if BufferManager.instantiated?
- ::Thread.new do
+ Redwood::reporting_thread do
begin
#raise Net::IMAP::ByeResponseError, "simulated imap failure"
- @imap = Net::IMAP.new host, ssl? ? 993 : 143, ssl?
+ # @imap = Net::IMAP.new host, ssl? ? 993 : 143, ssl?
+ sleep 3
BufferManager.say "Logging in...", sid if BufferManager.instantiated?
- @imap.authenticate 'LOGIN', @username, @password
+ # @imap.authenticate 'LOGIN', @username, @password
+ sleep 3
BufferManager.say "Sizing mailbox...", sid if BufferManager.instantiated?
- @imap.examine mailbox
- last_id = @imap.responses["EXISTS"][-1]
-
+ # @imap.examine mailbox
+ # last_id = @imap.responses["EXISTS"][-1]
+ sleep 1
+
BufferManager.say "Reading headers (because IMAP sucks)...", sid if BufferManager.instantiated?
- values = @imap.fetch(1 .. last_id, ['RFC822.SIZE', 'INTERNALDATE'])
-
+ # values = @imap.fetch(1 .. last_id, ['RFC822.SIZE', 'INTERNALDATE'])
+ sleep 3
+
+ raise Net::IMAP::ByeResponseError, "simulated imap failure"
Redwood::log "successfully connected to #{@parsed_uri}"
-
+
values.each do |v|
- msize, mdate = v.attr['RFC822.SIZE'], Time.parse(v.attr["INTERNALDATE"])
- id = sprintf("%d.%07d", mdate.to_i, msize).to_i
+ id = make_id v
@ids << id
@imap_ids[id] = v.seqno
end
end
end.join
+ @mutex.unlock
!!@imap
end
private :connect
+ def make_id imap_stuff
+ msize, mdate = imap_stuff.attr['RFC822.SIZE'], Time.parse(imap_stuff.attr["INTERNALDATE"])
+ sprintf("%d.%07d", mdate.to_i, msize).to_i
+ end
+ private :make_id
+
def host; @parsed_uri.host; end
def mailbox; @parsed_uri.path[1..-1] end ##XXXX TODO handle nil
def ssl?; @parsed_uri.scheme == 'imaps' end
## load the full header text
def raw_header id
- connect or raise SourceError, broken_msg
- get_imap_field(id, 'RFC822.HEADER').gsub(/\r\n/, "\n")
+ @mutex.synchronize do
+ connect or raise SourceError, broken_msg
+ get_imap_field(id, 'RFC822.HEADER').gsub(/\r\n/, "\n")
+ end
end
def raw_full_message id
- connect or raise SourceError, broken_msg
- get_imap_field(id, 'RFC822').gsub(/\r\n/, "\n")
+ @mutex.synchronize do
+ connect or raise SourceError, broken_msg
+ get_imap_field(id, 'RFC822').gsub(/\r\n/, "\n")
+ end
end
def get_imap_field id, field
- imap_id = @imap_ids[id] or raise SourceError, "Unknown message id #{id}. It is likely that messages have been deleted from this IMAP mailbox. Please run sup-import --rebuild #{to_s} in order to correct this problem."
-
- f =
+ f = nil
+ @mutex.synchronize do
+ imap_id = @imap_ids[id] or raise SourceError, "Unknown message id #{id}. It is likely that messages have been deleted from this IMAP mailbox."
begin
- @imap.fetch imap_id, field
+ f = @imap.fetch imap_id, [field, 'RFC822.SIZE', 'INTERNALDATE']
+ got_id = make_id f
+ raise SourceError, "IMAP message mismatch: requested #{id}, got #{got_id}. It is likely the IMAP mailbox has been modified." unless got_id == id
rescue Net::IMAP::Error => e
raise SourceError, e.message
end
- raise SourceError, "null IMAP field '#{field}' for message with id #{id} imap id #{imap_id}" if f.nil?
+ raise SourceError, "null IMAP field '#{field}' for message with id #{id} imap id #{imap_id}" if f.nil?
+ end
f[0].attr[field]
end
private :get_imap_field
def each
- connect or raise SourceError, broken_msg
+ @mutex.synchronize { connect or raise SourceError, broken_msg }
start = @ids.index(cur_offset || start_offset)
start.upto(@ids.length - 1) do |i|
end
def start_offset
- connect or raise SourceError, broken_msg
+ @mutex.synchronize { connect or raise SourceError, broken_msg }
@ids.first
end
def end_offset
- connect or raise SourceError, broken_msg
+ @mutex.synchronize { connect or raise SourceError, broken_msg }
@ids.last
end
end
class Index
include Singleton
- attr_reader :index # debugging only
-
+ attr_reader :index
def initialize dir=BASE_DIR
@dir = dir
@sources = {}
def source_for name; @sources.values.find { |s| s.is_source_for? name }; end
def usual_sources; @sources.values.find_all { |s| s.usual? }; end
+ def sources; @sources.values; end
def load_index dir=File.join(@dir, "ferret")
if File.exists? dir
end
def user_labels; @labels.keys; end
-
def << t; @labels[t] = true unless @labels.member?(t) || RESERVED_LABELS.member?(t); end
-
def delete t; @labels.delete t; end
-
def save
File.open(@fn, "w") { |f| f.puts @labels.keys }
end
## straight through the mbox (an import) or we're reading a few
## messages at a time (viewing messages) so the latency is not a problem.
-## all of the methods here catch SSHFileErrors, SocketErrors, and
-## Net::SSH::Exceptions and reraise them as SourceErrors. due to this
-## and to the logging, this class is somewhat tied to Sup, but it
-## wouldn't be too difficult to remove those bits and make it more
-## general-purpose.
+## all of the methods here can throw SSHFileErrors, SocketErrors,
+## Net::SSH::Exceptions and Errno::ENOENTs.
## debugging TODO: remove me
def debug s
@ssh_opts = ssh_opts
@file_size = nil
@offset = 0
+ @say_id = nil
+ @broken_msg = nil
+ end
+
+ def broken?; !@broken_msg.nil?; end
+
+ def say s
+ @say_id = BufferManager.say s, @say_id if BufferManager.instantiated?
+ Redwood::log s
+ end
+ private :say
+
+ def shutup
+ BufferManager.clear @say_id if BufferManager.instantiated?
+ @say_id = nil
end
def connect
return if @session
+ raise SSHFileError, @broken_msg if broken?
- Redwood::log "starting SSH session to #@host for #@fn..."
- sid = BufferManager.say "Connecting to SSH host #{@host}..." if BufferManager.instantiated?
+ say "Opening SSH connection to #{@host}..."
begin
- @session = Net::SSH.start @host, @ssh_opts
- MBox::debug "starting SSH shell..."
- BufferManager.say "Starting SSH shell...", sid if BufferManager.instantiated?
- @shell = @session.shell.sync
- MBox::debug "checking for file existence..."
+ #raise SSHFileError, "simulated SSH file error"
+ #@session = Net::SSH.start @host, @ssh_opts
+ sleep 3
+ say "Starting SSH shell..."
+ # @shell = @session.shell.sync
+ sleep 3
+ say "Checking for #@fn..."
+ sleep 1
+ raise Errno::ENOENT, @fn
raise Errno::ENOENT, @fn unless @shell.test("-e #@fn").status == 0
- MBox::debug "SSH is ready"
- ensure
- BufferManager.clear sid if BufferManager.instantiated?
+ ensure
+ shutup
end
end
- def eof?; raise "offset #@offset size #{size}" unless @offset && size; @offset >= size; end
- def eof; eof?; end # lame but IO does this and rmail depends on it
- def seek loc; raise "nil" unless loc; @offset = loc; end
+ def eof?; @offset >= size; end
+ def eof; eof?; end # lame but IO's method is named this and rmail calls that
+ def seek loc; @offset = loc; end
def tell; @offset; end
def total; size; end
def gets
return nil if eof?
-
make_buf_include @offset
expand_buf_forward while @buf.index("\n", @offset).nil? && @buf.endd < size
-
- with(@buf[@offset .. (@buf.index("\n", @offset) || -1)]) { |line| @offset += line.length }
+ returning(@buf[@offset .. (@buf.index("\n", @offset) || -1)]) { |line| @offset += line.length }
end
def read n
begin
result = @shell.send_command cmd
raise SSHFileError, "Failure during remote command #{cmd.inspect}: #{result.stderr[0 .. 100]}" unless result.status == 0
-
rescue Net::SSH::Exception # these happen occasionally for no apparent reason. gotta love that nondeterminism!
retry if (retries += 1) < 3
raise
end
- result.stdout
- rescue Net::SSH::Exception, SocketError, Errno::ENOENT => e
- @session = nil
- Redwood::log "error connecting to SSH server: #{e.message}"
- raise SourceError, "error connecting to SSH server: #{e.message}"
+ rescue Net::SSH::Exception, SSHFileError, Errno::ENOENT => e
+ @broken_msg = e.message
+ raise
end
+ result.stdout
end
def get_bytes offset, size
- #MBox::debug "! request for [#{offset}, #{offset + size}); buf is #@buf"
- raise "wtf: offset #{offset} size #{size}" if size == 0 || offset < 0
do_remote "tail -c +#{offset + 1} #@fn | head -c #{size}", size
end
module Redwood
module MBox
+## this is slightly complicated because SSHFile (and thus @f or
+## @loader) can throw a variety of exceptions, and we need to catch
+## those, reraise them as SourceErrors, and set ourselves as broken.
+
class SSHLoader < Source
attr_reader_cloned :labels
+ attr_accessor :username, :password
def initialize uri, username=nil, password=nil, start_offset=nil, usual=true, archived=false, id=nil
raise ArgumentError, "not an mbox+ssh uri: #{uri.inspect}" unless uri =~ %r!^mbox\+ssh://!
end
def host; @parsed_uri.host; end
- def filename; @parsed_uri.path[1..-1] end ##XXXX TODO handle nil
+ def filename; @parsed_uri.path[1..-1] end
def next
- offset, labels = @loader.next
- self.cur_offset = @loader.cur_offset # only necessary because YAML is a PITA
- [offset, (labels + @labels).uniq]
+ return if broken?
+ begin
+ offset, labels = @loader.next
+ self.cur_offset = @loader.cur_offset # superclass keeps @cur_offset which is used by yaml
+ [offset, (labels + @labels).uniq] # add our labels
+ rescue Net::SSH::Exception, SocketError, SSHFileError, Errno::ENOENT => e
+ recover_from e
+ end
+ end
+
+ def end_offset
+ begin
+ @f.size
+ rescue Net::SSH::Exception, SocketError, SSHFileError, Errno::ENOENT => e
+ recover_from e
+ end
end
- def end_offset; @f.size; end
def cur_offset= o; @cur_offset = @loader.cur_offset = o; @dirty = true; end
def id; @loader.id; end
def id= o; @id = @loader.id = o; end
- def cur_offset; @loader.cur_offset; end
+ # def cur_offset; @loader.cur_offset; end # think we'll be ok without this
def to_s; @parsed_uri.to_s; end
- defer_all_other_method_calls_to :loader
+ def recover_from e
+ m = "error communicating with SSH server #{host} (#{e.class.name}): #{e.message}"
+ Redwood::log m
+ self.broken_msg = @loader.broken_msg = m
+ raise SourceError, m
+ end
+
+ [:start_offset, :load_header, :load_message, :raw_header, :raw_full_message].each do |meth|
+ define_method meth do |*a|
+ begin
+ @loader.send meth, *a
+ rescue Net::SSH::Exception, SocketError, SSHFileError, Errno::ENOENT => e
+ recover_from e
+ end
+ end
+ end
end
Redwood::register_yaml(SSHLoader, %w(uri username password cur_offset usual archived id))
<<EOS
#@snippet...
-***********
-** ERROR **
-***********
-
-An error occurred while loading this message. It is possible that the source
-has changed, or (in the case of remote sources) is down.
+***********************************************************************
+* An error occurred while loading this message. It is possible that *
+* the source has changed, or (in the case of remote sources) is down. *
+***********************************************************************
The error message was:
#{msg}
@botline = [@topline + buffer.content_height, lines].min
end
+ def resize *a
+ super *a
+ ensure_mode_validity
+ end
+
protected
def draw_line ln, opts={}
## TODO: don't regen text completely
Redwood::reporting_thread do
- Redwood::log "loading messages for thread"
mode = ThreadViewMode.new t, @hidden_labels
BufferManager.spawn t.subj, mode
BufferManager.draw_screen
UpdateManager.relay :read, m
end
end
-
- Redwood::log "releasing chunks and text from \"#{buffer.title}\""
@messages = @chunks = @text = nil
end
## reraise them as source errors.
bool_reader :usual, :archived, :dirty
- attr_reader :cur_offset, :broken_msg
+ attr_reader :uri, :cur_offset, :broken_msg
attr_accessor :id
def initialize uri, initial_offset=nil, usual=true, archived=false, id=nil
def is_source_for? s; to_s == s; end
def each
+ return if broken?
begin
self.cur_offset ||= start_offset
until done? || broken? # just like life!
raise "no message" unless n
yield n, labels
end
- rescue SourceError
- # just die
+ rescue SourceError => e
+ self.broken_msg = e.message
end
end
def broken_msg= m
@broken_msg = m
- Redwood::log "#{to_s}: #{m}"
+# Redwood::log "#{to_s}: #{m}"
end
end
##
## i'm sure there's pithy comment i could make here about the
## superiority of lisp, but fuck lisp.
- def with x; yield x; x; end
+ def returning x; yield x; x; end
end
class String