Class: Scrubber::Instance
- Inherits:
-
Object
- Object
- Scrubber::Instance
- Defined in:
- lib/scrubber/instance.rb,
sig/scrubber_rb.rbs
Overview
The compiled engine. Frozen; safe to share across threads.
Constant Summary collapse
- REPLACEMENTS =
%i[label mask hash remove].freeze
- CHUNK_SIZE =
Read in 1MB slices, and never cut closer than this to the end of the buffer, so a match straddling a chunk boundary still sees both halves.
1024 * 1024
- CARRY_SIZE =
8 * 1024
Instance Attribute Summary collapse
-
#custom ⇒ custom_patterns
readonly
Returns the value of attribute custom.
-
#detectors ⇒ Array[Symbol]
readonly
Returns the value of attribute detectors.
-
#hash_salt ⇒ String?
readonly
Returns the value of attribute hash_salt.
-
#replacement ⇒ Symbol
readonly
Returns the value of attribute replacement.
Class Method Summary collapse
Instance Method Summary collapse
-
#cache_key ⇒ Array[untyped]
Config identity, used to memoize module-level
Scrubber.scrubcalls. -
#detect(text) ⇒ Array[Match]
Find matches without replacing them.
-
#initialize(detectors: Scrubber::DEFAULTS, custom: {}, replacement: :label, hash_salt: nil) ⇒ Instance
constructor
A new instance of Instance.
- #inspect ⇒ Object
-
#match?(text) ⇒ Boolean
True if anything at all would be redacted.
-
#rule_count ⇒ Integer
How many compiled patterns back this instance.
-
#scrub(text) ⇒ String
Redact every match in
text, returning a new String with the same encoding. -
#scrub!(text) ⇒ String
Redact in place.
-
#scrub_file(input_path, output_path, chunk_size: CHUNK_SIZE) ⇒ Integer
Stream a file through the engine.
Constructor Details
#initialize(detectors: Scrubber::DEFAULTS, custom: {}, replacement: :label, hash_salt: nil) ⇒ Instance
Returns a new instance of Instance.
24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
# File 'lib/scrubber/instance.rb', line 24 def initialize(detectors: Scrubber::DEFAULTS, custom: {}, replacement: :label, hash_salt: nil) @detectors = normalize_detectors(detectors) @custom = normalize_custom(custom) @replacement = normalize_replacement(replacement) @hash_salt = hash_salt&.to_s @native = Native.new( @detectors.map(&:to_s), @custom.map { |name, regexp| [name.to_s, regexp.source, regexp.] }, @replacement.to_s, @hash_salt ) freeze end |
Instance Attribute Details
#custom ⇒ custom_patterns (readonly)
Returns the value of attribute custom.
22 23 24 |
# File 'lib/scrubber/instance.rb', line 22 def custom @custom end |
#detectors ⇒ Array[Symbol] (readonly)
Returns the value of attribute detectors.
22 23 24 |
# File 'lib/scrubber/instance.rb', line 22 def detectors @detectors end |
#hash_salt ⇒ String? (readonly)
Returns the value of attribute hash_salt.
22 23 24 |
# File 'lib/scrubber/instance.rb', line 22 def hash_salt @hash_salt end |
#replacement ⇒ Symbol (readonly)
Returns the value of attribute replacement.
22 23 24 |
# File 'lib/scrubber/instance.rb', line 22 def replacement @replacement end |
Class Method Details
.cache_key(detectors:, custom:, replacement:, hash_salt:) ⇒ Object
122 123 124 125 126 127 128 129 |
# File 'lib/scrubber/instance.rb', line 122 def self.cache_key(detectors:, custom:, replacement:, hash_salt:) customs = custom.to_a.map do |name, regexp| source = regexp.respond_to?(:source) ? regexp.source : regexp.to_s = regexp.respond_to?(:options) ? regexp. : 0 [name.to_sym, source, ] end [Array(detectors).map(&:to_sym).sort, customs.sort, replacement.to_sym, hash_salt&.to_s] end |
Instance Method Details
#cache_key ⇒ Array[untyped]
Config identity, used to memoize module-level Scrubber.scrub calls.
115 116 117 118 119 120 |
# File 'lib/scrubber/instance.rb', line 115 def cache_key self.class.cache_key( detectors: @detectors, custom: @custom, replacement: @replacement, hash_salt: @hash_salt ) end |
#detect(text) ⇒ Array[Match]
Find matches without replacing them.
Scrubber.detect("mail nik@example.com")
# => [#<Scrubber::Match type=:email 5...20 preview="n**@e******.com">]
Offsets are character offsets into text, not byte offsets, so they index
the Ruby string correctly even when it contains emoji or Devanagari.
66 67 68 69 70 71 72 73 74 |
# File 'lib/scrubber/instance.rb', line 66 def detect(text) str = coerce(text) # Only UTF-8 needs byte->character conversion; in single-byte encodings # the two are the same number. char_offsets = str.encoding == Encoding::UTF_8 @native.detect(str, char_offsets).map do |type, from, to, preview| Match.new(type: type.to_sym, begin: from, end: to, preview: preview) end end |
#inspect ⇒ Object
109 110 111 112 |
# File 'lib/scrubber/instance.rb', line 109 def inspect "#<Scrubber::Instance detectors=#{@detectors.size} rules=#{rule_count} " \ "replacement=#{@replacement.inspect}>" end |
#match?(text) ⇒ Boolean
True if anything at all would be redacted. Cheaper than scrub when you
only need a yes/no (an audit check, a test assertion, a CI gate).
78 79 80 81 |
# File 'lib/scrubber/instance.rb', line 78 def match?(text) # `detect` stops at the match list; it never builds the output string. !@native.detect(coerce(text), false).empty? end |
#rule_count ⇒ Integer
How many compiled patterns back this instance. Detectors expand to more
than one rule each in a few cases (:api_key alone is ~19).
105 106 107 |
# File 'lib/scrubber/instance.rb', line 105 def rule_count @native.rule_count end |
#scrub(text) ⇒ String
Redact every match in text, returning a new String with the same
encoding. The input is never mutated, and frozen input is fine.
41 42 43 44 45 46 47 |
# File 'lib/scrubber/instance.rb', line 41 def scrub(text) str = coerce(text) replaced = @native.scrub(str) return str.dup if replaced.nil? replaced.force_encoding(str.encoding) end |
#scrub!(text) ⇒ String
Redact in place. Returns the same object, so it raises FrozenError on a frozen string exactly like every other Ruby bang method.
51 52 53 54 55 56 57 |
# File 'lib/scrubber/instance.rb', line 51 def scrub!(text) str = coerce(text) replaced = @native.scrub(str) return str if replaced.nil? str.replace(replaced.force_encoding(str.encoding)) end |
#scrub_file(input_path, output_path, chunk_size: CHUNK_SIZE) ⇒ Integer
Stream a file through the engine.
Reads in 1MB chunks and cuts each chunk at the last line break before an 8KB carry window, so a match spanning a chunk boundary is never sliced in half. Multi-line PEM blocks get an extra guard: if a chunk would end between BEGIN and END, the cut moves back before the BEGIN.
Returns the number of bytes written.
91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/scrubber/instance.rb', line 91 def scrub_file(input_path, output_path, chunk_size: CHUNK_SIZE) written = 0 File.open(input_path, "rb") do |input| File.open(output_path, "wb") do |output| each_safe_chunk(input, chunk_size) do |chunk| written += output.write(@native.scrub(chunk) || chunk) end end end written end |