rphonetic 0.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.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/Cargo.lock +402 -0
  3. data/Cargo.toml +6 -0
  4. data/LICENSE +202 -0
  5. data/NOTICE +17 -0
  6. data/README.md +189 -0
  7. data/ext/rphonetic/Cargo.toml +13 -0
  8. data/ext/rphonetic/extconf.rb +8 -0
  9. data/ext/rphonetic/src/lib.rs +275 -0
  10. data/lib/rphonetic/beider_morse.rb +54 -0
  11. data/lib/rphonetic/caverphone1.rb +15 -0
  12. data/lib/rphonetic/caverphone2.rb +15 -0
  13. data/lib/rphonetic/cologne.rb +15 -0
  14. data/lib/rphonetic/daitch_mokotoff_soundex.rb +35 -0
  15. data/lib/rphonetic/double_metaphone.rb +52 -0
  16. data/lib/rphonetic/encoder.rb +93 -0
  17. data/lib/rphonetic/encoders.rb +19 -0
  18. data/lib/rphonetic/match_rating_approach.rb +18 -0
  19. data/lib/rphonetic/metaphone.rb +17 -0
  20. data/lib/rphonetic/nysiis.rb +19 -0
  21. data/lib/rphonetic/phonex.rb +18 -0
  22. data/lib/rphonetic/refined_soundex.rb +35 -0
  23. data/lib/rphonetic/soundex.rb +38 -0
  24. data/lib/rphonetic/version.rb +6 -0
  25. data/lib/rphonetic.rb +25 -0
  26. data/sig/rphonetic/beider_morse.rbs +14 -0
  27. data/sig/rphonetic/caverphone1.rbs +5 -0
  28. data/sig/rphonetic/caverphone2.rbs +5 -0
  29. data/sig/rphonetic/cologne.rbs +5 -0
  30. data/sig/rphonetic/daitch_mokotoff_soundex.rbs +9 -0
  31. data/sig/rphonetic/double_metaphone.rbs +11 -0
  32. data/sig/rphonetic/encoder.rbs +11 -0
  33. data/sig/rphonetic/match_rating_approach.rbs +5 -0
  34. data/sig/rphonetic/metaphone.rbs +7 -0
  35. data/sig/rphonetic/nysiis.rbs +7 -0
  36. data/sig/rphonetic/phonex.rbs +7 -0
  37. data/sig/rphonetic/refined_soundex.rbs +11 -0
  38. data/sig/rphonetic/soundex.rbs +12 -0
  39. data/sig/rphonetic.rbs +7 -0
  40. metadata +97 -0
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RPhonetic
4
+ # Shared interface for reusable native phonetic encoders.
5
+ # Each instance owns its native encoder. Class convenience methods accept
6
+ # the same keywords as the concrete encoder's constructor.
7
+ #
8
+ # @abstract Instantiate a concrete algorithm, such as DoubleMetaphone.
9
+ class Encoder
10
+ class << self
11
+ # Construct an encoder and encode one name.
12
+ # Reuse an instance when encoding repeatedly, especially with external rules.
13
+ #
14
+ # @param value [String] name to encode
15
+ # @param options [Hash] keyword arguments accepted by the subclass constructor
16
+ # @return [String] the single or primary code, or Beider–Morse expression
17
+ # @see #encode
18
+ def encode(value, **options)
19
+ new(**options).encode(value)
20
+ end
21
+
22
+ # Construct an encoder and encode several names in input order.
23
+ #
24
+ # @param values [Array<String>] names to encode
25
+ # @param options [Hash] keyword arguments accepted by the subclass constructor
26
+ # @return [Array<String>] one result per input name
27
+ # @see #encode_many
28
+ def encode_many(values, **options)
29
+ new(**options).encode_many(values)
30
+ end
31
+ end
32
+
33
+ # Encode one name using this instance's configuration.
34
+ # Strings are transcoded to UTF-8; no transliteration is performed.
35
+ #
36
+ # @param value [String] name to encode
37
+ # @return [String] the single or primary code, or Beider–Morse expression
38
+ # @raise [TypeError] if value is not a String
39
+ # @raise [ArgumentError] if the string contains invalid bytes, or an ASCII-only encoder receives non-ASCII text
40
+ # @raise [EncodingError] if the string cannot be transcoded to UTF-8
41
+ # @raise [RuntimeError] if the upstream encoder panics
42
+ def encode(value)
43
+ @native.encode(text(value))
44
+ end
45
+
46
+ # Encode an array in one native call, preserving input order.
47
+ # Returns an empty array for empty input. Beider–Morse guesses languages
48
+ # for each name. This call holds Ruby's GVL.
49
+ #
50
+ # @param values [Array<String>] names to encode
51
+ # @return [Array<String>] one result per input name
52
+ # @raise [TypeError] if values is not an Array or contains a non-String
53
+ # @raise [ArgumentError, EncodingError, RuntimeError] for the same input or upstream errors as #encode
54
+ # @see #encode
55
+ def encode_many(values)
56
+ raise TypeError, "values must be an Array" unless values.is_a?(Array)
57
+ @native.encode_many(values.map { |value| text(value) })
58
+ end
59
+
60
+ # Compare two names using the upstream algorithm.
61
+ # Match Rating Approach uses its similarity rating. Other encoders compare
62
+ # {#encode} results: Double Metaphone uses primary codes, Daitch–Mokotoff
63
+ # uses nonbranching codes, and Beider–Morse compares entire expressions.
64
+ # This does not check for overlap between alternative codes.
65
+ #
66
+ # @param first [String] first name
67
+ # @param second [String] second name
68
+ # @return [Boolean] whether the algorithm considers the names equivalent
69
+ # @raise [TypeError, ArgumentError, EncodingError, RuntimeError] for the same input or upstream errors as #encode
70
+ def match?(first, second)
71
+ @native.matches(text(first), text(second))
72
+ end
73
+
74
+ private
75
+
76
+ def text(value)
77
+ raise TypeError, "value must be a String" unless value.is_a?(String)
78
+ raise ArgumentError, "value contains invalid bytes" unless value.valid_encoding?
79
+ value.encode(Encoding::UTF_8)
80
+ end
81
+
82
+ def length_option(value, name, unlimited: false)
83
+ return nil if unlimited && value.nil?
84
+ raise ArgumentError, "#{name} must be a positive Integer#{' or nil' if unlimited}" unless value.is_a?(Integer) && value.positive?
85
+ value
86
+ end
87
+
88
+ def boolean(value, name)
89
+ raise ArgumentError, "#{name} must be true or false" unless value == true || value == false
90
+ value
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "encoder"
4
+ require_relative "caverphone1"
5
+ require_relative "caverphone2"
6
+ require_relative "cologne"
7
+ require_relative "match_rating_approach"
8
+ require_relative "metaphone"
9
+ require_relative "double_metaphone"
10
+ require_relative "nysiis"
11
+ require_relative "phonex"
12
+ require_relative "soundex"
13
+ require_relative "refined_soundex"
14
+ require_relative "daitch_mokotoff_soundex"
15
+ require_relative "beider_morse"
16
+
17
+ module RPhonetic
18
+ private_constant :NativeEncoder
19
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RPhonetic
4
+ # Match Rating Approach encodes names in at most six characters.
5
+ # Unlike the other encoders, {#match?} applies the algorithm's similarity
6
+ # rating rather than requiring identical codes.
7
+ #
8
+ # @example
9
+ # encoder = RPhonetic::MatchRatingApproach.new
10
+ # encoder.encode("Smith") # => "SMTH"
11
+ # encoder.match?("Smith", "Smyth") # => true
12
+ class MatchRatingApproach < Encoder
13
+ # Create a MatchRatingApproach encoder. This algorithm has no configuration options.
14
+ def initialize
15
+ @native = NativeEncoder.simple("MatchRatingApproach")
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RPhonetic
4
+ # Metaphone encodes English pronunciation as a single phonetic code.
5
+ #
6
+ # @example
7
+ # RPhonetic::Metaphone.encode("Joanne") # => "JN"
8
+ class Metaphone < Encoder
9
+ # Create a Metaphone encoder.
10
+ #
11
+ # @param max_length [Integer, nil] positive maximum code length; nil removes the limit
12
+ # @raise [ArgumentError] if max_length is neither positive nor nil
13
+ def initialize(max_length: 4)
14
+ @native = NativeEncoder.metaphone(length_option(max_length, :max_length, unlimited: true))
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RPhonetic
4
+ # New York State Identification and Intelligence System phonetic encoding.
5
+ # Strict mode limits the result to six characters.
6
+ #
7
+ # @example
8
+ # RPhonetic::Nysiis.encode("WESTERLUND") # => "WASTAR"
9
+ # RPhonetic::Nysiis.encode("WESTERLUND", strict: false) # => "WASTARLAD"
10
+ class Nysiis < Encoder
11
+ # Create a NYSIIS encoder.
12
+ #
13
+ # @param strict [Boolean] whether to truncate the code to six characters
14
+ # @raise [ArgumentError] if strict is not true or false
15
+ def initialize(strict: true)
16
+ @native = NativeEncoder.nysiis(boolean(strict, :strict))
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RPhonetic
4
+ # Phonex combines Soundex-style codes with pronunciation normalization.
5
+ # Codes are padded with zeroes to the configured length.
6
+ #
7
+ # @example
8
+ # RPhonetic::Phonex.encode("William") # => "W450"
9
+ class Phonex < Encoder
10
+ # Create a Phonex encoder.
11
+ #
12
+ # @param max_length [Integer] positive code length; nil is not supported
13
+ # @raise [ArgumentError] if max_length is not a positive Integer
14
+ def initialize(max_length: 4)
15
+ @native = NativeEncoder.phonex(length_option(max_length, :max_length))
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RPhonetic
4
+ # Refined Soundex uses more consonant groups than Soundex and returns
5
+ # variable-length codes. Input names must be ASCII; non-ASCII names raise
6
+ # ArgumentError.
7
+ #
8
+ # @example
9
+ # RPhonetic::RefinedSoundex.encode("jumped") # => "J408106"
10
+ class RefinedSoundex < Encoder
11
+ # Default code mapping for the letters A–Z.
12
+ # @return [String]
13
+ DEFAULT_MAPPING = "01360240043788015936020505"
14
+
15
+ # Create a Refined Soundex encoder.
16
+ #
17
+ # @param mapping [String] exactly 26 characters, one per letter A–Z
18
+ # @raise [TypeError] if mapping is not a String
19
+ # @raise [ArgumentError] if the mapping is invalid
20
+ def initialize(mapping: DEFAULT_MAPPING)
21
+ @native = NativeEncoder.refined_soundex(text(mapping))
22
+ end
23
+
24
+ # Count positions at which the two encoded names have the same character.
25
+ # This is a similarity count, not an edit distance.
26
+ #
27
+ # @param first [String] first ASCII name
28
+ # @param second [String] second ASCII name
29
+ # @return [Integer] number of equal positions, from zero through the shorter code's length
30
+ # @raise [TypeError, ArgumentError, EncodingError, RuntimeError] for the same input or upstream errors as #encode
31
+ def difference(first, second)
32
+ @native.difference(text(first), text(second))
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RPhonetic
4
+ # Soundex encodes a name as its initial letter followed by three digits.
5
+ # Input names must be ASCII; non-ASCII names raise ArgumentError.
6
+ #
7
+ # @example
8
+ # RPhonetic::Soundex.encode("Robert") # => "R163"
9
+ class Soundex < Encoder
10
+ # Default code mapping for the letters A–Z.
11
+ # @return [String]
12
+ DEFAULT_MAPPING = "01230120022455012623010202"
13
+ # Genealogical mapping; hyphens mark letters that are ignored.
14
+ # @return [String]
15
+ GENEALOGY_MAPPING = "-123-12--22455-12623-1-2-2"
16
+
17
+ # Create a Soundex encoder with a standard or custom A–Z mapping.
18
+ #
19
+ # @param mapping [String] exactly 26 characters, one per letter A–Z
20
+ # @param special_case_h_w [Boolean] whether H and W leave the preceding code unchanged
21
+ # @raise [TypeError] if mapping is not a String
22
+ # @raise [ArgumentError] if the mapping is invalid or special_case_h_w is not a Boolean
23
+ def initialize(mapping: DEFAULT_MAPPING, special_case_h_w: true)
24
+ @native = NativeEncoder.soundex(text(mapping), boolean(special_case_h_w, :special_case_h_w))
25
+ end
26
+
27
+ # Count positions at which the two encoded names have the same character.
28
+ # This is a similarity count, not an edit distance.
29
+ #
30
+ # @param first [String] first ASCII name
31
+ # @param second [String] second ASCII name
32
+ # @return [Integer] number of equal positions, from zero through the shorter code's length
33
+ # @raise [TypeError, ArgumentError, EncodingError, RuntimeError] for the same input or upstream errors as #encode
34
+ def difference(first, second)
35
+ @native.difference(text(first), text(second))
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RPhonetic
4
+ # Ruby gem version, independent of the wrapped Rust crate's version.
5
+ VERSION = "0.1.0"
6
+ end
data/lib/rphonetic.rb ADDED
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbconfig"
4
+ require_relative "rphonetic/version"
5
+
6
+ # Ruby bindings for the rphonetic Rust phonetic algorithms.
7
+ # Encoders are reusable and accept valid Strings, transcoded to UTF-8.
8
+ module RPhonetic
9
+ # Raised when caller-supplied rules cannot be loaded or parsed.
10
+ class RuleError < ArgumentError; end
11
+ end
12
+
13
+ series = RUBY_VERSION.split(".").first(2).join(".")
14
+ binary = File.expand_path("rphonetic/#{series}/rphonetic_ext.#{RbConfig::CONFIG.fetch('DLEXT')}", __dir__)
15
+ local_binary = File.expand_path("rphonetic/rphonetic_ext.#{RbConfig::CONFIG.fetch('DLEXT')}", __dir__)
16
+ if File.file?(local_binary)
17
+ # A fresh development build takes precedence over staged release artifacts.
18
+ require local_binary
19
+ elsif File.file?(binary)
20
+ require binary
21
+ else
22
+ require "rphonetic/rphonetic_ext"
23
+ end
24
+
25
+ require_relative "rphonetic/encoders"
@@ -0,0 +1,14 @@
1
+ module RPhonetic
2
+ class BeiderMorse < Encoder
3
+ type name_type = :generic | :ashkenazi | :sephardic | "generic" | "ashkenazi" | "sephardic"
4
+ type rule_type = :approx | :exact | "approx" | "exact"
5
+ type languages = Array[String] | :any | nil
6
+
7
+ def initialize: (rules_path: String, ?name_type: name_type, ?rule_type: rule_type, ?concat: bool, ?max_phonemes: Integer) -> void
8
+ def self.encode: (String value, rules_path: String, ?name_type: name_type, ?rule_type: rule_type, ?concat: bool, ?max_phonemes: Integer) -> String
9
+ def self.encode_many: (Array[String] values, rules_path: String, ?name_type: name_type, ?rule_type: rule_type, ?concat: bool, ?max_phonemes: Integer) -> Array[String]
10
+
11
+ # Only instance encoding accepts explicit languages.
12
+ def encode: (String value, ?languages: languages) -> String
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ module RPhonetic
2
+ class Caverphone1 < Encoder
3
+ def initialize: () -> void
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module RPhonetic
2
+ class Caverphone2 < Encoder
3
+ def initialize: () -> void
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module RPhonetic
2
+ class Cologne < Encoder
3
+ def initialize: () -> void
4
+ end
5
+ end
@@ -0,0 +1,9 @@
1
+ module RPhonetic
2
+ class DaitchMokotoffSoundex < Encoder
3
+ def initialize: (rules: String, ?ascii_folding: bool) -> void
4
+ def self.encode: (String value, rules: String, ?ascii_folding: bool) -> String
5
+ def self.encode_many: (Array[String] values, rules: String, ?ascii_folding: bool) -> Array[String]
6
+
7
+ def codes: (String value) -> Array[String]
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ module RPhonetic
2
+ class DoubleMetaphone < Encoder
3
+ def initialize: (?max_length: Integer?) -> void
4
+ def self.encode: (String value, ?max_length: Integer?) -> String
5
+ def self.encode_many: (Array[String] values, ?max_length: Integer?) -> Array[String]
6
+
7
+ def self.codes: (String value, ?max_length: Integer?) -> [String, String]
8
+ def codes: (String value) -> [String, String]
9
+ def alternate: (String value) -> String
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module RPhonetic
2
+ # Abstract shared interface; instantiate a concrete algorithm.
3
+ class Encoder
4
+ def self.encode: (String value) -> String
5
+ def self.encode_many: (Array[String] values) -> Array[String]
6
+
7
+ def encode: (String value) -> String
8
+ def encode_many: (Array[String] values) -> Array[String]
9
+ def match?: (String first, String second) -> bool
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ module RPhonetic
2
+ class MatchRatingApproach < Encoder
3
+ def initialize: () -> void
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ module RPhonetic
2
+ class Metaphone < Encoder
3
+ def initialize: (?max_length: Integer?) -> void
4
+ def self.encode: (String value, ?max_length: Integer?) -> String
5
+ def self.encode_many: (Array[String] values, ?max_length: Integer?) -> Array[String]
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module RPhonetic
2
+ class Nysiis < Encoder
3
+ def initialize: (?strict: bool) -> void
4
+ def self.encode: (String value, ?strict: bool) -> String
5
+ def self.encode_many: (Array[String] values, ?strict: bool) -> Array[String]
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module RPhonetic
2
+ class Phonex < Encoder
3
+ def initialize: (?max_length: Integer) -> void
4
+ def self.encode: (String value, ?max_length: Integer) -> String
5
+ def self.encode_many: (Array[String] values, ?max_length: Integer) -> Array[String]
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ module RPhonetic
2
+ class RefinedSoundex < Encoder
3
+ DEFAULT_MAPPING: String
4
+
5
+ def initialize: (?mapping: String) -> void
6
+ def self.encode: (String value, ?mapping: String) -> String
7
+ def self.encode_many: (Array[String] values, ?mapping: String) -> Array[String]
8
+
9
+ def difference: (String first, String second) -> Integer
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ module RPhonetic
2
+ class Soundex < Encoder
3
+ DEFAULT_MAPPING: String
4
+ GENEALOGY_MAPPING: String
5
+
6
+ def initialize: (?mapping: String, ?special_case_h_w: bool) -> void
7
+ def self.encode: (String value, ?mapping: String, ?special_case_h_w: bool) -> String
8
+ def self.encode_many: (Array[String] values, ?mapping: String, ?special_case_h_w: bool) -> Array[String]
9
+
10
+ def difference: (String first, String second) -> Integer
11
+ end
12
+ end
data/sig/rphonetic.rbs ADDED
@@ -0,0 +1,7 @@
1
+ module RPhonetic
2
+ VERSION: String
3
+
4
+ # Errors while loading or parsing caller-supplied phonetic rules.
5
+ class RuleError < ArgumentError
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rphonetic
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - tzaid
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rb_sys
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '0.9'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '0.9'
26
+ description: Reusable encoders for all twelve rphonetic algorithms, with caller-supplied
27
+ rules and precompiled native gem support.
28
+ executables: []
29
+ extensions:
30
+ - ext/rphonetic/extconf.rb
31
+ extra_rdoc_files: []
32
+ files:
33
+ - Cargo.lock
34
+ - Cargo.toml
35
+ - LICENSE
36
+ - NOTICE
37
+ - README.md
38
+ - ext/rphonetic/Cargo.toml
39
+ - ext/rphonetic/extconf.rb
40
+ - ext/rphonetic/src/lib.rs
41
+ - lib/rphonetic.rb
42
+ - lib/rphonetic/beider_morse.rb
43
+ - lib/rphonetic/caverphone1.rb
44
+ - lib/rphonetic/caverphone2.rb
45
+ - lib/rphonetic/cologne.rb
46
+ - lib/rphonetic/daitch_mokotoff_soundex.rb
47
+ - lib/rphonetic/double_metaphone.rb
48
+ - lib/rphonetic/encoder.rb
49
+ - lib/rphonetic/encoders.rb
50
+ - lib/rphonetic/match_rating_approach.rb
51
+ - lib/rphonetic/metaphone.rb
52
+ - lib/rphonetic/nysiis.rb
53
+ - lib/rphonetic/phonex.rb
54
+ - lib/rphonetic/refined_soundex.rb
55
+ - lib/rphonetic/soundex.rb
56
+ - lib/rphonetic/version.rb
57
+ - sig/rphonetic.rbs
58
+ - sig/rphonetic/beider_morse.rbs
59
+ - sig/rphonetic/caverphone1.rbs
60
+ - sig/rphonetic/caverphone2.rbs
61
+ - sig/rphonetic/cologne.rbs
62
+ - sig/rphonetic/daitch_mokotoff_soundex.rbs
63
+ - sig/rphonetic/double_metaphone.rbs
64
+ - sig/rphonetic/encoder.rbs
65
+ - sig/rphonetic/match_rating_approach.rbs
66
+ - sig/rphonetic/metaphone.rbs
67
+ - sig/rphonetic/nysiis.rbs
68
+ - sig/rphonetic/phonex.rbs
69
+ - sig/rphonetic/refined_soundex.rbs
70
+ - sig/rphonetic/soundex.rbs
71
+ homepage: https://github.com/tzaid/rphonetic-ruby
72
+ licenses:
73
+ - Apache-2.0
74
+ metadata:
75
+ source_code_uri: https://github.com/tzaid/rphonetic-ruby
76
+ bug_tracker_uri: https://github.com/tzaid/rphonetic-ruby/issues
77
+ rdoc_options: []
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '3.4'
85
+ - - "<"
86
+ - !ruby/object:Gem::Version
87
+ version: '4.1'
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ requirements: []
94
+ rubygems_version: 3.6.9
95
+ specification_version: 4
96
+ summary: Ruby bindings for the rphonetic Rust phonetic algorithms
97
+ test_files: []