From 76c10dada86fd807d36ccafca393e945e381696a Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 12 Nov 2016 02:13:42 +0000 Subject: initial --- lib/msgthr.rb | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 lib/msgthr.rb (limited to 'lib/msgthr.rb') diff --git a/lib/msgthr.rb b/lib/msgthr.rb new file mode 100644 index 0000000..f8b5896 --- /dev/null +++ b/lib/msgthr.rb @@ -0,0 +1,91 @@ +# Copyright (C) 2016 all contributors +# License: GPL-2.0+ + +# Non-recursive, container-agnostic message threading. +class Msgthr + + # an Array of root (parent-less) messages, only populated after + # calling Msgthr#thread! + attr_reader :rootset + + # Initialize a Msgthr object + def initialize + @id_table = {} + @rootset = [] + end + + # Clear internal data structures to save memory and prepare for reuse + def clear + @rootset.clear + @id_table.clear + end + + # Threads the message + # This does not sort + def thread! + ret = @rootset + @id_table.each_value { |cont| ret << cont if cont.parent.nil? }.clear + ret + end + + def order! + yield @rootset + @rootset.each { |cont| cont.order! { |children| yield(children) } } + end + + # non-recursively walk a thread + 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 + + # Adds a message to prepare a Msgthr object for threading. + # + # * +mid+ is a unique identifier for the message in a given thread. + # + # * +refs+ should be an Array of unique identifiers. 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 +nil+ for messages with no parent. + # + # * +msg+ is 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. + 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 +end + +require_relative 'msgthr/container' -- cgit v1.2.3-24-ge0c7