Class: Msgthr
- Inherits:
-
Object
- Object
- Msgthr
- Defined in:
- lib/msgthr.rb
Overview
Non-recursive, container-agnostic message threading.
Usage is typically:
-
use Msgthr.new to create a new object
-
use Msgthr#add! for every message you have
-
use Msgthr#thread! to perform threading operations
-
optionally, use Msgthr#order! to sort messages
-
use Msgthr#walk_thread to iterate through the threaded tree
See 80x24.org/msgthr/README for more info You may email us publically at [email protected] Archives are at 80x24.org/msgthr-public/
Defined Under Namespace
Classes: Container
Instance Attribute Summary collapse
-
#rootset ⇒ Object
readonly
an Array of root (parent-less) messages, only populated after calling Msgthr#thread!.
Instance Method Summary collapse
-
#add(mid, refs, msg) ⇒ Object
Adds a message to prepare a Msgthr object for threading.
-
#clear ⇒ Object
Clear internal data structures to save memory and prepare for reuse.
-
#initialize ⇒ Msgthr
constructor
Initialize a Msgthr object.
-
#order! {|@rootset| ... } ⇒ Object
Performs an in-place sort on messages after thread! This is optional and intended to be called this only after #thread!.
-
#thread! ⇒ Object
Performs threading on the messages and returns the rootset (set of message containers without parents).
-
#walk_thread ⇒ Object
non-recursively walk a set of messages after #thread! (and optionally, #order!).
Constructor Details
#initialize ⇒ Msgthr
Initialize a Msgthr object
24 25 26 27 |
# File 'lib/msgthr.rb', line 24 def initialize @id_table = {} @rootset = [] end |
Instance Attribute Details
#rootset ⇒ Object (readonly)
an Array of root (parent-less) messages, only populated after calling Msgthr#thread!
21 22 23 |
# File 'lib/msgthr.rb', line 21 def rootset @rootset end |
Instance Method Details
#add(mid, refs, msg) ⇒ Object
Adds a message to prepare a Msgthr object for threading.
-
midis a unique identifier for the message in a given thread. It is typically a String or Integer, but may be anything usable as a Hash key in Ruby. -
refsshould be an Array of unique identifiers belonging to ancestors of the current message. For mail and news messages, this is usually the parsed result of the “References:” header. Order should be oldest to newest in terms of ancestry, with the last element being the immediate parent of the given message.This is
nilfor messages with no parent (root messages). -
msgis an opaque object which typically contains a Mail or Tmail object for handling mail.
If mid is a String, it is recommended to freeze the string before calling this method to avoid wasting memory on hash keys. Likewise is true for any String objects in refs.
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/msgthr.rb', line 130 def add(mid, refs, msg) cur = @id_table[mid] ||= Msgthr::Container.new(mid) cur.msg = msg refs or return # n.b. centralized messaging systems (e.g. forums) do not need # multiple References:, only decentralized systems need it to # tolerate missing messages prev = nil refs.each do |ref| cont = @id_table[ref] ||= Msgthr::Container.new(ref) # link refs together in order implied, # but do not change existing links or loop if prev && !cont.parent && !cont.has_descendent(prev) prev.add_child(cont) end prev = cont end # set parent of this message to be the last element in refs prev.add_child(cur) if prev end |
#clear ⇒ Object
Clear internal data structures to save memory and prepare for reuse
30 31 32 33 |
# File 'lib/msgthr.rb', line 30 def clear @rootset.clear @id_table.clear end |
#order! {|@rootset| ... } ⇒ Object
Performs an in-place sort on messages after thread! This is optional and intended to be called this only after #thread!
This takes a block which yields an array of Msgthr::Container objects for sorting.
To sort by unique mid identifiers for each container:
msgthr.order! { |ary| ary.sort_by!(&:mid) }
If your opaque message pointer contains a time accessor which gives a Time object:
msgthr.order! do |ary|
ary.sort_by! do |cont| # Msgthr::Container
cur = cont.topmost
cur ? cur.msg.time : Time.at(0)
end
end
Note, using Msgthr::Container#topmost is NOT necessary when accessing Msgthr::Container#mid, as any known missing messages (ghosts) will still have a mid. However, Msgthr::Container#topmost is necessary if accessing Msgthr::Container#msg.
70 71 72 73 74 75 76 |
# File 'lib/msgthr.rb', line 70 def order! yield @rootset @rootset.each do |cont| # this calls Msgthr::Container#order!, which is non-recursive cont.order! { |children| yield(children) } end end |
#thread! ⇒ Object
Performs threading on the messages and returns the rootset (set of message containers without parents).
Call this after all #add operations are complete. This does not sort, use #order! if sorting is necessary.
40 41 42 43 44 |
# File 'lib/msgthr.rb', line 40 def thread! ret = @rootset @id_table.each_value { |cont| ret << cont if cont.parent.nil? }.clear ret end |
#walk_thread ⇒ Object
non-recursively walk a set of messages after #thread! (and optionally, #order!)
This takes a block and yields 3 elements to it: |level, container, index| for each message container.
-
levelis the current depth within the walk (non-negative Integer) -
containeris the Msgthr::Container object -
indexis the offset of the container within its level (starting at 0)
To display the subject of each message with indentation, assuming your msg pointer has a subject field:
msgthr.walk_thread do |level, container, index|
msg = container.msg
subject = msg ? msg.subject : "[missing: <#{container.mid}>]"
indent = ' ' * level
printf("#{indent} % 3d. %s\n", index, subject)
end
97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/msgthr.rb', line 97 def walk_thread i = -1 q = @rootset.map { |cont| [ 0, cont, i += 1 ] } while tmp = q.shift level, cont, idx = tmp yield(level, cont, idx) level += 1 i = -1 q = cont.children.map { |cld| [ level, cld, i += 1 ] }.concat(q) end end |