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.
- checksums.yaml +5 -5
- data/.gitignore +3 -0
- data/.reek.yml +17 -0
- data/.rspec +4 -2
- data/.rubocop.yml +187 -0
- data/.ruby-version +1 -1
- data/.yardopts +2 -0
- data/CHANGELOG.md +22 -1
- data/Gemfile +3 -1
- data/Jenkinsfile +20 -0
- data/LittleWeasel.gemspec +31 -18
- data/README.md +408 -42
- data/Rakefile +296 -3
- data/lib/LittleWeasel/block_results.rb +81 -0
- data/lib/LittleWeasel/configure.rb +98 -0
- data/lib/LittleWeasel/dictionary.rb +125 -0
- data/lib/LittleWeasel/dictionary_key.rb +48 -0
- data/lib/LittleWeasel/dictionary_manager.rb +91 -0
- data/lib/LittleWeasel/errors/dictionary_file_already_loaded_error.rb +9 -0
- data/lib/LittleWeasel/errors/dictionary_file_empty_error.rb +8 -0
- data/lib/LittleWeasel/errors/dictionary_file_not_found_error.rb +8 -0
- data/lib/LittleWeasel/errors/dictionary_file_too_large_error.rb +16 -0
- data/lib/LittleWeasel/errors/language_required_error.rb +8 -0
- data/lib/LittleWeasel/errors/must_override_error.rb +8 -0
- data/lib/LittleWeasel/filters/en_us/currency_filter.rb +19 -0
- data/lib/LittleWeasel/filters/en_us/numeric_filter.rb +19 -0
- data/lib/LittleWeasel/filters/en_us/single_character_word_filter.rb +21 -0
- data/lib/LittleWeasel/filters/word_filter.rb +59 -0
- data/lib/LittleWeasel/filters/word_filter_managable.rb +80 -0
- data/lib/LittleWeasel/filters/word_filter_validatable.rb +31 -0
- data/lib/LittleWeasel/filters/word_filterable.rb +19 -0
- data/lib/LittleWeasel/filters/word_filters_validatable.rb +29 -0
- data/lib/LittleWeasel/metadata/dictionary_metadata.rb +145 -0
- data/lib/LittleWeasel/metadata/invalid_words_metadata.rb +134 -0
- data/lib/LittleWeasel/metadata/invalid_words_service_results.rb +45 -0
- data/lib/LittleWeasel/metadata/metadata_observable_validatable.rb +22 -0
- data/lib/LittleWeasel/metadata/metadata_observerable.rb +90 -0
- data/lib/LittleWeasel/metadata/metadatable.rb +134 -0
- data/lib/LittleWeasel/modules/class_name_to_symbol.rb +26 -0
- data/lib/LittleWeasel/modules/configurable.rb +26 -0
- data/lib/LittleWeasel/modules/deep_dup.rb +11 -0
- data/lib/LittleWeasel/modules/dictionary_cache_keys.rb +34 -0
- data/lib/LittleWeasel/modules/dictionary_cache_servicable.rb +26 -0
- data/lib/LittleWeasel/modules/dictionary_cache_validatable.rb +18 -0
- data/lib/LittleWeasel/modules/dictionary_creator_servicable.rb +27 -0
- data/lib/LittleWeasel/modules/dictionary_file_loader.rb +67 -0
- data/lib/LittleWeasel/modules/dictionary_key_validatable.rb +17 -0
- data/lib/LittleWeasel/modules/dictionary_keyable.rb +24 -0
- data/lib/LittleWeasel/modules/dictionary_metadata_servicable.rb +29 -0
- data/lib/LittleWeasel/modules/dictionary_metadata_validatable.rb +15 -0
- data/lib/LittleWeasel/modules/dictionary_source_validatable.rb +15 -0
- data/lib/LittleWeasel/modules/dictionary_sourceable.rb +86 -0
- data/lib/LittleWeasel/modules/dictionary_validatable.rb +18 -0
- data/lib/LittleWeasel/modules/language.rb +24 -0
- data/lib/LittleWeasel/modules/language_validatable.rb +14 -0
- data/lib/LittleWeasel/modules/locale.rb +23 -0
- data/lib/LittleWeasel/modules/order_validatable.rb +16 -0
- data/lib/LittleWeasel/modules/orderable.rb +17 -0
- data/lib/LittleWeasel/modules/region.rb +24 -0
- data/lib/LittleWeasel/modules/region_validatable.rb +14 -0
- data/lib/LittleWeasel/modules/tag_validatable.rb +14 -0
- data/lib/LittleWeasel/modules/taggable.rb +31 -0
- data/lib/LittleWeasel/modules/word_results_validatable.rb +28 -0
- data/lib/LittleWeasel/preprocessors/en_us/capitalize_preprocessor.rb +22 -0
- data/lib/LittleWeasel/preprocessors/preprocessed_word.rb +29 -0
- data/lib/LittleWeasel/preprocessors/preprocessed_word_validatable.rb +56 -0
- data/lib/LittleWeasel/preprocessors/preprocessed_words.rb +59 -0
- data/lib/LittleWeasel/preprocessors/preprocessed_words_validatable.rb +28 -0
- data/lib/LittleWeasel/preprocessors/word_preprocessable.rb +19 -0
- data/lib/LittleWeasel/preprocessors/word_preprocessor.rb +123 -0
- data/lib/LittleWeasel/preprocessors/word_preprocessor_managable.rb +114 -0
- data/lib/LittleWeasel/preprocessors/word_preprocessor_validatable.rb +40 -0
- data/lib/LittleWeasel/preprocessors/word_preprocessors_validatable.rb +24 -0
- data/lib/LittleWeasel/services/dictionary_cache_service.rb +211 -0
- data/lib/LittleWeasel/services/dictionary_creator_service.rb +94 -0
- data/lib/LittleWeasel/services/dictionary_file_loader_service.rb +37 -0
- data/lib/LittleWeasel/services/dictionary_killer_service.rb +35 -0
- data/lib/LittleWeasel/services/dictionary_metadata_service.rb +116 -0
- data/lib/LittleWeasel/services/invalid_words_service.rb +59 -0
- data/lib/LittleWeasel/version.rb +3 -1
- data/lib/LittleWeasel/word_results.rb +146 -0
- data/lib/LittleWeasel.rb +5 -184
- data/spec/factories/dictionary.rb +43 -0
- data/spec/factories/dictionary_cache_service.rb +95 -0
- data/spec/factories/dictionary_creator_service.rb +16 -0
- data/spec/factories/dictionary_file_loader_service.rb +13 -0
- data/spec/factories/dictionary_hash.rb +39 -0
- data/spec/factories/dictionary_key.rb +14 -0
- data/spec/factories/dictionary_killer_service.rb +14 -0
- data/spec/factories/dictionary_manager.rb +10 -0
- data/spec/factories/dictionary_metadata.rb +16 -0
- data/spec/factories/dictionary_metadata_service.rb +16 -0
- data/spec/factories/numeric_filter.rb +12 -0
- data/spec/factories/preprocessed_word.rb +16 -0
- data/spec/factories/preprocessed_words.rb +41 -0
- data/spec/factories/single_character_word_filter.rb +12 -0
- data/spec/factories/word_results.rb +16 -0
- data/spec/lib/LittleWeasel/block_results_spec.rb +248 -0
- data/spec/lib/LittleWeasel/configure_spec.rb +74 -0
- data/spec/lib/LittleWeasel/dictionary_key_spec.rb +118 -0
- data/spec/lib/LittleWeasel/dictionary_manager_spec.rb +166 -0
- data/spec/lib/LittleWeasel/dictionary_spec.rb +289 -0
- data/spec/lib/LittleWeasel/filters/en_us/currency_filter_spec.rb +80 -0
- data/spec/lib/LittleWeasel/filters/en_us/numeric_filter_spec.rb +66 -0
- data/spec/lib/LittleWeasel/filters/en_us/single_character_word_filter_spec.rb +58 -0
- data/spec/lib/LittleWeasel/filters/word_filter_managable_spec.rb +180 -0
- data/spec/lib/LittleWeasel/filters/word_filter_spec.rb +151 -0
- data/spec/lib/LittleWeasel/filters/word_filter_validatable_spec.rb +94 -0
- data/spec/lib/LittleWeasel/filters/word_filters_validatable_spec.rb +48 -0
- data/spec/lib/LittleWeasel/integraton_tests/dictionary_integration_spec.rb +201 -0
- data/spec/lib/LittleWeasel/metadata/dictionary_creator_servicable_spec.rb +54 -0
- data/spec/lib/LittleWeasel/metadata/dictionary_metadata_spec.rb +209 -0
- data/spec/lib/LittleWeasel/metadata/invalid_words_metadata_spec.rb +155 -0
- data/spec/lib/LittleWeasel/metadata/metadata_observerable_spec.rb +31 -0
- data/spec/lib/LittleWeasel/metadata/metadatable_spec.rb +35 -0
- data/spec/lib/LittleWeasel/modules/class_name_to_symbol_spec.rb +21 -0
- data/spec/lib/LittleWeasel/modules/dictionary_file_loader_spec.rb +125 -0
- data/spec/lib/LittleWeasel/modules/dictionary_sourceable_spec.rb +81 -0
- data/spec/lib/LittleWeasel/modules/language_spec.rb +112 -0
- data/spec/lib/LittleWeasel/modules/locale_spec.rb +95 -0
- data/spec/lib/LittleWeasel/modules/region_spec.rb +112 -0
- data/spec/lib/LittleWeasel/preprocessors/en_us/capitalize_preprocessor_spec.rb +34 -0
- data/spec/lib/LittleWeasel/preprocessors/preprocessed_word_spec.rb +105 -0
- data/spec/lib/LittleWeasel/preprocessors/preprocessed_word_validatable_spec.rb +143 -0
- data/spec/lib/LittleWeasel/preprocessors/preprocessed_words_spec.rb +77 -0
- data/spec/lib/LittleWeasel/preprocessors/preprocessed_words_validatable_spec.rb +58 -0
- data/spec/lib/LittleWeasel/preprocessors/word_preprocessor_managable_spec.rb +242 -0
- data/spec/lib/LittleWeasel/preprocessors/word_preprocessor_spec.rb +218 -0
- data/spec/lib/LittleWeasel/preprocessors/word_preprocessor_validatable_spec.rb +109 -0
- data/spec/lib/LittleWeasel/preprocessors/word_preprocessors_validatable_spec.rb +49 -0
- data/spec/lib/LittleWeasel/services/dictionary_cache_service_spec.rb +444 -0
- data/spec/lib/LittleWeasel/services/dictionary_creator_service_spec.rb +119 -0
- data/spec/lib/LittleWeasel/services/dictionary_file_loader_service_spec.rb +71 -0
- data/spec/lib/LittleWeasel/services/dictionary_metadata_service_spec.rb +279 -0
- data/spec/lib/LittleWeasel/word_results_spec.rb +275 -0
- data/spec/lib/LittleWeasel/workflow/workflow_spec.rb +20 -0
- data/spec/spec_helper.rb +117 -6
- data/spec/support/factory_bot.rb +15 -0
- data/spec/support/file_helpers.rb +46 -0
- data/spec/support/files/empty-dictionary.txt +0 -0
- data/{lib/dictionary → spec/support/files/en-US-big.txt} +262156 -31488
- data/spec/support/files/en-US-tagged.txt +26 -0
- data/spec/support/files/en-US.txt +26 -0
- data/spec/support/files/en.txt +26 -0
- data/spec/support/files/es-ES.txt +27 -0
- data/spec/support/files/es.txt +27 -0
- data/spec/support/general_helpers.rb +68 -0
- data/spec/support/shared_contexts.rb +107 -0
- data/spec/support/shared_examples.rb +105 -0
- metadata +378 -38
- 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
|