msgthr 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.olddoc.yml +1 -1
- data/GNUmakefile +1 -1
- data/README +1 -1
- data/lib/msgthr.rb +41 -18
- data/lib/msgthr/container.rb +3 -3
- data/msgthr.gemspec +9 -1
- data/test/test_msgthr.rb +63 -1
- metadata +7 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5482fc1e20bf45ed07ee4b3eacc93eccd28c3f28195550d1629cb5d21919f42b
|
4
|
+
data.tar.gz: eaca3d5d6ef9840154d6b5fd70033382bdbb9fe3863f726585b7ef6d0d7dfae1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e73d95955bd492e97bd2c129986da0658df78fe82e185218195a769e77643b858bee1c64de84c94bb8e7baf6b5a611cad08ac492072ad25812c70821563dfc66
|
7
|
+
data.tar.gz: f12ec74ea787a1d09ccd20c191429bfafe0f291dfffa3fded5e4966efa1b8b82aad0e8653c06c1728e0a6ba06ee4703f63ffe2cf5a40e58a1cdb6d63c56c84d8
|
data/.olddoc.yml
CHANGED
data/GNUmakefile
CHANGED
data/README
CHANGED
data/lib/msgthr.rb
CHANGED
@@ -7,8 +7,7 @@
|
|
7
7
|
#
|
8
8
|
# * use Msgthr.new to create a new object
|
9
9
|
# * use Msgthr#add! for every message you have
|
10
|
-
# * use Msgthr#thread! to perform threading
|
11
|
-
# * optionally, use Msgthr#order! to sort messages
|
10
|
+
# * use Msgthr#thread! to perform threading and (optionally) sort
|
12
11
|
# * use Msgthr#walk_thread to iterate through the threaded tree
|
13
12
|
#
|
14
13
|
# See https://80x24.org/msgthr/README for more info
|
@@ -20,43 +19,39 @@ class Msgthr
|
|
20
19
|
# calling Msgthr#thread!
|
21
20
|
attr_reader :rootset
|
22
21
|
|
22
|
+
# raised when methods are called in an unsupported order
|
23
|
+
StateError = Class.new(RuntimeError)
|
24
|
+
|
23
25
|
# Initialize a Msgthr object
|
24
26
|
def initialize
|
25
27
|
@id_table = {}
|
26
28
|
@rootset = []
|
29
|
+
@state = :init # :init => :threaded => :ordered
|
27
30
|
end
|
28
31
|
|
29
32
|
# Clear internal data structures to save memory and prepare for reuse
|
30
33
|
def clear
|
31
34
|
@rootset.clear
|
32
35
|
@id_table.clear
|
36
|
+
@state = :init
|
33
37
|
end
|
34
38
|
|
35
39
|
# Performs threading on the messages and returns the rootset
|
36
40
|
# (set of message containers without parents).
|
37
41
|
#
|
38
|
-
# Call this after all #add operations are complete.
|
39
|
-
# This does not sort, use #order! if sorting is necessary.
|
40
|
-
def thread!
|
41
|
-
ret = @rootset
|
42
|
-
@id_table.each_value { |cont| ret << cont if cont.parent.nil? }.clear
|
43
|
-
ret
|
44
|
-
end
|
45
|
-
|
46
|
-
# Performs an in-place sort on messages after thread!
|
47
|
-
# This is optional and intended to be called this only after #thread!
|
42
|
+
# Call this only after all #add operations are complete.
|
48
43
|
#
|
49
|
-
#
|
50
|
-
#
|
44
|
+
# If given an optional block, it will perform an in-place sort
|
45
|
+
# using the block parameter.
|
51
46
|
#
|
52
|
-
# To sort by unique +mid+ identifiers for each container:
|
47
|
+
# To thread and sort by unique +mid+ identifiers for each container:
|
53
48
|
#
|
54
|
-
# msgthr.
|
49
|
+
# msgthr.thread! { |ary| ary.sort_by!(&:mid) }
|
55
50
|
#
|
56
51
|
# If your opaque message pointer contains a +time+ accessor which gives
|
57
52
|
# a Time object:
|
58
53
|
#
|
59
|
-
# msgthr.
|
54
|
+
# msgthr.thread! do |ary|
|
60
55
|
# ary.sort_by! do |cont| # Msgthr::Container
|
61
56
|
# cur = cont.topmost
|
62
57
|
# cur ? cur.msg.time : Time.at(0)
|
@@ -67,16 +62,40 @@ class Msgthr
|
|
67
62
|
# Msgthr::Container#mid, as any known missing messages (ghosts)
|
68
63
|
# will still have a +mid+. However, Msgthr::Container#topmost is
|
69
64
|
# necessary if accessing Msgthr::Container#msg.
|
65
|
+
def thread!
|
66
|
+
raise StateError, "already #@state" if @state != :init
|
67
|
+
ret = @rootset
|
68
|
+
@id_table.each_value { |cont| ret << cont if cont.parent.nil? }.clear
|
69
|
+
@state = :threaded
|
70
|
+
order! { |ary| yield(ary) } if block_given?
|
71
|
+
ret
|
72
|
+
end
|
73
|
+
|
74
|
+
# Calling this method is unnecessary since msgthr 1.1.0.
|
75
|
+
# In previous releases, the #thread! did not support a block
|
76
|
+
# parameter for ordering. This method remains for compatibility.
|
70
77
|
def order!
|
78
|
+
case @state
|
79
|
+
when :init then raise StateError, "#thread! not called"
|
80
|
+
when :ordered then raise StateError, "already #@state"
|
81
|
+
# else @state == :threaded
|
82
|
+
end
|
83
|
+
|
71
84
|
yield @rootset
|
72
85
|
@rootset.each do |cont|
|
73
86
|
# this calls Msgthr::Container#order!, which is non-recursive
|
74
87
|
cont.order! { |children| yield(children) }
|
75
88
|
end
|
89
|
+
@state = :ordered
|
90
|
+
@rootset
|
76
91
|
end
|
77
92
|
|
78
93
|
# non-recursively walk a set of messages after #thread!
|
79
|
-
# (and optionally, #order!)
|
94
|
+
# (and optionally, #order!).
|
95
|
+
#
|
96
|
+
# If you do not care about ordering, you may call this
|
97
|
+
# immediately after all #add operations are complete starting
|
98
|
+
# with msgthr 1.1.0
|
80
99
|
#
|
81
100
|
# This takes a block and yields 3 elements to it: +|level, container, index|+
|
82
101
|
# for each message container.
|
@@ -95,6 +114,8 @@ class Msgthr
|
|
95
114
|
# printf("#{indent} % 3d. %s\n", index, subject)
|
96
115
|
# end
|
97
116
|
def walk_thread
|
117
|
+
thread! if @state == :init
|
118
|
+
order! { |_| } if @state == :threaded
|
98
119
|
i = -1
|
99
120
|
q = @rootset.map { |cont| [ 0, cont, i += 1 ] }
|
100
121
|
while tmp = q.shift
|
@@ -128,6 +149,8 @@ class Msgthr
|
|
128
149
|
# calling this method to avoid wasting memory on hash keys. Likewise
|
129
150
|
# is true for any String objects in +refs+.
|
130
151
|
def add(mid, refs, msg)
|
152
|
+
@state == :init or raise StateError, "cannot add when already #@state"
|
153
|
+
|
131
154
|
cur = @id_table[mid] ||= Msgthr::Container.new(mid)
|
132
155
|
cur.msg = msg
|
133
156
|
refs or return
|
data/lib/msgthr/container.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
# Copyright (C) 2016 all contributors <msgthr-public@80x24.org>
|
2
2
|
# License: GPL-2.0+ <https://www.gnu.org/licenses/gpl-2.0.txt>
|
3
3
|
|
4
|
-
# An internal container class, this is exposed for Msgthr#
|
5
|
-
# and Msgthr#walk_thread APIs
|
6
|
-
# in your own code.
|
4
|
+
# An internal container class, this is exposed for Msgthr#thread!
|
5
|
+
# Msgthr#order! and Msgthr#walk_thread APIs through block parameters.
|
6
|
+
# They should should not be initialized in your own code.
|
7
7
|
#
|
8
8
|
# One container object will exist for every message you call Msgthr#add! on,
|
9
9
|
# so there can potentially be many of these objects for large sets of
|
data/msgthr.gemspec
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
manifest = File.read('.manifest').split(/\n/)
|
6
6
|
s.name = %q{msgthr}
|
7
|
-
s.version = ENV['VERSION'] || '1.0
|
7
|
+
s.version = ENV['VERSION'] || '1.1.0'
|
8
8
|
s.authors = ['msgthr hackers']
|
9
9
|
s.summary = 'container-agnostic, non-recursive message threading'
|
10
10
|
s.description = File.read('README').split(/\n\n/)[1].strip
|
@@ -14,4 +14,12 @@ Gem::Specification.new do |s|
|
|
14
14
|
s.files = manifest
|
15
15
|
s.licenses = 'GPL-2.0+'
|
16
16
|
s.required_ruby_version = '>= 1.9.3'
|
17
|
+
|
18
|
+
if s.respond_to?(:metadata=)
|
19
|
+
s.metadata = {
|
20
|
+
'source_code_uri' => 'https://80x24.org/msgthr.git',
|
21
|
+
'mailing_list_uri' => 'https://80x24.org/msgthr-public/',
|
22
|
+
'bug_tracker_uri' => 'https://80x24.org/msgthr-public/',
|
23
|
+
}
|
24
|
+
end
|
17
25
|
end
|
data/test/test_msgthr.rb
CHANGED
@@ -11,8 +11,9 @@ class TestMsgthr < Test::Unit::TestCase
|
|
11
11
|
thr.add('c', nil, 'c')
|
12
12
|
thr.add('D', nil, 'D')
|
13
13
|
thr.add('d', %w(missing), 'd')
|
14
|
-
thr.thread!
|
14
|
+
rset = thr.thread!
|
15
15
|
rootset = thr.order! { |c| c.sort_by!(&:mid) }
|
16
|
+
assert_same rset, rootset
|
16
17
|
assert_equal %w(D c missing), rootset.map(&:mid)
|
17
18
|
assert_equal 'D', rootset[0].msg
|
18
19
|
assert_equal %w(b), rootset[1].children.map(&:mid)
|
@@ -30,6 +31,67 @@ class TestMsgthr < Test::Unit::TestCase
|
|
30
31
|
0. abc
|
31
32
|
2. [missing: <missing>]
|
32
33
|
0. d
|
34
|
+
EOF
|
35
|
+
assert_equal exp, out
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_order_in_thread
|
39
|
+
thr = Msgthr.new
|
40
|
+
thr.add(1, nil, 'a')
|
41
|
+
thr.add(2, [1], 'b')
|
42
|
+
thr.thread! do |ary|
|
43
|
+
ary.sort_by! do |cont|
|
44
|
+
cur = cont.topmost
|
45
|
+
cur ? cur : 0
|
46
|
+
end
|
47
|
+
end
|
48
|
+
out = ''
|
49
|
+
thr.walk_thread do |level, container, index|
|
50
|
+
msg = container.msg
|
51
|
+
out << "#{level} [#{index}] #{msg}\n"
|
52
|
+
end
|
53
|
+
exp = <<EOF.b
|
54
|
+
0 [0] a
|
55
|
+
1 [0] b
|
56
|
+
EOF
|
57
|
+
assert_equal exp, out
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_out_of_order
|
61
|
+
thr = Msgthr.new
|
62
|
+
thr.thread!
|
63
|
+
assert_raise(Msgthr::StateError) { thr.add(1, nil, 'a') }
|
64
|
+
thr.clear # make things good again, following should not raise:
|
65
|
+
thr.add(1, nil, 'a')
|
66
|
+
thr.thread!
|
67
|
+
assert_raise(Msgthr::StateError) { thr.thread! }
|
68
|
+
|
69
|
+
out = []
|
70
|
+
thr.walk_thread do |level, container, index|
|
71
|
+
msg = container.msg
|
72
|
+
out << "#{level} [#{index}] #{msg}"
|
73
|
+
end
|
74
|
+
assert_equal [ '0 [0] a' ], out
|
75
|
+
assert_raise(Msgthr::StateError) { thr.thread! { raise "DO NOT CALL" } }
|
76
|
+
assert_raise(Msgthr::StateError) { thr.order! { |_| raise "DO NOT CALL" } }
|
77
|
+
|
78
|
+
# this is legal, even if non-sensical
|
79
|
+
thr.clear
|
80
|
+
thr.walk_thread { |level, container, index| raise "DO NOT CALL" }
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_short_add_to_walk
|
84
|
+
thr = Msgthr.new
|
85
|
+
thr.add(1, nil, 'a')
|
86
|
+
thr.add(2, [1], 'b')
|
87
|
+
out = ''
|
88
|
+
thr.walk_thread do |level, container, index|
|
89
|
+
msg = container.msg
|
90
|
+
out << "#{level} [#{index}] #{msg}\n"
|
91
|
+
end
|
92
|
+
exp = <<EOF.b
|
93
|
+
0 [0] a
|
94
|
+
1 [0] b
|
33
95
|
EOF
|
34
96
|
assert_equal exp, out
|
35
97
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: msgthr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- msgthr hackers
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-12-31 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |-
|
14
14
|
Pure Ruby message threading based on the algorithm described by
|
@@ -33,7 +33,10 @@ files:
|
|
33
33
|
homepage: https://80x24.org/msgthr/
|
34
34
|
licenses:
|
35
35
|
- GPL-2.0+
|
36
|
-
metadata:
|
36
|
+
metadata:
|
37
|
+
source_code_uri: https://80x24.org/msgthr.git
|
38
|
+
mailing_list_uri: https://80x24.org/msgthr-public/
|
39
|
+
bug_tracker_uri: https://80x24.org/msgthr-public/
|
37
40
|
post_install_message:
|
38
41
|
rdoc_options: []
|
39
42
|
require_paths:
|
@@ -50,7 +53,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
50
53
|
version: '0'
|
51
54
|
requirements: []
|
52
55
|
rubyforge_project:
|
53
|
-
rubygems_version: 2.
|
56
|
+
rubygems_version: 2.7.3
|
54
57
|
signing_key:
|
55
58
|
specification_version: 4
|
56
59
|
summary: container-agnostic, non-recursive message threading
|