LittleWeasel 3.0.3 → 5.0.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 (151) 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/CHANGELOG.md +22 -1
  9. data/Gemfile +3 -1
  10. data/Jenkinsfile +20 -0
  11. data/LittleWeasel.gemspec +31 -18
  12. data/README.md +408 -42
  13. data/Rakefile +296 -3
  14. data/lib/LittleWeasel/block_results.rb +81 -0
  15. data/lib/LittleWeasel/configure.rb +98 -0
  16. data/lib/LittleWeasel/dictionary.rb +125 -0
  17. data/lib/LittleWeasel/dictionary_key.rb +48 -0
  18. data/lib/LittleWeasel/dictionary_manager.rb +91 -0
  19. data/lib/LittleWeasel/errors/dictionary_file_already_loaded_error.rb +9 -0
  20. data/lib/LittleWeasel/errors/dictionary_file_empty_error.rb +8 -0
  21. data/lib/LittleWeasel/errors/dictionary_file_not_found_error.rb +8 -0
  22. data/lib/LittleWeasel/errors/dictionary_file_too_large_error.rb +16 -0
  23. data/lib/LittleWeasel/errors/language_required_error.rb +8 -0
  24. data/lib/LittleWeasel/errors/must_override_error.rb +8 -0
  25. data/lib/LittleWeasel/filters/en_us/currency_filter.rb +19 -0
  26. data/lib/LittleWeasel/filters/en_us/numeric_filter.rb +19 -0
  27. data/lib/LittleWeasel/filters/en_us/single_character_word_filter.rb +21 -0
  28. data/lib/LittleWeasel/filters/word_filter.rb +59 -0
  29. data/lib/LittleWeasel/filters/word_filter_managable.rb +80 -0
  30. data/lib/LittleWeasel/filters/word_filter_validatable.rb +31 -0
  31. data/lib/LittleWeasel/filters/word_filterable.rb +19 -0
  32. data/lib/LittleWeasel/filters/word_filters_validatable.rb +29 -0
  33. data/lib/LittleWeasel/metadata/dictionary_metadata.rb +145 -0
  34. data/lib/LittleWeasel/metadata/invalid_words_metadata.rb +134 -0
  35. data/lib/LittleWeasel/metadata/invalid_words_service_results.rb +45 -0
  36. data/lib/LittleWeasel/metadata/metadata_observable_validatable.rb +22 -0
  37. data/lib/LittleWeasel/metadata/metadata_observerable.rb +90 -0
  38. data/lib/LittleWeasel/metadata/metadatable.rb +134 -0
  39. data/lib/LittleWeasel/modules/class_name_to_symbol.rb +26 -0
  40. data/lib/LittleWeasel/modules/configurable.rb +26 -0
  41. data/lib/LittleWeasel/modules/deep_dup.rb +11 -0
  42. data/lib/LittleWeasel/modules/dictionary_cache_keys.rb +34 -0
  43. data/lib/LittleWeasel/modules/dictionary_cache_servicable.rb +26 -0
  44. data/lib/LittleWeasel/modules/dictionary_cache_validatable.rb +18 -0
  45. data/lib/LittleWeasel/modules/dictionary_creator_servicable.rb +27 -0
  46. data/lib/LittleWeasel/modules/dictionary_file_loader.rb +67 -0
  47. data/lib/LittleWeasel/modules/dictionary_key_validatable.rb +17 -0
  48. data/lib/LittleWeasel/modules/dictionary_keyable.rb +24 -0
  49. data/lib/LittleWeasel/modules/dictionary_metadata_servicable.rb +29 -0
  50. data/lib/LittleWeasel/modules/dictionary_metadata_validatable.rb +15 -0
  51. data/lib/LittleWeasel/modules/dictionary_source_validatable.rb +15 -0
  52. data/lib/LittleWeasel/modules/dictionary_sourceable.rb +86 -0
  53. data/lib/LittleWeasel/modules/dictionary_validatable.rb +18 -0
  54. data/lib/LittleWeasel/modules/language.rb +24 -0
  55. data/lib/LittleWeasel/modules/language_validatable.rb +14 -0
  56. data/lib/LittleWeasel/modules/locale.rb +23 -0
  57. data/lib/LittleWeasel/modules/order_validatable.rb +16 -0
  58. data/lib/LittleWeasel/modules/orderable.rb +17 -0
  59. data/lib/LittleWeasel/modules/region.rb +24 -0
  60. data/lib/LittleWeasel/modules/region_validatable.rb +14 -0
  61. data/lib/LittleWeasel/modules/tag_validatable.rb +14 -0
  62. data/lib/LittleWeasel/modules/taggable.rb +31 -0
  63. data/lib/LittleWeasel/modules/word_results_validatable.rb +28 -0
  64. data/lib/LittleWeasel/preprocessors/en_us/capitalize_preprocessor.rb +22 -0
  65. data/lib/LittleWeasel/preprocessors/preprocessed_word.rb +29 -0
  66. data/lib/LittleWeasel/preprocessors/preprocessed_word_validatable.rb +56 -0
  67. data/lib/LittleWeasel/preprocessors/preprocessed_words.rb +59 -0
  68. data/lib/LittleWeasel/preprocessors/preprocessed_words_validatable.rb +28 -0
  69. data/lib/LittleWeasel/preprocessors/word_preprocessable.rb +19 -0
  70. data/lib/LittleWeasel/preprocessors/word_preprocessor.rb +123 -0
  71. data/lib/LittleWeasel/preprocessors/word_preprocessor_managable.rb +114 -0
  72. data/lib/LittleWeasel/preprocessors/word_preprocessor_validatable.rb +40 -0
  73. data/lib/LittleWeasel/preprocessors/word_preprocessors_validatable.rb +24 -0
  74. data/lib/LittleWeasel/services/dictionary_cache_service.rb +211 -0
  75. data/lib/LittleWeasel/services/dictionary_creator_service.rb +94 -0
  76. data/lib/LittleWeasel/services/dictionary_file_loader_service.rb +37 -0
  77. data/lib/LittleWeasel/services/dictionary_killer_service.rb +35 -0
  78. data/lib/LittleWeasel/services/dictionary_metadata_service.rb +116 -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/lib/LittleWeasel.rb +5 -184
  83. data/spec/factories/dictionary.rb +43 -0
  84. data/spec/factories/dictionary_cache_service.rb +95 -0
  85. data/spec/factories/dictionary_creator_service.rb +16 -0
  86. data/spec/factories/dictionary_file_loader_service.rb +13 -0
  87. data/spec/factories/dictionary_hash.rb +39 -0
  88. data/spec/factories/dictionary_key.rb +14 -0
  89. data/spec/factories/dictionary_killer_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 +166 -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 +81 -0
  119. data/spec/lib/LittleWeasel/modules/language_spec.rb +112 -0
  120. data/spec/lib/LittleWeasel/modules/locale_spec.rb +95 -0
  121. data/spec/lib/LittleWeasel/modules/region_spec.rb +112 -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 +242 -0
  128. data/spec/lib/LittleWeasel/preprocessors/word_preprocessor_spec.rb +218 -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_metadata_service_spec.rb +279 -0
  135. data/spec/lib/LittleWeasel/word_results_spec.rb +275 -0
  136. data/spec/lib/LittleWeasel/workflow/workflow_spec.rb +20 -0
  137. data/spec/spec_helper.rb +117 -6
  138. data/spec/support/factory_bot.rb +15 -0
  139. data/spec/support/file_helpers.rb +46 -0
  140. data/spec/support/files/empty-dictionary.txt +0 -0
  141. data/{lib/dictionary → spec/support/files/en-US-big.txt} +262156 -31488
  142. data/spec/support/files/en-US-tagged.txt +26 -0
  143. data/spec/support/files/en-US.txt +26 -0
  144. data/spec/support/files/en.txt +26 -0
  145. data/spec/support/files/es-ES.txt +27 -0
  146. data/spec/support/files/es.txt +27 -0
  147. data/spec/support/general_helpers.rb +68 -0
  148. data/spec/support/shared_contexts.rb +107 -0
  149. data/spec/support/shared_examples.rb +105 -0
  150. metadata +378 -38
  151. data/spec/checker/checker_spec.rb +0 -286
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../modules/class_name_to_symbol'
4
+ require_relative '../modules/configurable'
5
+ require_relative '../modules/dictionary_cache_servicable'
6
+ require_relative '../modules/dictionary_keyable'
7
+ require_relative '../services/invalid_words_service'
8
+ require_relative 'metadata_observerable'
9
+
10
+ module LittleWeasel
11
+ module Metadata
12
+ # This class provides the ability to cache words not found in the
13
+ # associated dictionary.
14
+ class InvalidWordsMetadata
15
+ include Metadata::MetadataObserverable
16
+ include Modules::ClassNameToSymbol
17
+ include Modules::Configurable
18
+ include Modules::DictionaryCacheServicable
19
+ include Modules::DictionaryKeyable
20
+ include Modules::DictionaryMetadataServicable
21
+
22
+ delegate :on?, :off?, :value, :value_exceeded?,
23
+ :current_invalid_word_bytesize, :cache_invalid_words?,
24
+ to: :metadata
25
+
26
+ attr_reader :dictionary_metadata_object
27
+
28
+ def initialize(dictionary_metadata_object:, dictionary_metadata:,
29
+ dictionary_cache:, dictionary_key:, dictionary_words:)
30
+ validate_dictionary_key dictionary_key: dictionary_key
31
+ self.dictionary_key = dictionary_key
32
+
33
+ validate_dictionary_cache dictionary_cache: dictionary_cache
34
+ self.dictionary_cache = dictionary_cache
35
+
36
+ validate_dictionary_metadata dictionary_metadata: dictionary_metadata
37
+ self.dictionary_metadata = dictionary_metadata
38
+
39
+ unless dictionary_metadata_object.is_a? Observable
40
+ raise ArgumentError,
41
+ "Argument dictionary_metadata_object is not an Observable: #{dictionary_metadata_object.class}."
42
+ end
43
+
44
+ dictionary_metadata_object.add_observer self
45
+ self.dictionary_metadata_object = dictionary_metadata_object
46
+
47
+ unless dictionary_words.is_a? Hash
48
+ raise ArgumentError,
49
+ "Argument dictionary_words is not a Hash: #{dictionary_words.class}."
50
+ end
51
+
52
+ self.dictionary_words = dictionary_words
53
+ end
54
+
55
+ class << self
56
+ def metadata_key
57
+ to_sym
58
+ end
59
+
60
+ def observe?
61
+ config.max_invalid_words_bytesize?
62
+ end
63
+ end
64
+
65
+ # rubocop: disable Lint/UnusedMethodArgument
66
+ def init(params: nil)
67
+ dictionary_metadata_service.init(metadata_key: metadata_key)
68
+ self.metadata = Services::InvalidWordsService.new(dictionary_words).execute
69
+ self
70
+ end
71
+ # rubocop: enable Lint/UnusedMethodArgument
72
+
73
+ # rubocop: disable Lint/UnusedMethodArgument
74
+ def refresh(params: nil)
75
+ refresh_local_metadata
76
+ init unless metadata.present?
77
+ self
78
+ end
79
+ # rubocop: enable Lint/UnusedMethodArgument
80
+
81
+ # This method is called when a word is being searched in the
82
+ # dictionary.
83
+ def word_search(params:)
84
+ word_results = params[:word_results]
85
+
86
+ # TODO: NOW: Should we be returning #word_valid? or #success?
87
+ return word_results.word_valid? if word_results.word_cached?
88
+
89
+ # If we get here, we know that the word is NOT in the dictionary either
90
+ # as a valid word OR as a cached, INVALID word.
91
+
92
+ # If caching is supposed to take place, cache the word as invalid
93
+ # (not found).
94
+ word = word_results.preprocessed_word_or_original_word
95
+ dictionary_words[word] = false if cache_word? word
96
+ false
97
+ end
98
+
99
+ def update(action, params)
100
+ unless actions_whitelist.include? action
101
+ raise ArgumentError,
102
+ "Argument action is not in the actions_whitelist: #{action}"
103
+ end
104
+
105
+ send(action, params: params)
106
+ self
107
+ end
108
+
109
+ def actions_whitelist
110
+ %i[init refresh word_search]
111
+ end
112
+
113
+ private
114
+
115
+ attr_accessor :dictionary_words
116
+ attr_writer :dictionary_metadata_object
117
+
118
+ def cache_word?(word)
119
+ return false unless metadata.cache_invalid_words?
120
+
121
+ if metadata.value >= (word.bytesize + metadata.current_invalid_word_bytesize)
122
+ metadata.current_invalid_word_bytesize += word.bytesize
123
+ true
124
+ else
125
+ false
126
+ end
127
+ end
128
+
129
+ def update_dictionary_metadata(value:)
130
+ dictionary_metadata_service.set_dictionary_metadata(value: value, metadata_key: metadata_key)
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LittleWeasel
4
+ module Metadata
5
+ # This class provides a container for the results of the
6
+ # InvalidWordsService service.
7
+ class InvalidWordsServiceResults
8
+ attr_accessor :current_invalid_word_bytesize
9
+ attr_reader :max_invalid_words_bytesize
10
+
11
+ def initialize(max_invalid_words_bytesize_on:,
12
+ current_invalid_word_bytesize:, max_invalid_words_bytesize:)
13
+
14
+ self.max_invalid_words_bytesize_on = max_invalid_words_bytesize_on
15
+ self.current_invalid_word_bytesize = current_invalid_word_bytesize
16
+ self.max_invalid_words_bytesize = max_invalid_words_bytesize
17
+ end
18
+
19
+ def on?
20
+ max_invalid_words_bytesize_on
21
+ end
22
+
23
+ def off?
24
+ !on?
25
+ end
26
+
27
+ def value
28
+ max_invalid_words_bytesize
29
+ end
30
+
31
+ def value_exceeded?
32
+ on? && current_invalid_word_bytesize > max_invalid_words_bytesize
33
+ end
34
+
35
+ def cache_invalid_words?
36
+ on? && !value_exceeded?
37
+ end
38
+
39
+ private
40
+
41
+ attr_accessor :max_invalid_words_bytesize_on
42
+ attr_writer :max_invalid_words_bytesize
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'metadata_observerable'
4
+
5
+ module LittleWeasel
6
+ module Metadata
7
+ # This module provides methods to validate MetadataObservable objects.
8
+ module MetadataObservableValidatable
9
+ # This method validates a single MetadataObserverable object.
10
+ def validate_metadata_observable(metadata_observable)
11
+ unless valid_metadata_observable? metadata_observable
12
+ raise 'Argument metadata_observable is not a ' \
13
+ "Metadata::MetadataObserverable object: #{metadata_observable.class}"
14
+ end
15
+ end
16
+
17
+ def valid_metadata_observable?(metadata_observable)
18
+ metadata_observable.is_a? Metadata::MetadataObserverable
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../errors/must_override_error'
4
+ require_relative 'metadatable'
5
+
6
+ module LittleWeasel
7
+ module Metadata
8
+ # Defines methods to support metadata modules that are observers of
9
+ # objects that include Metadata::DictionaryMetadata.
10
+ module MetadataObserverable
11
+ include Metadatable
12
+
13
+ def self.included(base)
14
+ base.extend ClassMethods
15
+ end
16
+
17
+ # class method inclusions for convenience.
18
+ module ClassMethods
19
+ # If the medatata observer is not in a state to observe, or is turned
20
+ # "off", return false; otherwise, return true...
21
+ #
22
+ # Configuration option settings may turn a metadata observer "off";
23
+ # for example, InvalidWordsMedata will not be observable unless
24
+ # LittleWeasel.configuration.max_invalid_words_bytesize? returns true.
25
+ #
26
+ # Other variables may also determine whether or not a metadata object is
27
+ # capable of observing; consequently, an instance-level #observe? method
28
+ # is also availble if this is the case (see below).
29
+ #
30
+ # If the observable state of your metadata object depends on
31
+ # configuration settings ALONE, return true/false using this class-level
32
+ # method, and do not override the instance-level #observe? method.
33
+ #
34
+ # If the observable state of your metadata object can only be determined
35
+ # AFTER the metadata object is instantiated: return true from the the
36
+ # class-level .observe? method; then, override the instance-level
37
+ # #observe? method and return true/false accordingly.
38
+ #
39
+ # If the observable state of your metadata object is determined by BOTH
40
+ # configuration settings AND variables that can only be determined AFTER
41
+ # the metadata object has been instantiated, use both the class-level
42
+ # and instance-level observe? to return true/false accordingly.
43
+ def observe?
44
+ false
45
+ end
46
+ end
47
+
48
+ # Return true/false depending on whether or not this metadata observer
49
+ # is in a state to observe.
50
+ #
51
+ # (See .observe? class-level method comments)
52
+ def observe?
53
+ self.class.observe?
54
+ end
55
+
56
+ # This is an override of Metadata#refresh_local_metadata. See
57
+ # Metadata#refresh_local_metadata comments.
58
+ def refresh_local_metadata
59
+ @metadata = dictionary_metadata_service.get_dictionary_metadata(metadata_key: metadata_key)
60
+ end
61
+
62
+ # This method receives notifications from an observable.
63
+ # object and should be chainable (return self).
64
+ # All actions should be filtered through the
65
+ # actions_whitelist and an error raised if action
66
+ # is not in the actions_whitelist. If **args are used,
67
+ # further filtering should be applied based on the
68
+ # need.
69
+ #
70
+ # @example
71
+ #
72
+ # def update(action, **args)
73
+ # raise ArgumentError unless actions_whitelist.include? action
74
+ #
75
+ # send(action)
76
+ # self
77
+ # end
78
+ def update(_action, **_args)
79
+ raise Errors::MustOverrideError
80
+ end
81
+
82
+ # This method should return actions (messages) that can be sent
83
+ # to this object; for example, at a minimum :init and :refresh
84
+ # need to be in this list
85
+ def actions_whitelist
86
+ %i[init refresh]
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../errors/must_override_error'
4
+ require_relative '../services/dictionary_cache_service'
5
+
6
+ module LittleWeasel
7
+ module Metadata
8
+ # This module defines methods to support objects that manage other objects
9
+ # that manage metadata related to a dictionary/ies.
10
+ # rubocop: disable Lint/UnusedMethodArgument, ignored - Methods in this
11
+ # module need to keep their argument names because of specs.
12
+ module Metadatable
13
+ def self.included(base)
14
+ base.extend ClassMethods
15
+ end
16
+
17
+ # class method inclusions for convenience.
18
+ module ClassMethods
19
+ # Override this method to return the metadata key associated with this
20
+ # metadata object in the dictionary cache.
21
+ def metadata_key
22
+ to_sym
23
+ end
24
+ end
25
+
26
+ def metadata_key
27
+ self.class.metadata_key
28
+ end
29
+
30
+ # This method should UNCONDITIONALLY update the local metadata, using the
31
+ # metadata_key and notify all observers (if any) to initialize themselves
32
+ # as well.
33
+ #
34
+ # This method should be chainable (return self).
35
+ #
36
+ # @example
37
+ #
38
+ # . # Example of a root-level dictionary metadata object (e.g.
39
+ # . # Metadata::DictionaryMetadata)
40
+ # def init(_params: nil)
41
+ # self.metadata = {}
42
+ # notify action: :init
43
+ # refresh_local_metadata
44
+ # unless count_observers.zero? || metadata.present?
45
+ # raise 'Observers were called to #init but the dictionary cache metadata was not initialized'
46
+ # end
47
+ #
48
+ # self
49
+ # end
50
+ #
51
+ # . # Example of a metadata observable object (e.g.
52
+ # . # Metadata::InvalidWords::InvalidWordsMetadata)
53
+ # def init(params: nil)
54
+ # self.metadata = Services::InvalidWordsService.new(dictionary_words).execute
55
+ # self
56
+ # end
57
+ def init(params: nil)
58
+ raise Errors::MustOverrideError
59
+ end
60
+
61
+ # This method should refresh the local metadata from the dictionary cache,
62
+ # if metadata exists in the dictionary cache for the given metatata_key.
63
+ # Otherwise, #init should be called to initialize this object.
64
+ # The idea is that metadata should be shared across metadata objects of
65
+ # the same type that use the same metadata_key.
66
+ #
67
+ # This method should be chainable (return self).
68
+ #
69
+ # @example
70
+ #
71
+ # def refresh(params: nil)
72
+ # refresh_local_metadata
73
+ # init unless metadata.present?
74
+ # self
75
+ # end
76
+ def refresh(params: nil)
77
+ raise Errors::MustOverrideError
78
+ end
79
+
80
+ private
81
+
82
+ attr_reader :metadata
83
+
84
+ # This method should set the metadata in the dictionary cache, using the
85
+ # appropriate metadata key for this object (or nil if the root metadata
86
+ # object) AND set the @metadata local attribute so that a local copy is
87
+ # available for use.
88
+ #
89
+ # When instantiating this object, #init (see #init comments). If an
90
+ # updated copy of the metadata is needed #refresh (see comments) should
91
+ # be called.
92
+ #
93
+ # @example
94
+ #
95
+ # def metadata=(value)
96
+ # dictionary_cache_service = LittleWeasel::Services::DictionaryCacheService.new(
97
+ # dictionary_key: dictionary_key, dictionary_cache: dictionary_cache)
98
+ # dictionary_cache_service.dictionary_metadata_set(
99
+ # metadata_key: METADATA_KEY, value: value)
100
+ # @metadata = value
101
+ # end
102
+ def metadata=(value)
103
+ @metadata = value
104
+ update_dictionary_metadata value: value
105
+ end
106
+
107
+ # This method updates the local metadata ONLY. Use this method if you
108
+ # need to update the local metadata from the dictionary cache metadata.
109
+ #
110
+ # Override this method in metadata observable classes as needed.
111
+ def refresh_local_metadata
112
+ @metadata = dictionary_metadata_service.dictionary_metadata
113
+ end
114
+
115
+ # This method should update the dictionary metadata for the the object
116
+ # when it is called.
117
+ #
118
+ # @example
119
+ #
120
+ # def update_dictionary_metadata(value:)
121
+ # dictionary_cache_service = LittleWeasel::Services::DictionaryCacheService.new(
122
+ # dictionary_key: dictionary_key, dictionary_cache: dictionary_cache)
123
+ # dictionary_cache_service.dictionary_metadata_set(
124
+ # metadata_key: metadata_key, value: value)
125
+ # end
126
+ # :reek:UnusedParameters, ignored - This method is meant to be called with the given argument and raises an
127
+ # error if not overridden
128
+ def update_dictionary_metadata(value:)
129
+ raise Errors::MustOverrideError
130
+ end
131
+ end
132
+ # rubocop: enable Lint/UnusedMethodArgument
133
+ end
134
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/inflector'
4
+
5
+ module LittleWeasel
6
+ module Modules
7
+ # This module provides methods to convert the class name of the class
8
+ # mixing this module in to snake-case.
9
+ module ClassNameToSymbol
10
+ def self.included(base)
11
+ base.extend(ClassMethods)
12
+ end
13
+
14
+ # class method inclusions for convenience.
15
+ module ClassMethods
16
+ def to_sym
17
+ name.demodulize.underscore.to_sym
18
+ end
19
+ end
20
+
21
+ def to_sym
22
+ self.class.to_sym
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LittleWeasel
4
+ module Modules
5
+ # This module provides convienience methods for accessing this gem's
6
+ # configuration.
7
+ module Configurable
8
+ def self.included(base)
9
+ base.extend(ClassMethods)
10
+ end
11
+
12
+ # class method inclusions for convenience.
13
+ module ClassMethods
14
+ def config
15
+ LittleWeasel.configuration
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def config
22
+ @config ||= self.class.config
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LittleWeasel
4
+ module Modules
5
+ # This module simply includes ActiveSupport's core for #deep_dup support. If
6
+ # we ever want to roll our own, we can do so here.
7
+ module DeepDup
8
+ require 'active_support/core_ext/object/deep_dup'
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LittleWeasel
4
+ module Modules
5
+ # This module provides methods and constants used to define, initialize
6
+ # and manipulate a dictionary cache Hash object.
7
+ module DictionaryCacheKeys
8
+ DICTIONARY_CACHE = 'dictionary_cache'
9
+ DICTIONARY_REFERENCES = 'dictionary_references'
10
+ DICTIONARY_ID = 'dictionary_id'
11
+ DICTIONARIES = 'dictionaries'
12
+ SOURCE = 'source'
13
+ DICTIONARY_OBJECT = 'dictionary_object'
14
+
15
+ module_function
16
+
17
+ def initialize_dictionary_cache(dictionary_cache:)
18
+ dictionary_cache.each_key { |key| dictionary_cache.delete(key) }
19
+ dictionary_cache[DICTIONARY_CACHE] = initialized_dictionary_cache(include_root: false)
20
+ dictionary_cache
21
+ end
22
+
23
+ def initialized_dictionary_cache(include_root: true)
24
+ dictionary_cache = {
25
+ DICTIONARY_REFERENCES => {},
26
+ DICTIONARIES => {}
27
+ }
28
+ return { DICTIONARY_CACHE => dictionary_cache } if include_root
29
+
30
+ dictionary_cache
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../dictionary_key'
4
+ require_relative '../services/dictionary_cache_service'
5
+ require_relative 'dictionary_cache_validatable'
6
+
7
+ module LittleWeasel
8
+ module Modules
9
+ # This module defines methods and attributes to consume the dictionary
10
+ # cache service.
11
+ module DictionaryCacheServicable
12
+ include DictionaryKeyable
13
+ include DictionaryCacheValidatable
14
+
15
+ attr_reader :dictionary_cache, :dictionary_key
16
+
17
+ def dictionary_cache_service
18
+ Services::DictionaryCacheService.new(dictionary_key: dictionary_key, dictionary_cache: dictionary_cache)
19
+ end
20
+
21
+ private
22
+
23
+ attr_writer :dictionary_cache, :dictionary_key
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LittleWeasel
4
+ module Modules
5
+ # This module provides methods to validate a dictionary cache object.
6
+ # A dictionary cache object is a container that holds cached data
7
+ # related to one or more dictionaries. Dictionary cache objects are
8
+ # normally specific to a DictionaryManager object.
9
+ module DictionaryCacheValidatable
10
+ module_function
11
+
12
+ def validate_dictionary_cache(dictionary_cache:)
13
+ raise ArgumentError, "Argument dictionary_cache is not a valid Hash object: #{dictionary_cache.class}" \
14
+ unless dictionary_cache.is_a? Hash
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../dictionary_key'
4
+ require_relative '../services/dictionary_cache_service'
5
+ require_relative '../filters/word_filters_validatable'
6
+
7
+ module LittleWeasel
8
+ module Modules
9
+ # This module defines methods and attributes to consume the dictionary
10
+ # creator service.
11
+ module DictionaryCreatorServicable
12
+ include DictionaryKeyable
13
+ include Filters::WordFiltersValidatable
14
+
15
+ attr_reader :dictionary_cache, :dictionary_key, :word_filters
16
+
17
+ def dictionary_creator_service
18
+ Services::DictionaryCreatorService.new(dictionary_key: dictionary_key, dictionary_cache: dictionary_cache,
19
+ dictionary_metadata: dictionary_metadata, word_filters: word_filters)
20
+ end
21
+
22
+ private
23
+
24
+ attr_writer :dictionary_cache, :dictionary_key, :word_filters
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../errors/dictionary_file_already_loaded_error'
4
+ require_relative '../errors/dictionary_file_empty_error'
5
+ require_relative '../errors/dictionary_file_not_found_error'
6
+ require_relative '../errors/dictionary_file_too_large_error'
7
+
8
+ module LittleWeasel
9
+ module Modules
10
+ # Defines methods to load dictionaries. The dictionary file path is used
11
+ # as a key to avoid loading the same dictionary multiple times.
12
+ module DictionaryFileLoader
13
+ def load(dictionary_file_path)
14
+ Loader.new(dictionary_file_path, config).load
15
+ end
16
+
17
+ # Helps with dictionary loading.
18
+ class Loader
19
+ def initialize(dictionary_file_path, config)
20
+ self.dictionary_file_path = dictionary_file_path
21
+ self.config = config
22
+ end
23
+
24
+ # Loads but DOES NOT update the dictionaries_hash. Use this if the dictionary
25
+ # DOES NOT need to hang around for any length of time.
26
+ def load
27
+ raise Errors::DictionaryFileNotFoundError unless file_exist?
28
+ raise Errors::DictionaryFileEmptyError if file_empty?
29
+ raise Errors::DictionaryFileTooLargeError if file_too_large?
30
+
31
+ load_dictionary
32
+ end
33
+
34
+ private
35
+
36
+ attr_accessor :config, :dictionary_file_path, :dictionary_words
37
+
38
+ def load_dictionary
39
+ prepare_dictionary(File.read(dictionary_file_path, mode: 'r')&.split)
40
+ end
41
+
42
+ def prepare_dictionary(words)
43
+ words&.uniq!&.compact!
44
+ words if words.present?
45
+ end
46
+
47
+ def file_size
48
+ # File.size? returns nil if file_name doesn't exist or has zero size,
49
+ # the size of the file otherwise.
50
+ @file_size ||= File.size?(dictionary_file_path) || 0
51
+ end
52
+
53
+ def file_exist?
54
+ @file_exist ||= File.exist? dictionary_file_path
55
+ end
56
+
57
+ def file_empty?
58
+ @file_empty ||= file_exist? && file_size.zero?
59
+ end
60
+
61
+ def file_too_large?
62
+ @file_too_large ||= file_exist? && file_size > config.max_dictionary_file_bytes
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../dictionary_key'
4
+
5
+ module LittleWeasel
6
+ module Modules
7
+ # Provides methods to validate a dictionary key object.
8
+ module DictionaryKeyValidatable
9
+ module_function
10
+
11
+ def validate_dictionary_key(dictionary_key:)
12
+ raise ArgumentError, "Argument dictionary_key is not a valid DictionaryKey object: #{dictionary_key.class}" \
13
+ unless dictionary_key.is_a? DictionaryKey
14
+ end
15
+ end
16
+ end
17
+ end