msgthr 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|