rambling-trie 2.5.1 → 2.7.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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +2 -1
  3. data/Gemfile +2 -0
  4. data/README.md +28 -20
  5. data/Steepfile +5 -1
  6. data/lib/rambling/trie/comparable.rb +4 -2
  7. data/lib/rambling/trie/compressible.rb +1 -1
  8. data/lib/rambling/trie/compressor.rb +16 -4
  9. data/lib/rambling/trie/configuration/properties.rb +3 -1
  10. data/lib/rambling/trie/configuration/provider_collection.rb +7 -7
  11. data/lib/rambling/trie/container.rb +46 -21
  12. data/lib/rambling/trie/enumerable.rb +8 -3
  13. data/lib/rambling/trie/inspectable.rb +6 -1
  14. data/lib/rambling/trie/nodes/compressed.rb +27 -19
  15. data/lib/rambling/trie/nodes/missing.rb +17 -0
  16. data/lib/rambling/trie/nodes/node.rb +28 -21
  17. data/lib/rambling/trie/nodes/raw.rb +11 -8
  18. data/lib/rambling/trie/not_implemented.rb +23 -0
  19. data/lib/rambling/trie/readers/plain_text.rb +4 -1
  20. data/lib/rambling/trie/readers/reader.rb +5 -2
  21. data/lib/rambling/trie/serializers/marshal.rb +5 -0
  22. data/lib/rambling/trie/serializers/serializer.rb +8 -4
  23. data/lib/rambling/trie/serializers/yaml.rb +5 -0
  24. data/lib/rambling/trie/serializers/zip.rb +31 -21
  25. data/lib/rambling/trie/stringifyable.rb +2 -5
  26. data/lib/rambling/trie/version.rb +1 -1
  27. data/lib/rambling/trie.rb +14 -10
  28. data/rambling-trie.gemspec +1 -1
  29. data/sig/lib/nilable.rbs +3 -0
  30. data/sig/lib/rambling/trie/comparable.rbs +6 -4
  31. data/sig/lib/rambling/trie/compressible.rbs +2 -2
  32. data/sig/lib/rambling/trie/compressor.rbs +6 -6
  33. data/sig/lib/rambling/trie/configuration/properties.rbs +5 -5
  34. data/sig/lib/rambling/trie/configuration/provider_collection.rbs +0 -4
  35. data/sig/lib/rambling/trie/container.rbs +24 -23
  36. data/sig/lib/rambling/trie/enumerable.rbs +6 -5
  37. data/sig/lib/rambling/trie/inspectable.rbs +8 -3
  38. data/sig/lib/rambling/trie/nodes/compressed.rbs +6 -4
  39. data/sig/lib/rambling/trie/nodes/missing.rbs +10 -2
  40. data/sig/lib/rambling/trie/nodes/node.rbs +21 -19
  41. data/sig/lib/rambling/trie/nodes/raw.rbs +5 -5
  42. data/sig/lib/rambling/trie/not_implemented.rbs +10 -0
  43. data/sig/lib/rambling/trie/readers/plain_text.rbs +2 -2
  44. data/sig/lib/rambling/trie/readers/reader.rbs +2 -0
  45. data/sig/lib/rambling/trie/serializers/marshal.rbs +1 -1
  46. data/sig/lib/rambling/trie/serializers/serializer.rbs +2 -0
  47. data/sig/lib/rambling/trie/serializers/yaml.rbs +1 -1
  48. data/sig/lib/rambling/trie/serializers/zip.rbs +8 -6
  49. data/sig/lib/rambling/trie/stringifyable.rbs +4 -4
  50. data/sig/lib/rambling/trie.rbs +10 -10
  51. metadata +8 -10
  52. data/sig/lib/zip/entry.rbs +0 -11
  53. data/sig/lib/zip/file.rbs +0 -11
@@ -7,6 +7,7 @@ module Rambling
7
7
  # :reek:MissingSafeMethod { exclude: [ terminal! ] }
8
8
  # :reek:RepeatedConditional { max_ifs: 3 }
9
9
  class Node
10
+ include NotImplemented
10
11
  include Rambling::Trie::Compressible
11
12
  include Rambling::Trie::Enumerable
12
13
  include Rambling::Trie::Comparable
@@ -31,14 +32,20 @@ module Rambling
31
32
  # @return [Node, nil] the parent of the current node.
32
33
  attr_accessor :parent
33
34
 
35
+ # Arbitrary value stored in this node
36
+ # @return [TValue, nil] the value stored in this node.
37
+ attr_accessor :value
38
+
34
39
  # Creates a new node.
35
40
  # @param [Symbol, nil] letter the Node's letter value.
36
41
  # @param [Node, nil] parent the parent of the current node.
37
- # @param [Hash<Symbol, Node>] children_tree the children tree of the current node.
38
- def initialize letter = nil, parent = nil, children_tree = {}
42
+ # @param [Hash<Symbol, Node>, nil] children_tree the children tree of the current node,
43
+ # or +nil+ to start with an empty tree. When a hash is provided, ownership transfers
44
+ # to the node; callers must not retain references to or mutate the hash after passing it.
45
+ def initialize letter = nil, parent = nil, children_tree = nil
39
46
  @letter = letter
40
47
  @parent = parent
41
- @children_tree = children_tree
48
+ @children_tree = children_tree || {}
42
49
  end
43
50
 
44
51
  # Child nodes.
@@ -50,13 +57,7 @@ module Rambling
50
57
  # First child node.
51
58
  # @return [Node, nil] the first child contained in the current node.
52
59
  def first_child
53
- return if children_tree.empty?
54
-
55
- # rubocop:disable Lint/UnreachableLoop
56
- children_tree.each_value { |child| return child }
57
- # rubocop:enable Lint/UnreachableLoop
58
-
59
- nil
60
+ children_tree.each_value.first
60
61
  end
61
62
 
62
63
  # Indicates if the current node is the root node.
@@ -133,7 +134,7 @@ module Rambling
133
134
  end
134
135
 
135
136
  # Set the {Node Node} that corresponds to a given letter.
136
- # @param [Symbol] letter the letter to insert or update in the node's
137
+ # @param [Symbol] letter the letter to insert or update in the node's children tree.
137
138
  # @param [Node] node the {Node Node} to assign to that letter.
138
139
  # @return [Node] the node corresponding to the inserted or updated letter.
139
140
  # @see https://ruby-doc.org/3.3.0/Hash.html#method-i-5B-5D Hash#[]
@@ -170,22 +171,28 @@ module Rambling
170
171
 
171
172
  attr_accessor :terminal
172
173
 
173
- # abstract methods
174
-
175
- def children_match_prefix chars
176
- raise NotImplementedError
174
+ # @abstract Subclass and override {#children_match_prefix} to match a prefix against child nodes.
175
+ # @raise [NotImplementedError] when not overridden by a subclass
176
+ def children_match_prefix _chars
177
+ not_implemented
177
178
  end
178
179
 
179
- def partial_word_chars? chars
180
- raise NotImplementedError
180
+ # @abstract Subclass and override {#partial_word_chars?} to check for a partial-word path.
181
+ # @raise [NotImplementedError] when not overridden by a subclass
182
+ def partial_word_chars? _chars
183
+ not_implemented
181
184
  end
182
185
 
183
- def word_chars? chars
184
- raise NotImplementedError
186
+ # @abstract Subclass and override {#word_chars?} to check for a full-word path.
187
+ # @raise [NotImplementedError] when not overridden by a subclass
188
+ def word_chars? _chars
189
+ not_implemented
185
190
  end
186
191
 
187
- def closest_node chars
188
- raise NotImplementedError
192
+ # @abstract Subclass and override {#closest_node} to return the deepest matching node.
193
+ # @raise [NotImplementedError] when not overridden by a subclass
194
+ def closest_node _chars
195
+ not_implemented
189
196
  end
190
197
  end
191
198
  end
@@ -9,13 +9,16 @@ module Rambling
9
9
  # Adds a word to the current raw (uncompressed) trie node.
10
10
  # @param [Array<Symbol>] reversed_chars the char array to add to the trie, in reverse order.
11
11
  # @return [Node] the added/modified node based on the word added.
12
- # @note This method clears the contents of the chars variable.
13
- def add reversed_chars
12
+ # @note This method consumes the array by popping each element during recursion, leaving it empty on return.
13
+ def add reversed_chars, value = nil
14
14
  if reversed_chars.empty?
15
- terminal! unless root?
15
+ unless root?
16
+ self.value = value
17
+ terminal!
18
+ end
16
19
  self
17
20
  else
18
- add_to_children_tree reversed_chars
21
+ add_to_children_tree reversed_chars, value
19
22
  end
20
23
  end
21
24
 
@@ -27,10 +30,10 @@ module Rambling
27
30
 
28
31
  private
29
32
 
30
- def add_to_children_tree chars
33
+ def add_to_children_tree chars, value = nil
31
34
  letter = chars.pop || raise
32
35
  child = children_tree[letter] || new_node(letter)
33
- child.add chars
36
+ child.add chars, value
34
37
  child
35
38
  end
36
39
 
@@ -67,10 +70,10 @@ module Rambling
67
70
  def children_match_prefix chars
68
71
  return enum_for :children_match_prefix, chars unless block_given?
69
72
 
70
- return EMPTY_ENUMERATOR if chars.empty?
73
+ return empty_enum if chars.empty?
71
74
 
72
75
  child = children_tree[(chars.shift || raise).to_sym]
73
- return EMPTY_ENUMERATOR unless child
76
+ return empty_enum unless child
74
77
 
75
78
  child.match_prefix(chars) { |word| yield word }
76
79
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rambling
4
+ # :reek:IrresponsibleModule (unclear why this is necessary here but not in other submodules)
5
+ module Trie
6
+ # Provides a private helper for marking abstract methods.
7
+ # @api private
8
+ module NotImplemented
9
+ private
10
+
11
+ # Raises {NotImplementedError} with the concrete class and calling method name.
12
+ # @raise [NotImplementedError] always, with message
13
+ # <code>"ClassName#method_name is not implemented"</code>
14
+ # @return [void]
15
+ def not_implemented
16
+ raise NotImplementedError,
17
+ "#{self.class}##{caller_locations(1, 1)&.first&.label} is not implemented"
18
+ end
19
+ end
20
+
21
+ private_constant :NotImplemented
22
+ end
23
+ end
@@ -12,7 +12,10 @@ module Rambling
12
12
  def each_word filepath
13
13
  return enum_for :each_word, filepath unless block_given?
14
14
 
15
- ::File.foreach(filepath) { |line| yield line.chomp! }
15
+ ::File.foreach(filepath) do |line|
16
+ line.chomp!
17
+ yield line
18
+ end
16
19
 
17
20
  self
18
21
  end
@@ -5,13 +5,16 @@ module Rambling
5
5
  module Readers
6
6
  # Base class for all readers.
7
7
  class Reader
8
+ include NotImplemented
9
+
8
10
  # Yields each word read from given file.
9
11
  # @abstract Subclass and override {#each_word} to fit to a particular file format.
10
12
  # @param [String] filepath the full path of the file to load the words from.
11
13
  # @yield [String] Each line read from the file.
12
14
  # @return [self]
13
- def each_word filepath
14
- raise NotImplementedError
15
+ # @raise [NotImplementedError] when not overridden by a subclass
16
+ def each_word _filepath
17
+ not_implemented
15
18
  end
16
19
  end
17
20
  end
@@ -19,6 +19,11 @@ module Rambling
19
19
  # @note Use of {https://ruby-doc.org/3.3.0/Marshal.html#method-c-load Marshal.load} is generally
20
20
  # discouraged. Only use this with trusted input.
21
21
  def load filepath
22
+ warn <<~WARN.chomp.tr("\n", ' ')
23
+ WARNING: Marshal.load is being used to deserialize trie data.
24
+ Loading untrusted input via Marshal.load can execute arbitrary code.
25
+ Only load Marshal data from trusted sources.
26
+ WARN
22
27
  ::Marshal.load serializer.load filepath
23
28
  end
24
29
 
@@ -5,12 +5,15 @@ module Rambling
5
5
  module Serializers
6
6
  # Base class for all serializers.
7
7
  class Serializer
8
+ include NotImplemented
9
+
8
10
  # Loads contents from a specified filepath.
9
11
  # @abstract Subclass and override {#load} to parse the desired format.
10
12
  # @param [String] filepath the filepath to load contents from.
11
13
  # @return [TContents] parsed contents from given file.
12
- def load filepath
13
- raise NotImplementedError
14
+ # @raise [NotImplementedError] when not overridden by a subclass
15
+ def load _filepath
16
+ not_implemented
14
17
  end
15
18
 
16
19
  # Dumps contents into a specified filepath.
@@ -18,8 +21,9 @@ module Rambling
18
21
  # @param [TContents] contents the contents to dump into given file.
19
22
  # @param [String] filepath the filepath to dump the contents to.
20
23
  # @return [Numeric] number of bytes written to disk.
21
- def dump contents, filepath
22
- raise NotImplementedError
24
+ # @raise [NotImplementedError] when not overridden by a subclass
25
+ def dump _contents, _filepath
26
+ not_implemented
23
27
  end
24
28
  end
25
29
  end
@@ -17,6 +17,11 @@ module Rambling
17
17
  # @return [Nodes::Node] The deserialized {Nodes::Node Node}.
18
18
  # @see https://ruby-doc.org/3.3.0/exts/psych/Psych.html#method-c-safe_load Psych.safe_load
19
19
  def load filepath
20
+ warn <<~WARN.chomp.tr("\n", ' ')
21
+ WARNING: YAML aliases are enabled.
22
+ Loading untrusted YAML with aliases: true can trigger a billion-laughs memory attack.
23
+ Only load YAML data from trusted sources.
24
+ WARN
20
25
  require 'yaml'
21
26
  ::YAML.safe_load(
22
27
  serializer.load(filepath),
@@ -18,47 +18,47 @@ module Rambling
18
18
  # Unzip contents from specified filepath and load in contents from
19
19
  # unzipped files.
20
20
  # @param [String] filepath the filepath to load contents from.
21
- # @return [TContents] all contents of the unzipped loaded file.
21
+ # @return [Nodes::Node] all contents of the unzipped loaded file.
22
22
  # @see https://github.com/rubyzip/rubyzip#reading-a-zip-file Zip
23
23
  # reading a file
24
24
  def load filepath
25
25
  require 'zip'
26
26
 
27
- ::Zip::File.open filepath do |zip|
28
- entry = zip.entries.first
29
- raise unless entry
27
+ clean_up_tmp_files do |tmp_paths|
28
+ ::Zip::File.open filepath do |zip|
29
+ entry = zip.entries.first
30
+ raise unless entry
30
31
 
31
- entry_name = entry.name
32
- entry_path = path entry_name
33
- entry.extract entry_path
32
+ entry_name = entry.name
33
+ entry_path = path_with_random_prefix entry_name
34
+ tmp_paths << entry_path
34
35
 
35
- serializer = serializers.resolve entry_name
36
- raise unless serializer
36
+ entry.extract ::File.basename(entry_path), destination_directory: tmp_path
37
37
 
38
- serializer.load entry_path
38
+ (serializers.resolve(entry_name) || raise).load entry_path
39
+ end
39
40
  end
40
41
  end
41
42
 
42
43
  # Dumps contents and zips into a specified filepath.
43
44
  # @param [String] contents the contents to dump.
44
45
  # @param [String] filepath the filepath to dump the contents to.
45
- # @return [TContents] number of bytes written to disk.
46
+ # @return [Integer] number of bytes written to disk.
46
47
  # @see https://github.com/rubyzip/rubyzip#basic-zip-archive-creation
47
48
  # Zip archive creation
48
49
  def dump contents, filepath
49
50
  require 'zip'
50
51
 
51
- ::Zip::File.open filepath, ::Zip::File::CREATE do |zip|
52
- filename = ::File.basename filepath, '.zip'
52
+ clean_up_tmp_files do |tmp_paths|
53
+ ::Zip::File.open filepath, create: true do |zip|
54
+ filename = ::File.basename filepath, '.zip'
53
55
 
54
- entry_path = path filename
55
- serializer = serializers.resolve filename
56
+ entry_path = path_with_random_prefix filename
57
+ tmp_paths << entry_path
56
58
 
57
- raise unless serializer
58
-
59
- serializer.dump contents, entry_path
60
-
61
- zip.add filename, entry_path
59
+ (serializers.resolve(filename) || raise).dump contents, entry_path
60
+ zip.add filename, entry_path
61
+ end
62
62
  end
63
63
 
64
64
  ::File.size filepath
@@ -76,10 +76,20 @@ module Rambling
76
76
  properties.tmp_path
77
77
  end
78
78
 
79
- def path filename
79
+ def path_with_random_prefix filename
80
80
  require 'securerandom'
81
81
  ::File.join tmp_path, "#{SecureRandom.uuid}-#{filename}"
82
82
  end
83
+
84
+ def clean_up_tmp_files
85
+ # @type var tmp_paths: Array[String]
86
+ tmp_paths = []
87
+ begin
88
+ yield tmp_paths
89
+ ensure
90
+ tmp_paths.each { |path| ::FileUtils.rm_f path }
91
+ end
92
+ end
83
93
  end
84
94
  end
85
95
  end
@@ -6,12 +6,9 @@ module Rambling
6
6
  module Stringifyable
7
7
  # String representation of the current node, if it is a terminal node.
8
8
  # @return [String] the string representation of the current node.
9
- # @raise [InvalidOperation] if node is not terminal or is root.
9
+ # @raise [InvalidOperation] if the node has a letter and is not terminal.
10
10
  def as_word
11
- if letter && !terminal?
12
- raise Rambling::Trie::InvalidOperation,
13
- 'Cannot represent branch as a word'
14
- end
11
+ raise Rambling::Trie::InvalidOperation, 'Cannot represent branch as a word' if letter && !terminal?
15
12
 
16
13
  to_s
17
14
  end
@@ -3,6 +3,6 @@
3
3
  module Rambling
4
4
  module Trie
5
5
  # Current version of the rambling-trie.
6
- VERSION = '2.5.1'
6
+ VERSION = '2.7.0'
7
7
  end
8
8
  end
data/lib/rambling/trie.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  path = File.join 'rambling', 'trie'
4
4
  %w(
5
- comparable compressible compressor configuration container enumerable inspectable invalid_operation
5
+ comparable compressible compressor configuration container enumerable inspectable invalid_operation not_implemented
6
6
  readers serializers stringifyable nodes version
7
7
  ).each { |file| require File.join(path, file) }
8
8
 
@@ -10,6 +10,9 @@ path = File.join 'rambling', 'trie'
10
10
  module Rambling
11
11
  # Entry point for `rambling-trie` API.
12
12
  module Trie
13
+ PROPERTIES_MUTEX = Mutex.new
14
+ private_constant :PROPERTIES_MUTEX
15
+
13
16
  class << self
14
17
  # Creates a new `Rambling::Trie`. Entry point for the `rambling-trie` API.
15
18
  # @param [String, nil] filepath the file to load the words from.
@@ -21,14 +24,12 @@ module Rambling
21
24
  root = root_builder.call
22
25
 
23
26
  Rambling::Trie::Container.new root, compressor do |container|
24
- # noinspection RubyMismatchedArgumentType
25
27
  if filepath
26
- reader ||= readers.resolve filepath
27
- # noinspection RubyMismatchedArgumentType,RubyNilAnalysis
28
- (reader || raise).each_word(filepath) { |word| container << word }
28
+ input_reader = reader || readers.resolve(filepath) || raise(ArgumentError, "no reader for #{filepath}")
29
+ input_reader.each_word(filepath) { |word| container << word }
29
30
  end
30
31
 
31
- yield container if block_given?
32
+ yield container if block_given? # steep:ignore
32
33
  end
33
34
  end
34
35
 
@@ -37,7 +38,7 @@ module Rambling
37
38
  # Available formats are `yml`, `marshal`, and `zip` versions of all the
38
39
  # previous formats. You can also define your own.
39
40
  # @param [String] filepath the file to load the words from.
40
- # @param [Serializer, nil] serializer the object responsible of loading the trie from disk.
41
+ # @param [Serializer, nil] serializer the object responsible for loading the trie from disk.
41
42
  # @return [Container] the trie just loaded.
42
43
  # @yield [Container] the trie just loaded.
43
44
  # @see Rambling::Trie::Serializers Serializers.
@@ -47,7 +48,7 @@ module Rambling
47
48
  serializer ||= serializers.resolve filepath
48
49
  root = (serializer || raise).load filepath
49
50
  Rambling::Trie::Container.new root, compressor do |container|
50
- yield container if block_given?
51
+ yield container if block_given? # steep:ignore
51
52
  end
52
53
  end
53
54
 
@@ -62,7 +63,6 @@ module Rambling
62
63
  # @see Serializers Serializers.
63
64
  def dump trie, filepath, serializer = nil
64
65
  serializer ||= serializers.resolve filepath
65
- # noinspection RubyNilAnalysis
66
66
  (serializer || raise).dump trie.root, filepath
67
67
  end
68
68
 
@@ -77,7 +77,11 @@ module Rambling
77
77
  private
78
78
 
79
79
  def properties
80
- @properties ||= Rambling::Trie::Configuration::Properties.new
80
+ return @properties if @properties
81
+
82
+ PROPERTIES_MUTEX.synchronize do
83
+ @properties ||= Rambling::Trie::Configuration::Properties.new
84
+ end
81
85
  end
82
86
 
83
87
  def readers
@@ -31,5 +31,5 @@ Gem::Specification.new do |gem|
31
31
  gem.license = 'MIT'
32
32
  gem.version = Rambling::Trie::VERSION
33
33
  gem.platform = Gem::Platform::RUBY
34
- gem.required_ruby_version = '>= 3.1', '< 4'
34
+ gem.required_ruby_version = '>= 3.2', '< 5'
35
35
  end
@@ -0,0 +1,3 @@
1
+ interface _Nilable
2
+ def nil?: -> bool
3
+ end
@@ -1,17 +1,19 @@
1
1
  module Rambling
2
2
  module Trie
3
- module Comparable
4
- def ==: (Nodes::Node) -> bool
3
+ module Comparable[TValue < BasicObject & _Inspect & _Nilable]
4
+ def ==: (Nodes::Node[TValue]) -> bool
5
5
 
6
6
  private
7
7
 
8
8
  # abstract methods
9
9
 
10
- def letter: -> Symbol
10
+ def letter: -> Symbol?
11
+
12
+ def value: -> TValue?
11
13
 
12
14
  def terminal?: -> bool
13
15
 
14
- def children_tree: -> Hash[Symbol, Nodes::Node]
16
+ def children_tree: -> Hash[Symbol, Nodes::Node[TValue]]
15
17
  end
16
18
  end
17
19
  end
@@ -1,6 +1,6 @@
1
1
  module Rambling
2
2
  module Trie
3
- module Compressible
3
+ module Compressible[TValue < BasicObject & _Inspect & _Nilable]
4
4
  def compressible?: -> bool
5
5
 
6
6
  private
@@ -11,7 +11,7 @@ module Rambling
11
11
 
12
12
  def terminal?: -> bool
13
13
 
14
- def children_tree: -> Hash[Symbol, Nodes::Node]
14
+ def children_tree: -> Hash[Symbol, Nodes::Node[TValue]]
15
15
  end
16
16
  end
17
17
  end
@@ -1,17 +1,17 @@
1
1
  module Rambling
2
2
  module Trie
3
- class Compressor
4
- def compress: (Nodes::Node?) -> Nodes::Compressed?
3
+ class Compressor[TValue < BasicObject & _Inspect & _Nilable]
4
+ def compress: (Nodes::Node[TValue]?) -> Nodes::Compressed[TValue]?
5
5
 
6
6
  private
7
7
 
8
- def compress_only_child_and_merge: (Nodes::Node) -> Nodes::Compressed
8
+ def compress_only_child_and_merge: (Nodes::Node[TValue]) -> Nodes::Compressed[TValue]
9
9
 
10
- def merge: (Nodes::Node, Nodes::Node) -> Nodes::Compressed
10
+ def merge: (Nodes::Node[TValue], Nodes::Node[TValue]) -> Nodes::Compressed[TValue]
11
11
 
12
- def compress_children_and_copy: (Nodes::Node) -> Nodes::Compressed
12
+ def compress_children_and_copy: (Nodes::Node[TValue]) -> Nodes::Compressed[TValue]
13
13
 
14
- def compress_children: (Hash[Symbol, Nodes::Node]) -> Hash[Symbol, Nodes::Node]
14
+ def compress_children: (Hash[Symbol, Nodes::Node[TValue]]) -> Hash[Symbol, Nodes::Node[TValue]]
15
15
  end
16
16
  end
17
17
  end
@@ -1,11 +1,11 @@
1
1
  module Rambling
2
2
  module Trie
3
3
  module Configuration
4
- class Properties
4
+ class Properties[TValue < BasicObject & _Inspect & _Nilable]
5
5
  attr_reader readers: ProviderCollection[Readers::Reader]
6
- attr_reader serializers: ProviderCollection[Serializers::Serializer[Nodes::Node]]
7
- attr_accessor compressor: Compressor
8
- attr_accessor root_builder: ^() -> Nodes::Node
6
+ attr_reader serializers: ProviderCollection[Serializers::Serializer[Nodes::Node[TValue]]]
7
+ attr_accessor compressor: Compressor[TValue]
8
+ attr_accessor root_builder: ^() -> Nodes::Node[TValue]
9
9
  attr_accessor tmp_path: String
10
10
 
11
11
  def initialize: -> void
@@ -15,7 +15,7 @@ module Rambling
15
15
  private
16
16
 
17
17
  attr_writer readers: ProviderCollection[Readers::Reader]
18
- attr_writer serializers: ProviderCollection[Serializers::Serializer[Nodes::Node]]
18
+ attr_writer serializers: ProviderCollection[Serializers::Serializer[Nodes::Node[TValue]]]
19
19
 
20
20
  def reset_readers: -> void
21
21
 
@@ -2,10 +2,6 @@ module Rambling
2
2
  module Trie
3
3
  module Configuration
4
4
  class ProviderCollection[TProvider < _Nilable]
5
- interface _Nilable
6
- def nil?: -> bool
7
- end
8
-
9
5
  @providers: Hash[Symbol, TProvider]
10
6
 
11
7
  attr_reader name: Symbol
@@ -1,43 +1,44 @@
1
1
  module Rambling
2
2
  module Trie
3
- class Container
3
+ class Container[TValue < BasicObject & _Inspect & _Nilable]
4
4
  include ::Enumerable[String]
5
5
 
6
- @compressor: Compressor
6
+ @compressor: Compressor[TValue]
7
7
 
8
- attr_reader root: Nodes::Node
8
+ attr_reader root: Nodes::Node[TValue]
9
9
 
10
- def initialize: (Nodes::Node, Compressor) ?{ (Container) -> void } -> void
10
+ def initialize: (Nodes::Node[TValue], Compressor[TValue]) ?{ (Container[TValue]) -> void } -> void
11
11
 
12
- def add: (String) -> Nodes::Node
12
+ def add: (String, ?TValue?) -> Nodes::Node[TValue]
13
13
 
14
- def concat: (Array[String]) -> Array[Nodes::Node]
14
+ def concat: (Array[String], ?Array[TValue?]?) -> Array[Nodes::Node[TValue]]
15
15
 
16
- def compress!: -> Container
16
+ def compress!: -> Container[TValue]
17
17
 
18
- def compress: -> Container
18
+ def compress: -> Container[TValue]
19
19
 
20
- def each: { (String) -> void } -> (Enumerator[String, void] | Enumerable)
20
+ def each: { (String) -> void } -> Container[TValue]
21
+ | -> Enumerator[String, void]
21
22
 
22
- def partial_word?: (String) -> bool
23
+ def partial_word?: (?String) -> bool
23
24
 
24
- def push: (*String) -> Array[Nodes::Node]
25
+ def push: (*String) -> Array[Nodes::Node[TValue]]
25
26
 
26
- def word?: (String) -> bool
27
+ def word?: (?String) -> bool
27
28
 
28
- def scan: (String) -> Array[String]
29
+ def scan: (?String) -> Array[String]
29
30
 
30
31
  def words_within: (String) -> Array[String]
31
32
 
32
33
  def words_within?: (String) -> bool
33
34
 
34
- def ==: (Container) -> bool
35
+ def ==: (Container[TValue]) -> bool
35
36
 
36
- def []: (Symbol) -> Nodes::Node
37
+ def []: (Symbol) -> Nodes::Node[TValue]?
37
38
 
38
- def children: -> Array[Nodes::Node]
39
+ def children: -> Array[Nodes::Node[TValue]]
39
40
 
40
- def children_tree: -> Hash[Symbol, Nodes::Node]
41
+ def children_tree: -> Hash[Symbol, Nodes::Node[TValue]]
41
42
 
42
43
  def compressed?: -> bool
43
44
 
@@ -45,23 +46,23 @@ module Rambling
45
46
 
46
47
  def key?: (Symbol) -> bool
47
48
 
48
- def size: -> Numeric
49
+ def size: -> Integer
49
50
 
50
51
  alias include? word?
51
52
  alias match? partial_word?
52
- alias words? scan
53
+ alias words scan
53
54
  alias << add
54
55
  alias has_key? key?
55
56
  alias has_letter? key?
56
57
 
57
58
  private
58
59
 
59
- attr_reader compressor: Compressor
60
- attr_writer root: Nodes::Node
60
+ attr_reader compressor: Compressor[TValue]
61
+ attr_writer root: Nodes::Node[TValue]
61
62
 
62
- def words_within_root: (String) ?{ (String) -> void } -> Enumerator[String, void]
63
+ def words_within_root: (String) ?{ (String) -> void } -> (Container[TValue] | Enumerator[String, void])
63
64
 
64
- def compress_root: -> Nodes::Compressed
65
+ def compress_root: -> Nodes::Compressed[TValue]
65
66
 
66
67
  def reversed_char_symbols: (String) -> Array[Symbol]
67
68
  end