LittleWeasel 3.0.4 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (152) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +3 -0
  3. data/.reek.yml +17 -0
  4. data/.rspec +4 -2
  5. data/.rubocop.yml +187 -0
  6. data/.ruby-version +1 -1
  7. data/.yardopts +2 -0
  8. data/Gemfile +3 -1
  9. data/LittleWeasel.gemspec +31 -18
  10. data/README.md +408 -42
  11. data/Rakefile +296 -3
  12. data/lib/LittleWeasel.rb +5 -184
  13. data/lib/LittleWeasel/block_results.rb +81 -0
  14. data/lib/LittleWeasel/configure.rb +98 -0
  15. data/lib/LittleWeasel/dictionary.rb +125 -0
  16. data/lib/LittleWeasel/dictionary_key.rb +48 -0
  17. data/lib/LittleWeasel/dictionary_manager.rb +85 -0
  18. data/lib/LittleWeasel/errors/dictionary_file_already_loaded_error.rb +9 -0
  19. data/lib/LittleWeasel/errors/dictionary_file_empty_error.rb +8 -0
  20. data/lib/LittleWeasel/errors/dictionary_file_not_found_error.rb +8 -0
  21. data/lib/LittleWeasel/errors/dictionary_file_too_large_error.rb +16 -0
  22. data/lib/LittleWeasel/errors/language_required_error.rb +8 -0
  23. data/lib/LittleWeasel/errors/must_override_error.rb +8 -0
  24. data/lib/LittleWeasel/filters/en_us/currency_filter.rb +19 -0
  25. data/lib/LittleWeasel/filters/en_us/numeric_filter.rb +19 -0
  26. data/lib/LittleWeasel/filters/en_us/single_character_word_filter.rb +21 -0
  27. data/lib/LittleWeasel/filters/word_filter.rb +59 -0
  28. data/lib/LittleWeasel/filters/word_filter_managable.rb +80 -0
  29. data/lib/LittleWeasel/filters/word_filter_validatable.rb +31 -0
  30. data/lib/LittleWeasel/filters/word_filterable.rb +19 -0
  31. data/lib/LittleWeasel/filters/word_filters_validatable.rb +29 -0
  32. data/lib/LittleWeasel/metadata/dictionary_metadata.rb +145 -0
  33. data/lib/LittleWeasel/metadata/invalid_words_metadata.rb +134 -0
  34. data/lib/LittleWeasel/metadata/invalid_words_service_results.rb +45 -0
  35. data/lib/LittleWeasel/metadata/metadata_observable_validatable.rb +22 -0
  36. data/lib/LittleWeasel/metadata/metadata_observerable.rb +90 -0
  37. data/lib/LittleWeasel/metadata/metadatable.rb +136 -0
  38. data/lib/LittleWeasel/modules/class_name_to_symbol.rb +26 -0
  39. data/lib/LittleWeasel/modules/configurable.rb +26 -0
  40. data/lib/LittleWeasel/modules/deep_dup.rb +11 -0
  41. data/lib/LittleWeasel/modules/dictionary_cache_keys.rb +34 -0
  42. data/lib/LittleWeasel/modules/dictionary_cache_servicable.rb +26 -0
  43. data/lib/LittleWeasel/modules/dictionary_cache_validatable.rb +20 -0
  44. data/lib/LittleWeasel/modules/dictionary_creator_servicable.rb +27 -0
  45. data/lib/LittleWeasel/modules/dictionary_file_loader.rb +67 -0
  46. data/lib/LittleWeasel/modules/dictionary_key_validatable.rb +19 -0
  47. data/lib/LittleWeasel/modules/dictionary_keyable.rb +24 -0
  48. data/lib/LittleWeasel/modules/dictionary_loader_servicable.rb +27 -0
  49. data/lib/LittleWeasel/modules/dictionary_metadata_servicable.rb +29 -0
  50. data/lib/LittleWeasel/modules/dictionary_metadata_validatable.rb +17 -0
  51. data/lib/LittleWeasel/modules/dictionary_sourceable.rb +26 -0
  52. data/lib/LittleWeasel/modules/dictionary_validatable.rb +30 -0
  53. data/lib/LittleWeasel/modules/language.rb +23 -0
  54. data/lib/LittleWeasel/modules/language_validatable.rb +16 -0
  55. data/lib/LittleWeasel/modules/locale.rb +40 -0
  56. data/lib/LittleWeasel/modules/order_validatable.rb +18 -0
  57. data/lib/LittleWeasel/modules/orderable.rb +17 -0
  58. data/lib/LittleWeasel/modules/region.rb +23 -0
  59. data/lib/LittleWeasel/modules/region_validatable.rb +16 -0
  60. data/lib/LittleWeasel/modules/tag_validatable.rb +16 -0
  61. data/lib/LittleWeasel/modules/taggable.rb +31 -0
  62. data/lib/LittleWeasel/modules/word_results_validatable.rb +28 -0
  63. data/lib/LittleWeasel/preprocessors/en_us/capitalize_preprocessor.rb +22 -0
  64. data/lib/LittleWeasel/preprocessors/preprocessed_word.rb +28 -0
  65. data/lib/LittleWeasel/preprocessors/preprocessed_word_validatable.rb +55 -0
  66. data/lib/LittleWeasel/preprocessors/preprocessed_words.rb +55 -0
  67. data/lib/LittleWeasel/preprocessors/preprocessed_words_validatable.rb +27 -0
  68. data/lib/LittleWeasel/preprocessors/word_preprocessable.rb +19 -0
  69. data/lib/LittleWeasel/preprocessors/word_preprocessor.rb +122 -0
  70. data/lib/LittleWeasel/preprocessors/word_preprocessor_managable.rb +114 -0
  71. data/lib/LittleWeasel/preprocessors/word_preprocessor_validatable.rb +40 -0
  72. data/lib/LittleWeasel/preprocessors/word_preprocessors_validatable.rb +24 -0
  73. data/lib/LittleWeasel/services/dictionary_cache_service.rb +262 -0
  74. data/lib/LittleWeasel/services/dictionary_creator_service.rb +94 -0
  75. data/lib/LittleWeasel/services/dictionary_file_loader_service.rb +37 -0
  76. data/lib/LittleWeasel/services/dictionary_killer_service.rb +35 -0
  77. data/lib/LittleWeasel/services/dictionary_loader_service.rb +59 -0
  78. data/lib/LittleWeasel/services/dictionary_metadata_service.rb +114 -0
  79. data/lib/LittleWeasel/services/invalid_words_service.rb +59 -0
  80. data/lib/LittleWeasel/version.rb +3 -1
  81. data/lib/LittleWeasel/word_results.rb +146 -0
  82. data/spec/factories/dictionary.rb +43 -0
  83. data/spec/factories/dictionary_cache_service.rb +95 -0
  84. data/spec/factories/dictionary_creator_service.rb +16 -0
  85. data/spec/factories/dictionary_file_loader_service.rb +13 -0
  86. data/spec/factories/dictionary_hash.rb +39 -0
  87. data/spec/factories/dictionary_key.rb +14 -0
  88. data/spec/factories/dictionary_killer_service.rb +14 -0
  89. data/spec/factories/dictionary_loader_service.rb +14 -0
  90. data/spec/factories/dictionary_manager.rb +10 -0
  91. data/spec/factories/dictionary_metadata.rb +16 -0
  92. data/spec/factories/dictionary_metadata_service.rb +16 -0
  93. data/spec/factories/numeric_filter.rb +12 -0
  94. data/spec/factories/preprocessed_word.rb +16 -0
  95. data/spec/factories/preprocessed_words.rb +41 -0
  96. data/spec/factories/single_character_word_filter.rb +12 -0
  97. data/spec/factories/word_results.rb +16 -0
  98. data/spec/lib/LittleWeasel/block_results_spec.rb +248 -0
  99. data/spec/lib/LittleWeasel/configure_spec.rb +74 -0
  100. data/spec/lib/LittleWeasel/dictionary_key_spec.rb +118 -0
  101. data/spec/lib/LittleWeasel/dictionary_manager_spec.rb +116 -0
  102. data/spec/lib/LittleWeasel/dictionary_spec.rb +289 -0
  103. data/spec/lib/LittleWeasel/filters/en_us/currency_filter_spec.rb +80 -0
  104. data/spec/lib/LittleWeasel/filters/en_us/numeric_filter_spec.rb +66 -0
  105. data/spec/lib/LittleWeasel/filters/en_us/single_character_word_filter_spec.rb +58 -0
  106. data/spec/lib/LittleWeasel/filters/word_filter_managable_spec.rb +180 -0
  107. data/spec/lib/LittleWeasel/filters/word_filter_spec.rb +151 -0
  108. data/spec/lib/LittleWeasel/filters/word_filter_validatable_spec.rb +94 -0
  109. data/spec/lib/LittleWeasel/filters/word_filters_validatable_spec.rb +48 -0
  110. data/spec/lib/LittleWeasel/integraton_tests/dictionary_integration_spec.rb +201 -0
  111. data/spec/lib/LittleWeasel/metadata/dictionary_creator_servicable_spec.rb +54 -0
  112. data/spec/lib/LittleWeasel/metadata/dictionary_metadata_spec.rb +209 -0
  113. data/spec/lib/LittleWeasel/metadata/invalid_words_metadata_spec.rb +155 -0
  114. data/spec/lib/LittleWeasel/metadata/metadata_observerable_spec.rb +31 -0
  115. data/spec/lib/LittleWeasel/metadata/metadatable_spec.rb +35 -0
  116. data/spec/lib/LittleWeasel/modules/class_name_to_symbol_spec.rb +21 -0
  117. data/spec/lib/LittleWeasel/modules/dictionary_file_loader_spec.rb +125 -0
  118. data/spec/lib/LittleWeasel/modules/dictionary_sourceable_spec.rb +44 -0
  119. data/spec/lib/LittleWeasel/modules/language_spec.rb +52 -0
  120. data/spec/lib/LittleWeasel/modules/locale_spec.rb +140 -0
  121. data/spec/lib/LittleWeasel/modules/region_spec.rb +52 -0
  122. data/spec/lib/LittleWeasel/preprocessors/en_us/capitalize_preprocessor_spec.rb +34 -0
  123. data/spec/lib/LittleWeasel/preprocessors/preprocessed_word_spec.rb +105 -0
  124. data/spec/lib/LittleWeasel/preprocessors/preprocessed_word_validatable_spec.rb +143 -0
  125. data/spec/lib/LittleWeasel/preprocessors/preprocessed_words_spec.rb +77 -0
  126. data/spec/lib/LittleWeasel/preprocessors/preprocessed_words_validatable_spec.rb +58 -0
  127. data/spec/lib/LittleWeasel/preprocessors/word_preprocessor_managable_spec.rb +216 -0
  128. data/spec/lib/LittleWeasel/preprocessors/word_preprocessor_spec.rb +175 -0
  129. data/spec/lib/LittleWeasel/preprocessors/word_preprocessor_validatable_spec.rb +109 -0
  130. data/spec/lib/LittleWeasel/preprocessors/word_preprocessors_validatable_spec.rb +49 -0
  131. data/spec/lib/LittleWeasel/services/dictionary_cache_service_spec.rb +444 -0
  132. data/spec/lib/LittleWeasel/services/dictionary_creator_service_spec.rb +119 -0
  133. data/spec/lib/LittleWeasel/services/dictionary_file_loader_service_spec.rb +71 -0
  134. data/spec/lib/LittleWeasel/services/dictionary_loader_service_spec.rb +50 -0
  135. data/spec/lib/LittleWeasel/services/dictionary_metadata_service_spec.rb +279 -0
  136. data/spec/lib/LittleWeasel/word_results_spec.rb +275 -0
  137. data/spec/lib/LittleWeasel/workflow/workflow_spec.rb +20 -0
  138. data/spec/spec_helper.rb +117 -6
  139. data/spec/support/factory_bot.rb +15 -0
  140. data/spec/support/file_helpers.rb +32 -0
  141. data/spec/support/files/empty-dictionary.txt +0 -0
  142. data/{lib/dictionary → spec/support/files/en-US-big.txt} +262156 -31488
  143. data/spec/support/files/en-US-tagged.txt +26 -0
  144. data/spec/support/files/en-US.txt +26 -0
  145. data/spec/support/files/en.txt +26 -0
  146. data/spec/support/files/es-ES.txt +27 -0
  147. data/spec/support/files/es.txt +27 -0
  148. data/spec/support/general_helpers.rb +68 -0
  149. data/spec/support/shared_contexts.rb +108 -0
  150. data/spec/support/shared_examples.rb +105 -0
  151. metadata +408 -65
  152. data/spec/checker/checker_spec.rb +0 -286
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/module/delegation'
4
+ require_relative 'word_results'
5
+
6
+ module LittleWeasel
7
+ # This class represents the results of gathering information about a word
8
+ # block (group of words).
9
+ class BlockResults
10
+ # :reek:Attribute - Ignored, it doesn't make sense to create a formal setter method.
11
+ attr_reader :original_word_block, :word_results
12
+
13
+ def initialize(original_word_block:)
14
+ self.original_word_block = original_word_block
15
+ self.word_results = []
16
+ end
17
+
18
+ def <<(word_result)
19
+ unless word_result.is_a? WordResults
20
+ raise ArgumentError, "Argument word_result is not a WordResults object: #{word_result.class}"
21
+ end
22
+
23
+ word_results << word_result
24
+ end
25
+
26
+ # Calls #success? on all WordResults objects. Returns true if all
27
+ # WordResults return true; false is returned otherwise.
28
+ def success?
29
+ return false unless word_results.present?
30
+
31
+ word_results.all?(&:success?)
32
+ end
33
+
34
+ # Returns true if all WordResults object words are valid (#word_valid?);
35
+ # false otherwise.
36
+ def words_valid?
37
+ return false unless word_results.present?
38
+
39
+ word_results.all?(&:word_valid?)
40
+ end
41
+
42
+ # Returns true if all WordResults object words have filter matches (#filters_match?);
43
+ # false otherwise.
44
+ def filters_match?
45
+ return false unless word_results.present?
46
+
47
+ word_results.all?(&:filter_match?)
48
+ end
49
+
50
+ # Returns true if all WordResults object words have been preprocessed (#preprocessed_words?);
51
+ # false otherwise.
52
+ def preprocessed_words?
53
+ return false unless word_results.present?
54
+
55
+ word_results.all?(&:preprocessed_word?)
56
+ end
57
+
58
+ # Returns an Array of the results of calling
59
+ # #preprocessed_word_or_original_word on all WordResults objects.
60
+
61
+ # Calls #preprocessed_word_or_original_word on all WordResults objects.
62
+ # An Array of the results is returned.
63
+ def preprocessed_words_or_original_words
64
+ return [] unless word_results.present?
65
+
66
+ word_results.map(&:preprocessed_word_or_original_word)
67
+ end
68
+
69
+ # Returns true if all WordResults object words have been cached (#words_cached?);
70
+ # false otherwise.
71
+ def words_cached?
72
+ return false unless word_results.present?
73
+
74
+ word_results.all?(&:word_cached?)
75
+ end
76
+
77
+ private
78
+
79
+ attr_writer :original_word_block, :word_results
80
+ end
81
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This is the configuration for LittleWeasel.
4
+ module LittleWeasel
5
+ class << self
6
+ attr_reader :configuration
7
+
8
+ # Returns the application configuration object.
9
+ #
10
+ # @return [Configuration] the application Configuration object.
11
+ def configure
12
+ self.configuration ||= Configuration.new
13
+ yield(configuration)
14
+ end
15
+
16
+ private
17
+
18
+ attr_writer :configuration
19
+ end
20
+
21
+ # This class encapsulates the configuration properties for this gem and
22
+ # provides methods and attributes that allow for management of the same.
23
+ #
24
+ # attr_reader :max_dictionary_file_megabytes, :max_invalid_words_bytesize, :metadata_observers
25
+ class Configuration
26
+ attr_reader :max_dictionary_file_megabytes,
27
+ :max_invalid_words_bytesize, :metadata_observers, :word_block_regex
28
+
29
+ # The constructor; calls {#reset}.
30
+ def initialize
31
+ reset
32
+ end
33
+
34
+ # Resets the configuration settings to their default values.
35
+ #
36
+ # @return [void]
37
+ def reset
38
+ @max_dictionary_file_megabytes = 5
39
+ @max_invalid_words_bytesize = 25_000
40
+ @metadata_observers = [
41
+ LittleWeasel::Metadata::InvalidWordsMetadata
42
+ ]
43
+ # TODO: Is this the correct regex to use, or is there something better?
44
+ # @word_block_regex = /\s+(?=(?:[^"]*"[^"]*")*[^"]*$)/
45
+ # @word_block_regex = /(?:(?:[\-A-Za-z0-9]|\d(?!\d|\b))+(?:'[\-A-Za-z0-9]+)?)/
46
+ # @word_block_regex = /(?:(?:[\-a-z0-9]|\d(?!\d|\b))+(?:'[\-a-z0-9]+)?)/i
47
+ @word_block_regex = /[[[:word:]]'-]+/
48
+ end
49
+
50
+ # Returns the maximum consumable dictionary size in bytes. Dictionaries
51
+ # larger than {#max_dictionary_file_bytes} will raise an error.
52
+ #
53
+ # The default is 5 megabytes.
54
+ #
55
+ # @return [Integer] the maximum number of bytes for a dictionary.
56
+ def max_dictionary_file_bytes
57
+ @max_dictionary_file_megabytes * 1_000_000
58
+ end
59
+
60
+ # If {#max_invalid_words_bytesize} is > 0, true will be returned; false
61
+ # otherwise.
62
+ #
63
+ # @return [true, false] based on {#max_invalid_words_bytesize}.
64
+ def max_invalid_words_bytesize?
65
+ max_invalid_words_bytesize.positive?
66
+ end
67
+
68
+ # rubocop: disable Style/TrivialAccessors
69
+ def max_dictionary_file_megabytes=(value)
70
+ @max_dictionary_file_megabytes = value
71
+ end
72
+
73
+ # Sets the maximum cache size (in bytes) for invalid words. If
74
+ # less than or equal to 0, invalid words will NOT be cached.
75
+ #
76
+ # If greater than 0, invalid words will be cached up to and including
77
+ # {#max_invalid_words_bytesize} bytes.
78
+ #
79
+ # @see #max_invalid_words_bytesize?
80
+ def max_invalid_words_bytesize=(value)
81
+ value = 0 if value.negative?
82
+ @max_invalid_words_bytesize = value
83
+ end
84
+
85
+ def metadata_observers=(value)
86
+ raise ArgumentError, "Argument value is not an Array: #{value.class}" unless value.is_a? Array
87
+
88
+ # TODO: Limit the amount of observer classes, exploits?
89
+
90
+ @metadata_observers = value
91
+ end
92
+
93
+ def word_block_regex=(value)
94
+ @word_block_regex = value
95
+ end
96
+ # rubocop: enable Style/TrivialAccessors
97
+ end
98
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'block_results'
4
+ require_relative 'filters/word_filter_managable'
5
+ require_relative 'metadata/dictionary_metadata'
6
+ require_relative 'modules/configurable'
7
+ require_relative 'modules/dictionary_cache_servicable'
8
+ require_relative 'modules/dictionary_keyable'
9
+ require_relative 'modules/dictionary_metadata_servicable'
10
+ require_relative 'preprocessors/word_preprocessor_managable'
11
+ require_relative 'word_results'
12
+
13
+ module LittleWeasel
14
+ class Dictionary
15
+ include Filters::WordFilterManagable
16
+ include Modules::Configurable
17
+ include Modules::DictionaryCacheServicable
18
+ include Modules::DictionaryKeyable
19
+ include Modules::DictionaryMetadataServicable
20
+ include Preprocessors::WordPreprocessorManagable
21
+
22
+ attr_reader :dictionary_metadata_object, :dictionary_words
23
+
24
+ def initialize(dictionary_key:, dictionary_words:, dictionary_cache:,
25
+ dictionary_metadata:, word_filters: nil, word_preprocessors: nil)
26
+ validate_dictionary_key dictionary_key: dictionary_key
27
+ self.dictionary_key = dictionary_key
28
+
29
+ validate_dictionary_cache dictionary_cache: dictionary_cache
30
+ self.dictionary_cache = dictionary_cache
31
+
32
+ validate_dictionary_metadata dictionary_metadata: dictionary_metadata
33
+ self.dictionary_metadata = dictionary_metadata
34
+
35
+ unless dictionary_words.is_a?(Array)
36
+ raise ArgumentError,
37
+ "Argument dictionary_words is not an Array: #{dictionary_words.class}"
38
+ end
39
+
40
+ # Set up the dictionary metadata object and observers
41
+ self.dictionary_words = self.class.to_hash(dictionary_words: dictionary_words)
42
+ self.dictionary_metadata_object = create_dictionary_metadata
43
+ dictionary_metadata_object.add_observers
44
+
45
+ add_filters word_filters: word_filters || []
46
+ add_preprocessors word_preprocessors: word_preprocessors || []
47
+ end
48
+
49
+ class << self
50
+ def to_hash(dictionary_words:)
51
+ dictionary_words.each_with_object(Hash.new(false)) { |word, hash| hash[word] = true; }
52
+ end
53
+ end
54
+
55
+ def word_results(word)
56
+ # TODO: Make max word size configurable.
57
+ raise ArgumentError, "Argument word is not a String: #{word.class}" unless word.is_a?(String)
58
+
59
+ preprocessed_words = preprocess(word: word)
60
+ preprocessed_word = preprocessed_words.preprocessed_word
61
+ filters_matched = filters_matched(preprocessed_word || word)
62
+ word_results = WordResults.new(original_word: word,
63
+ filters_matched: filters_matched,
64
+ preprocessed_words: preprocessed_words,
65
+ word_cached: dictionary_words.include?(preprocessed_word || word),
66
+ word_valid: dictionary_words[preprocessed_word || word] || false)
67
+
68
+ dictionary_metadata_object.notify(action: :word_search,
69
+ params: { word_results: word_results })
70
+
71
+ word_results
72
+ end
73
+
74
+ def block_results(word_block)
75
+ # TODO: Make max word_block size configurable.
76
+ raise ArgumentError, "Argument word_block is not a String: #{word_block.class}" unless word_block.is_a?(String)
77
+ raise ArgumentError, "Argument word_block is empty: #{word_block.class}" unless word_block.present?
78
+
79
+ BlockResults.new(original_word_block: word_block).tap do |block_results|
80
+ word_block.scan(config.word_block_regex)&.map do |word|
81
+ block_results << word_results(word)
82
+ end
83
+ end
84
+ end
85
+
86
+ # This method returns true if this dictionary object is detached from the
87
+ # dictionary cache; this can happen if the dictionary object is unloaded
88
+ # from the dictionary cache(DictionaryManager#unload_dictionary) or the
89
+ # dictionary is killed (DictionaryManager#kill_dictionary).
90
+ def detached?
91
+ !dictionary_cache_service.dictionary_object?
92
+ end
93
+
94
+ # This method returns a count of VALID words in the dictionary.
95
+ def count
96
+ dictionary_words.each_pair.count { |_word, valid| valid }
97
+ end
98
+
99
+ # This method returns a count of all VALID and INVALID words in
100
+ # the dictionary.
101
+ def count_all_words
102
+ dictionary_words.count
103
+ end
104
+
105
+ # This method returns a count of all INVALID words in the dictionary.
106
+ def count_invalid_words
107
+ dictionary_words.each_pair.count { |_word, valid| !valid }
108
+ end
109
+
110
+ private
111
+
112
+ attr_writer :dictionary_metadata_object, :dictionary_words
113
+
114
+ def create_dictionary_metadata
115
+ # We unconditionally attach metadata to this dictionary. DictionaryMetadata
116
+ # only attaches the metadata services that are turned "on".
117
+ Metadata::DictionaryMetadata.new(
118
+ dictionary_words: dictionary_words,
119
+ dictionary_key: dictionary_key,
120
+ dictionary_cache: dictionary_cache,
121
+ dictionary_metadata: dictionary_metadata
122
+ )
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'modules/language_validatable'
4
+ require_relative 'modules/locale'
5
+ require_relative 'modules/region_validatable'
6
+ require_relative 'modules/taggable'
7
+
8
+ module LittleWeasel
9
+ # This class describes a unique key associated with a particular dictionary
10
+ # file. Dictionary keys are used to identify a dictionary on which an action
11
+ # should be performed.
12
+ class DictionaryKey
13
+ include Modules::LanguageValidatable
14
+ include Modules::Locale
15
+ include Modules::RegionValidatable
16
+ include Modules::Taggable
17
+
18
+ attr_reader :language, :region
19
+
20
+ def initialize(language:, region: nil, tag: nil)
21
+ validate_language language: language
22
+ self.language = self.class.normalize_language language
23
+
24
+ validate_region region: region
25
+ self.region = self.class.normalize_region region
26
+
27
+ validate_tag tag: tag
28
+ self.tag = tag
29
+ end
30
+
31
+ def key
32
+ return locale unless tagged?
33
+
34
+ "#{locale}-#{tag}"
35
+ end
36
+ alias to_s key
37
+
38
+ class << self
39
+ def key(language:, region: nil, tag: nil)
40
+ new(language: language, region: region, tag: tag).key
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ attr_writer :language, :region
47
+ end
48
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/module/delegation'
4
+ require_relative 'dictionary_key'
5
+ require_relative 'modules/dictionary_key_validatable'
6
+
7
+ module LittleWeasel
8
+ # This class provides dictionary management functionality.
9
+ class DictionaryManager
10
+ include Modules::DictionaryKeyValidatable
11
+
12
+ attr_reader :dictionary_cache, :dictionary_metadata
13
+
14
+ def initialize
15
+ self.dictionary_cache = {}
16
+ self.dictionary_metadata = {}
17
+ init
18
+ end
19
+
20
+ def dictionary_for(dictionary_key:)
21
+ validate_dictionary_key dictionary_key: dictionary_key
22
+
23
+ unless dictionary_cache_service(dictionary_key: dictionary_key).dictionary_exists?
24
+ # TODO: Raise an error or let the service handle it?
25
+ end
26
+
27
+ dictionary_cache_service(dictionary_key: dictionary_key).dictionary_object!
28
+ end
29
+
30
+ # Adds a dictionary file source, creates the dictionary and returns the
31
+ # Dictionary object.
32
+ def create_dictionary_from_file(dictionary_key:, file:, word_filters: nil, word_preprocessors: nil)
33
+ validate_dictionary_key dictionary_key: dictionary_key
34
+
35
+ dictionary_creator_service(dictionary_key: dictionary_key, word_filters: word_filters,
36
+ word_preprocessors: word_preprocessors).from_file_source file: file
37
+ end
38
+
39
+ # Adds a dictionary memory source, creates the dictionary and returns the
40
+ # Dictionary object.
41
+ def create_dictionary_from_memory(dictionary_key:, dictionary_words:, word_filters: nil, word_preprocessors: nil)
42
+ validate_dictionary_key dictionary_key: dictionary_key
43
+
44
+ dictionary_creator_service(dictionary_key: dictionary_key, word_filters: word_filters,
45
+ word_preprocessors: word_preprocessors).from_memory_source dictionary_words: dictionary_words
46
+ end
47
+
48
+ # Removes any and all traces of the dictionary associated with the
49
+ # dictionary key from the dictionary cache - the Dictionary object, file
50
+ # reference and any metadata associated with the dictionary are completely
51
+ # removed from the dictionary cache.
52
+ def kill_dictionary(dictionary_key:)
53
+ validate_dictionary_key dictionary_key: dictionary_key
54
+
55
+ dictionary_killer_service(dictionary_key: dictionary_key).execute
56
+ self
57
+ end
58
+
59
+ # Resets the cache and metadata by clearing it out completely.
60
+ def init
61
+ Services::DictionaryCacheService.init dictionary_cache: dictionary_cache
62
+ Services::DictionaryMetadataService.init dictionary_metadata: dictionary_metadata
63
+ self
64
+ end
65
+
66
+ private
67
+
68
+ attr_writer :dictionary_cache, :dictionary_metadata
69
+
70
+ def dictionary_cache_service(dictionary_key:)
71
+ Services::DictionaryCacheService.new dictionary_key: dictionary_key, dictionary_cache: dictionary_cache
72
+ end
73
+
74
+ def dictionary_creator_service(dictionary_key:, word_filters:, word_preprocessors:)
75
+ Services::DictionaryCreatorService.new dictionary_key: dictionary_key, dictionary_cache: dictionary_cache,
76
+ dictionary_metadata: dictionary_metadata, word_filters: word_filters,
77
+ word_preprocessors: word_preprocessors
78
+ end
79
+
80
+ def dictionary_killer_service(dictionary_key:)
81
+ Services::DictionaryKillerService.new dictionary_key: dictionary_key, dictionary_cache: dictionary_cache,
82
+ dictionary_metadata: dictionary_metadata
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LittleWeasel
4
+ module Errors
5
+ # This class describes an error when a dictionary is already loaded and
6
+ # should not be loaded again.
7
+ class DictionaryFileAlreadyLoadedError < StandardError; end
8
+ end
9
+ end