naughty_words 0.1.2 → 1.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 +4 -4
- data/README.md +239 -44
- data/lib/generators/naughty_words/install/install_generator.rb +32 -0
- data/lib/generators/naughty_words/install/templates/create_naughty_words_lists.rb +20 -0
- data/lib/generators/naughty_words/install/templates/initializer.rb +13 -0
- data/lib/generators/naughty_words/install/templates/word_list.rb +26 -0
- data/lib/naughty_words/base.rb +129 -25
- data/lib/naughty_words/config/deny_list.txt +14 -392
- data/lib/naughty_words/config.rb +46 -0
- data/lib/naughty_words/version.rb +1 -1
- data/lib/naughty_words/word_list.rb +40 -0
- data/lib/naughty_words.rb +10 -11
- metadata +114 -19
- data/.rspec +0 -3
- data/.rubocop.yml +0 -31
- data/CODE_OF_CONDUCT.md +0 -84
- data/Gemfile +0 -14
- data/Gemfile.lock +0 -61
- data/Rakefile +0 -12
- data/bin/console +0 -15
- data/bin/setup +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 19600db4679a9894f5cebde198af0a4162aaa602b618189fcf26ef90f3b6cf28
|
4
|
+
data.tar.gz: 34828a7a2036b0f8a959e0ff41b7e6615c18c4d5cef49bb210cea3d6ed7ba7d2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 97bc17c0ca656cbacf740519d3d9761342ae5fea4a4ac7858f2e018367f086db320a793d52c2fc59f3406190a7494ed33b9cfb3a9ea30114d98710824c255d18
|
7
|
+
data.tar.gz: fdb7d5fc29a1d10fb621d6efe6fc663e4a19b07ff5c5136d7b36a617af83254ace06d8740b3f689d9cf8ab3fcb24b3bd400cd71ed89ad5242458177e7705285f
|
data/README.md
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
# NaughtyWords
|
2
2
|
|
3
|
-
A
|
3
|
+
A Ruby gem for filtering profanity from text. Features include:
|
4
|
+
- Built-in deny and allow lists
|
5
|
+
- Database integration for custom word lists
|
6
|
+
- Runtime word overrides
|
7
|
+
- Word boundary matching (for checks)
|
8
|
+
- Case insensitive matching
|
9
|
+
- Optional severity-based filtering for DB deny words
|
4
10
|
|
5
11
|
## Installation
|
6
12
|
|
@@ -11,75 +17,264 @@ gem 'naughty_words'
|
|
11
17
|
```
|
12
18
|
|
13
19
|
And then execute:
|
14
|
-
|
15
|
-
|
20
|
+
```bash
|
21
|
+
bundle install
|
22
|
+
```
|
16
23
|
|
17
24
|
Or install it yourself as:
|
25
|
+
```bash
|
26
|
+
gem install naughty_words
|
27
|
+
```
|
28
|
+
|
29
|
+
## Basic Usage
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
# Check if a string contains profanity
|
33
|
+
NaughtyWords.check(string: "hello world") # => false
|
34
|
+
NaughtyWords.check(string: "fuck this") # => true
|
35
|
+
|
36
|
+
# Filter profanity from a string
|
37
|
+
NaughtyWords.filter(string: "hello world") # => "hello world"
|
38
|
+
NaughtyWords.filter(string: "fuck this") # => "**** this"
|
39
|
+
|
40
|
+
# Use custom replacement character
|
41
|
+
NaughtyWords.filter(string: "fuck this", replacement: "@") # => "@@@@ this"
|
42
|
+
```
|
18
43
|
|
19
|
-
|
44
|
+
## Configuration
|
20
45
|
|
21
|
-
|
46
|
+
Configure the gem's behavior:
|
22
47
|
|
23
|
-
**Check if a string includes a profanity**
|
24
|
-
The `check` method takes in a `string:` argument and will return a boolean.
|
25
48
|
```ruby
|
26
|
-
|
27
|
-
|
49
|
+
NaughtyWords.configure do |config|
|
50
|
+
# Match whole words only (default: true)
|
51
|
+
# When true: "fuck" matches "fuck" but not "fuckthis"
|
52
|
+
# When false: "fuck" matches both "fuck" and "fuckthis"
|
53
|
+
config.word_boundaries = true
|
28
54
|
|
29
|
-
|
30
|
-
|
55
|
+
# Use built-in word lists (default: true)
|
56
|
+
# Set to false to use only database or runtime overrides
|
57
|
+
config.use_built_in_lists = true
|
31
58
|
|
32
|
-
|
33
|
-
|
59
|
+
# Only consider DB deny words at or above this severity (optional)
|
60
|
+
# One of: "high", "medium", "low". nil means all severities.
|
61
|
+
config.minimum_severity = nil
|
62
|
+
end
|
63
|
+
|
64
|
+
# For tests or to return to defaults
|
65
|
+
NaughtyWords::Config.reset!
|
34
66
|
```
|
35
67
|
|
36
|
-
|
37
|
-
|
68
|
+
## Database Integration
|
69
|
+
|
70
|
+
The gem can use a database (ActiveRecord) to store custom word lists.
|
38
71
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
=> "hello world"
|
43
|
-
|
44
|
-
# passing a string with profanities will return the string with the profanity filtered out
|
45
|
-
NaughtyWords.filter(string: "hello asshole")
|
46
|
-
=> "hello *******"
|
47
|
-
|
48
|
-
# you can use in your own filter character by passing it in as an argument ("*" is by default)
|
49
|
-
NaughtyWords.filter(string: "hello asshole", replacement: "!")
|
50
|
-
=> "hello !!!!!!!"
|
72
|
+
1) Install migration, model, and initializer (recommended):
|
73
|
+
```bash
|
74
|
+
rails generate naughty_words:install
|
51
75
|
```
|
52
76
|
|
53
|
-
|
77
|
+
This adds:
|
78
|
+
- db/migrate/create_naughty_words_lists.rb
|
79
|
+
- app/models/naughty_words/word_list.rb
|
80
|
+
- config/initializers/naughty_words.rb
|
54
81
|
|
55
|
-
|
56
|
-
|
82
|
+
2) Add optional columns if you need them
|
83
|
+
|
84
|
+
If you plan to use `category`, `severity` ("high" | "medium" | "low"), or `metadata` (JSON), add these columns to your migration before running it. Example:
|
57
85
|
|
58
86
|
```ruby
|
59
|
-
|
87
|
+
change_table :naughty_words_lists do |t|
|
88
|
+
t.string :category # optional
|
89
|
+
t.string :severity # optional, one of "high", "medium", "low"
|
90
|
+
t.json :metadata, default: {} # optional
|
91
|
+
end
|
92
|
+
```
|
60
93
|
|
61
|
-
|
62
|
-
|
94
|
+
Then run:
|
95
|
+
```bash
|
96
|
+
rails db:migrate
|
97
|
+
```
|
63
98
|
|
64
|
-
|
99
|
+
3) Add words to your lists:
|
100
|
+
```ruby
|
101
|
+
# Basic usage
|
102
|
+
NaughtyWords::WordList.create!(word: "badword", list_type: "deny")
|
103
|
+
NaughtyWords::WordList.create!(word: "scunthorpe", list_type: "allow")
|
65
104
|
|
66
|
-
|
67
|
-
|
68
|
-
|
105
|
+
# With optional metadata (requires columns above)
|
106
|
+
NaughtyWords::WordList.create!(
|
107
|
+
word: "badword",
|
108
|
+
list_type: "deny",
|
109
|
+
context: "Added due to user complaints",
|
110
|
+
added_by: "john@example.com",
|
111
|
+
severity: "high",
|
112
|
+
category: "insults",
|
113
|
+
metadata: { reported_by: "forum_moderator" }
|
114
|
+
)
|
115
|
+
```
|
116
|
+
|
117
|
+
4) View your lists:
|
118
|
+
```ruby
|
119
|
+
# Get just the words
|
120
|
+
NaughtyWords.show_list(list: "deny")
|
121
|
+
# => ["badword", "otherbadword", ...]
|
122
|
+
|
123
|
+
# Get full records with metadata
|
124
|
+
records = NaughtyWords.show_list(list: "deny", include_metadata: true)
|
125
|
+
records.first
|
126
|
+
# => #<NaughtyWords::WordList ... word: "badword", list_type: "deny", ...>
|
127
|
+
|
128
|
+
# Query with metadata (if you use it)
|
129
|
+
words = NaughtyWords::WordList.where(list_type: "deny")
|
130
|
+
.where(added_by: "john@example.com")
|
131
|
+
.where("metadata->>'category' = ?", "insults")
|
132
|
+
```
|
133
|
+
|
134
|
+
### View only the built-in default lists
|
135
|
+
|
136
|
+
If you want to see the gem's built-in lists (ignoring anything in your database):
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
# Ensure built-ins are enabled (default: true)
|
140
|
+
NaughtyWords::Config.use_built_in_lists = true
|
141
|
+
|
142
|
+
# If you have no DB entries, this already shows the built-ins
|
143
|
+
NaughtyWords.show_list(list: "deny") # => built-in deny words
|
144
|
+
NaughtyWords.show_list(list: "allow") # => built-in allow words
|
145
|
+
|
146
|
+
# If you DO have DB entries and want to isolate the built-ins only:
|
147
|
+
built_in_deny = NaughtyWords.show_list(list: "deny") - NaughtyWords::WordList.deny_list.pluck(:word)
|
148
|
+
built_in_allow = NaughtyWords.show_list(list: "allow") - NaughtyWords::WordList.allow_list.pluck(:word)
|
149
|
+
|
150
|
+
built_in_deny.first(10)
|
151
|
+
```
|
152
|
+
|
153
|
+
5) Severity-based checks from DB (optional)
|
154
|
+
```ruby
|
155
|
+
# Only consider DB deny words at or above "medium"
|
156
|
+
NaughtyWords.configure { |c| c.minimum_severity = "medium" }
|
157
|
+
|
158
|
+
# Or query explicitly via scopes
|
159
|
+
NaughtyWords::WordList.by_severity("high").pluck(:word)
|
160
|
+
NaughtyWords::WordList.by_severity("high").by_category("insults").pluck(:word)
|
161
|
+
```
|
162
|
+
|
163
|
+
If you don't install the DB table, the gem will work using only the built-in lists.
|
164
|
+
|
165
|
+
## Runtime Overrides
|
166
|
+
|
167
|
+
Override word lists temporarily during runtime:
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
# Allow a word that's normally blocked
|
171
|
+
NaughtyWords::Config.allow_word("someword")
|
172
|
+
|
173
|
+
# Block a word that's normally allowed
|
174
|
+
NaughtyWords::Config.deny_word("otherword")
|
175
|
+
|
176
|
+
# Remove an override
|
177
|
+
NaughtyWords::Config.remove_override("someword")
|
178
|
+
```
|
179
|
+
|
180
|
+
Overrides:
|
181
|
+
- Take precedence over both built-in lists and database lists
|
182
|
+
- Are case insensitive
|
183
|
+
- Reset when your application restarts
|
184
|
+
- Perfect for testing or temporary customizations
|
185
|
+
|
186
|
+
## How It Works
|
187
|
+
|
188
|
+
1) When checking/filtering text:
|
189
|
+
- First checks runtime overrides
|
190
|
+
- Then checks built-in lists (if enabled)
|
191
|
+
- Then checks database lists (if present)
|
192
|
+
|
193
|
+
2) Word matching is:
|
194
|
+
- Always case insensitive
|
195
|
+
- Configurable for word boundaries (affects checks)
|
196
|
+
- Handles special characters and spaces
|
197
|
+
|
198
|
+
3) Priority order:
|
199
|
+
1. Runtime overrides (highest)
|
200
|
+
2. Built-in lists
|
201
|
+
3. Database lists (lowest)
|
202
|
+
|
203
|
+
4) Filtering details:
|
204
|
+
- Replacement proceeds longest-to-shortest denied words.
|
205
|
+
- Filtering masks any occurrence of denied words (case-insensitive), regardless of `word_boundaries`.
|
206
|
+
- Example: with boundaries on, `check` may pass for “scunthorpe”, but if “cunt” is denied, `filter` will still mask “cunt” inside “scunthorpe”.
|
207
|
+
|
208
|
+
## Default list philosophy
|
209
|
+
|
210
|
+
The built-in deny list is intentionally minimal and neutral by default.
|
211
|
+
|
212
|
+
- Focuses on single-word profanities and slurs only
|
213
|
+
- Avoids medical/sexual terms and general sexual content
|
214
|
+
- Avoids multi-word phrases and “moral policing”
|
215
|
+
|
216
|
+
Customize to your community via the database layer:
|
217
|
+
- Add phrases (e.g., “eat my ass”) to `naughty_words_lists` with `category`/`severity`
|
218
|
+
- Use `minimum_severity` to tune strictness globally
|
219
|
+
- Use runtime overrides for temporary exceptions
|
220
|
+
|
221
|
+
## Examples
|
222
|
+
|
223
|
+
### Basic Filtering
|
224
|
+
```ruby
|
225
|
+
# Simple profanity check
|
226
|
+
NaughtyWords.check(string: "hello world") # => false
|
227
|
+
NaughtyWords.check(string: "fuck this") # => true
|
228
|
+
|
229
|
+
# Filter with default replacement (*)
|
230
|
+
NaughtyWords.filter(string: "fuck this") # => "**** this"
|
231
|
+
|
232
|
+
# Custom replacement character
|
233
|
+
NaughtyWords.filter(string: "fuck this", replacement: "@") # => "@@@@ this"
|
234
|
+
```
|
235
|
+
|
236
|
+
### Word Boundaries
|
237
|
+
```ruby
|
238
|
+
# With word_boundaries = true (default)
|
239
|
+
NaughtyWords.check(string: "fuck") # => true
|
240
|
+
NaughtyWords.check(string: "fuckthis") # => false
|
241
|
+
|
242
|
+
# With word_boundaries = false
|
243
|
+
NaughtyWords.configure { |c| c.word_boundaries = false }
|
244
|
+
NaughtyWords.check(string: "fuckthis") # => true
|
69
245
|
```
|
70
246
|
|
71
|
-
|
247
|
+
### Database Integration
|
248
|
+
```ruby
|
249
|
+
# Add custom words
|
250
|
+
NaughtyWords::WordList.create!(word: "badword", list_type: "deny")
|
251
|
+
NaughtyWords::WordList.create!(word: "goodword", list_type: "allow")
|
252
|
+
|
253
|
+
# View lists with metadata
|
254
|
+
NaughtyWords.show_list(list: "deny", include_metadata: true)
|
255
|
+
# => [#<NaughtyWords::WordList id: 1, word: "badword", list_type: "deny", created_at: ...>]
|
256
|
+
```
|
257
|
+
|
258
|
+
### Runtime Overrides
|
259
|
+
```ruby
|
260
|
+
# Override the built-in lists
|
261
|
+
NaughtyWords::Config.allow_word("fuck") # Allow this word
|
262
|
+
NaughtyWords.check(string: "fuck") # => false
|
72
263
|
|
73
|
-
|
264
|
+
# Multiple overrides
|
265
|
+
NaughtyWords::Config.allow_word("fuck")
|
266
|
+
NaughtyWords::Config.allow_word("shit")
|
267
|
+
NaughtyWords.filter(string: "fuck this shit") # => "fuck this shit"
|
268
|
+
|
269
|
+
# Remove overrides
|
270
|
+
NaughtyWords::Config.remove_override("fuck")
|
271
|
+
NaughtyWords.check(string: "fuck") # => true (back to default)
|
272
|
+
```
|
74
273
|
|
75
274
|
## Contributing
|
76
275
|
|
77
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/jaarnie/naughty_words
|
276
|
+
Bug reports and pull requests are welcome on GitHub at `https://github.com/jaarnie/naughty_words`.
|
78
277
|
|
79
278
|
## License
|
80
279
|
|
81
280
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
82
|
-
|
83
|
-
## Code of Conduct
|
84
|
-
|
85
|
-
Everyone interacting in the NaughtyWords project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/naughty_words/blob/master/CODE_OF_CONDUCT.md).
|
@@ -0,0 +1,32 @@
|
|
1
|
+
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module NaughtyWords
|
5
|
+
module Generators
|
6
|
+
class InstallGenerator < Rails::Generators::Base
|
7
|
+
include Rails::Generators::Migration
|
8
|
+
|
9
|
+
source_root File.expand_path("templates", __dir__)
|
10
|
+
|
11
|
+
def self.next_migration_number(path)
|
12
|
+
next_migration_number = current_migration_number(path) + 1
|
13
|
+
ActiveRecord::Migration.next_migration_number(next_migration_number)
|
14
|
+
end
|
15
|
+
|
16
|
+
def copy_migrations
|
17
|
+
migration_template(
|
18
|
+
"create_naughty_words_lists.rb",
|
19
|
+
"db/migrate/create_naughty_words_lists.rb"
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
def copy_models
|
24
|
+
template "word_list.rb", "app/models/naughty_words/word_list.rb"
|
25
|
+
end
|
26
|
+
|
27
|
+
def copy_initializer
|
28
|
+
template "initializer.rb", "config/initializers/naughty_words.rb"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CreateNaughtyWordsLists < ActiveRecord::Migration[7.0]
|
4
|
+
def change
|
5
|
+
create_table :naughty_words_lists do |t|
|
6
|
+
t.string :word, null: false
|
7
|
+
t.string :list_type, null: false # 'deny' or 'allow'
|
8
|
+
t.string :category
|
9
|
+
t.string :severity # "high" | "medium" | "low"
|
10
|
+
t.text :context
|
11
|
+
t.string :added_by
|
12
|
+
t.json :metadata, default: {}
|
13
|
+
t.timestamps
|
14
|
+
|
15
|
+
t.index [:word, :list_type], unique: true
|
16
|
+
t.index :category
|
17
|
+
t.index :severity
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
NaughtyWords.configure do |config|
|
4
|
+
# Match whole words only (default: true). Set false to allow substrings.
|
5
|
+
config.word_boundaries = true
|
6
|
+
|
7
|
+
# Include built-in allow/deny lists (default: true).
|
8
|
+
config.use_built_in_lists = true
|
9
|
+
|
10
|
+
# Optional: only consider DB deny words at or above this severity.
|
11
|
+
# One of: "high", "medium", "low"; nil means all severities.
|
12
|
+
config.minimum_severity = nil
|
13
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NaughtyWords
|
4
|
+
class WordList < ActiveRecord::Base
|
5
|
+
self.table_name = "naughty_words_lists"
|
6
|
+
|
7
|
+
validates :word, presence: true
|
8
|
+
validates :list_type, presence: true, inclusion: { in: %w[deny allow] }
|
9
|
+
validates :word, uniqueness: { scope: :list_type }
|
10
|
+
validates :severity, inclusion: { in: %w[high medium low], allow_nil: true }
|
11
|
+
|
12
|
+
scope :deny_list, -> { where(list_type: "deny") }
|
13
|
+
scope :allow_list, -> { where(list_type: "allow") }
|
14
|
+
scope :by_category, ->(category) { where(category: category) }
|
15
|
+
scope :by_severity, ->(severity) { where(severity: severity) }
|
16
|
+
scope :added_by, ->(user) { where(added_by: user) }
|
17
|
+
|
18
|
+
before_save :normalize_word
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def normalize_word
|
23
|
+
self.word = word.strip.downcase if word.present?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/naughty_words/base.rb
CHANGED
@@ -1,56 +1,160 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "config"
|
4
|
+
|
3
5
|
module NaughtyWords
|
4
6
|
class Base
|
5
7
|
class << self
|
6
8
|
def profanity?(string:)
|
7
|
-
|
8
|
-
|
9
|
+
validate_input!(string)
|
10
|
+
normalized_string = normalize_string(string)
|
11
|
+
|
12
|
+
return false if word_in_list?(normalized_string, Config.allow_overrides)
|
13
|
+
return true if word_in_list?(normalized_string, Config.deny_overrides)
|
14
|
+
|
15
|
+
return false if Config.use_built_in_lists && word_in_list?(normalized_string, allow_list_from_files)
|
16
|
+
return false if word_in_list?(normalized_string, allow_list_from_db)
|
17
|
+
|
18
|
+
return true if Config.use_built_in_lists && word_in_list?(normalized_string, deny_list_from_files)
|
19
|
+
return true if word_in_list?(normalized_string, deny_list_from_db)
|
20
|
+
|
21
|
+
false
|
22
|
+
end
|
23
|
+
|
24
|
+
def filter(string:, replacement: "*")
|
25
|
+
validate_input!(string)
|
26
|
+
validate_replacement!(replacement)
|
27
|
+
result = string.dup
|
28
|
+
|
29
|
+
denied_words = Config.deny_overrides.dup
|
30
|
+
denied_words += deny_list_from_files if Config.use_built_in_lists
|
31
|
+
denied_words += deny_list_from_db
|
32
|
+
denied_words -= Config.allow_overrides # Remove any allowed overrides
|
33
|
+
denied_words = denied_words.sort_by(&:length).reverse
|
34
|
+
|
35
|
+
denied_words.each do |word|
|
36
|
+
next if word.empty?
|
37
|
+
result.gsub!(/#{Regexp.escape(word)}/i, replacement * word.length)
|
9
38
|
end
|
10
39
|
|
11
|
-
|
12
|
-
|
40
|
+
result
|
41
|
+
end
|
42
|
+
|
43
|
+
def show_list(list:, include_metadata: false)
|
44
|
+
validate_list!(list)
|
45
|
+
|
46
|
+
if include_metadata && defined?(WordList)
|
47
|
+
WordList.where(list_type: list)
|
48
|
+
else
|
49
|
+
words = []
|
50
|
+
words += (list == "deny" ? deny_list_from_files : allow_list_from_files) if Config.use_built_in_lists
|
51
|
+
words += (list == "deny" ? deny_list_from_db : allow_list_from_db)
|
52
|
+
words
|
13
53
|
end
|
54
|
+
end
|
14
55
|
|
15
|
-
|
56
|
+
private
|
57
|
+
|
58
|
+
def deny_list_from_files
|
59
|
+
@deny_list_from_files ||= load_list(deny_list_path)
|
60
|
+
end
|
61
|
+
|
62
|
+
def allow_list_from_files
|
63
|
+
@allow_list_from_files ||= load_list(allow_list_path)
|
16
64
|
end
|
17
65
|
|
18
|
-
def
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
66
|
+
def deny_list_from_db
|
67
|
+
return [] unless db_table_available?
|
68
|
+
query = WordList.deny_list
|
69
|
+
|
70
|
+
if Config.minimum_severity
|
71
|
+
severities = %w[high medium low]
|
72
|
+
min_index = severities.index(Config.minimum_severity)
|
73
|
+
return [] unless min_index
|
74
|
+
allowed_severities = severities[0..min_index]
|
75
|
+
query = query.where(severity: allowed_severities)
|
23
76
|
end
|
24
77
|
|
25
|
-
|
78
|
+
query.pluck(:word)
|
79
|
+
rescue StandardError
|
80
|
+
[]
|
81
|
+
end
|
82
|
+
|
83
|
+
def allow_list_from_db
|
84
|
+
return [] unless db_table_available?
|
85
|
+
WordList.allow_list.pluck(:word)
|
86
|
+
rescue StandardError
|
87
|
+
[]
|
88
|
+
end
|
89
|
+
|
90
|
+
def deny_list_path
|
91
|
+
File.join(File.dirname(File.expand_path(__FILE__)), "config/deny_list.txt")
|
92
|
+
end
|
93
|
+
|
94
|
+
def allow_list_path
|
95
|
+
File.join(File.dirname(File.expand_path(__FILE__)), "config/allow_list.txt")
|
96
|
+
end
|
97
|
+
|
98
|
+
def load_list(path)
|
99
|
+
return [] unless Config.use_built_in_lists
|
100
|
+
File.readlines(path, chomp: true).reject(&:empty?)
|
101
|
+
rescue Errno::ENOENT
|
102
|
+
raise Error, "List file not found: #{path}"
|
103
|
+
rescue IOError => e
|
104
|
+
raise Error, "Failed to read list: #{e.message}"
|
26
105
|
end
|
27
106
|
|
28
|
-
def
|
29
|
-
|
30
|
-
|
107
|
+
def word_in_list?(string, list)
|
108
|
+
list.any? do |word|
|
109
|
+
normalized_word = normalize_string(word)
|
110
|
+
word_match?(string, normalized_word)
|
31
111
|
end
|
32
112
|
end
|
33
113
|
|
34
|
-
def
|
35
|
-
|
36
|
-
|
114
|
+
def normalize_string(str)
|
115
|
+
str.downcase
|
116
|
+
end
|
117
|
+
|
118
|
+
def word_pattern(word)
|
119
|
+
escaped = Regexp.escape(word)
|
120
|
+
if Config.word_boundaries
|
121
|
+
/(?:^|[^a-zA-Z0-9])#{escaped}(?:$|[^a-zA-Z0-9])/i
|
37
122
|
else
|
38
|
-
|
123
|
+
/#{escaped}/i
|
39
124
|
end
|
40
125
|
end
|
41
126
|
|
42
|
-
|
127
|
+
def word_match?(string, word)
|
128
|
+
pattern = Config.word_boundaries ?
|
129
|
+
/(?:^|[^a-zA-Z0-9])#{Regexp.escape(word)}(?:$|[^a-zA-Z0-9])/i :
|
130
|
+
/#{Regexp.escape(word)}/i
|
131
|
+
string.match?(pattern)
|
132
|
+
end
|
43
133
|
|
44
|
-
def
|
45
|
-
|
134
|
+
def validate_input!(string)
|
135
|
+
raise ArgumentError, "Input string cannot be nil" if string.nil?
|
136
|
+
raise ArgumentError, "Input string must be a String" unless string.is_a?(String)
|
137
|
+
end
|
46
138
|
|
47
|
-
|
139
|
+
def validate_replacement!(replacement)
|
140
|
+
raise ArgumentError, "Replacement cannot be nil" if replacement.nil?
|
141
|
+
raise ArgumentError, "Replacement must be a String" unless replacement.is_a?(String)
|
142
|
+
raise ArgumentError, "Replacement cannot be empty" if replacement.empty?
|
48
143
|
end
|
49
144
|
|
50
|
-
def
|
51
|
-
|
145
|
+
def validate_list!(list)
|
146
|
+
valid_lists = ["deny", "allow"]
|
147
|
+
unless valid_lists.include?(list)
|
148
|
+
raise ArgumentError, "Invalid list type. Must be one of: #{valid_lists.join(', ')}"
|
149
|
+
end
|
150
|
+
end
|
52
151
|
|
53
|
-
|
152
|
+
def db_table_available?
|
153
|
+
return false unless defined?(WordList) && defined?(ActiveRecord::Base)
|
154
|
+
conn = ActiveRecord::Base.connection
|
155
|
+
conn.data_source_exists?(WordList.table_name)
|
156
|
+
rescue StandardError
|
157
|
+
false
|
54
158
|
end
|
55
159
|
end
|
56
160
|
end
|