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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 9a571597f50c7ec04abef8c956d811854d727bcc
4
- data.tar.gz: 07f8634e7f6bd758206ad6e8170acd5048af2b07
2
+ SHA256:
3
+ metadata.gz: 5482fc1e20bf45ed07ee4b3eacc93eccd28c3f28195550d1629cb5d21919f42b
4
+ data.tar.gz: eaca3d5d6ef9840154d6b5fd70033382bdbb9fe3863f726585b7ef6d0d7dfae1
5
5
  SHA512:
6
- metadata.gz: 1e13bda13b30d4548955d306570848c8bf5f0993575617b81822a4fc684085182eb7200f5e0cadfc56c38215c166d06d8fa07853cbbad784c21665b13e9355ee
7
- data.tar.gz: 7217b572b347c32ef5a01d0b95c805dfc37e28bd54ad078d4bd38f8b7c5396ba0806d052c2194b078fac78770c72cb449cb485f2ec8af1f8fdff68b528fd3db6
6
+ metadata.gz: e73d95955bd492e97bd2c129986da0658df78fe82e185218195a769e77643b858bee1c64de84c94bb8e7baf6b5a611cad08ac492072ad25812c70821563dfc66
7
+ data.tar.gz: f12ec74ea787a1d09ccd20c191429bfafe0f291dfffa3fded5e4966efa1b8b82aad0e8653c06c1728e0a6ba06ee4703f63ffe2cf5a40e58a1cdb6d63c56c84d8
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  cgit_url: https://80x24.org/msgthr.git
3
3
  git_url: git://80x24.org/msgthr.git
4
- rdoc_url: https://80x24.org/msgthr/rdoc/
4
+ rdoc_url: https://80x24.org/msgthr/
5
5
  ml_url: https://80x24.org/msgthr-public/
6
6
  public_email: msgthr-public@80x24.org
@@ -4,7 +4,7 @@ all::
4
4
  pkg = msgthr
5
5
  RUBY = ruby
6
6
  lib := lib
7
- VERSION := 1.0.1
7
+ VERSION := 1.1.0
8
8
  RSYNC_DEST := 80x24.org:/srv/80x24/msgthr/
9
9
 
10
10
  RSYNC = rsync
data/README CHANGED
@@ -76,7 +76,7 @@ because CSS/JS/images are all too complicated for us.
76
76
 
77
77
  RDoc API documentation in simple HTML is available at:
78
78
 
79
- https://80x24.org/msgthr/rdoc/
79
+ https://80x24.org/msgthr/rdoc/Msgthr.html
80
80
 
81
81
  Copyright
82
82
  ---------
@@ -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 operations
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
- # This takes a block which yields an array of Msgthr::Container
50
- # objects for sorting.
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.order! { |ary| ary.sort_by!(&:mid) }
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.order! do |ary|
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
@@ -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#order!
5
- # and Msgthr#walk_thread APIs. They should should not be initialized
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
@@ -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.1'
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
@@ -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.1
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-06-08 00:00:00.000000000 Z
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.6.12
56
+ rubygems_version: 2.7.3
54
57
  signing_key:
55
58
  specification_version: 4
56
59
  summary: container-agnostic, non-recursive message threading