Class: Insika::Harvest::NegativeList

Inherits:
Object
  • Object
show all
Defined in:
lib/insika/harvest/negative_list.rb

Overview

C3 — the versioned negative list : things the harvester must never propose. Pure value object — never touches a store, never authors a rule (D4: the engine applies it, the forge authors it). parse accepts BOTH the markdown file shape (frontmatter + rule bullets) and the pack-data array shape ({ rule, pattern, note } hashes), so a rule cannot exist in one and not the other (E2's drift guard).

A rule is a phrase or a regex. Phrases match case/accent-folded at WORD boundaries (the hygiene — "NÃO DEVOLVEMOS" is the same word as "não devolvemos"); regexes match the raw OR the folded text (the author may write either spelling, /…/flags honored). matches_name is the stricter SUBSTRING reading the CI spec uses on skill names.

Defined Under Namespace

Classes: Rule

Constant Summary collapse

FOLD =

The pt-BR vowel fold — stdlib only, no UnicodeUtils. Both sides of a phrase match are folded through this map.

{
  "á" => "a", "à" => "a", "ã" => "a", "â" => "a",
  "é" => "e", "ê" => "e",
  "í" => "i",
  "ó" => "o", "õ" => "o", "ô" => "o",
  "ú" => "u", "ü" => "u",
  "ç" => "c"
}.freeze
FOLD_RE =
Regexp.union(FOLD.keys)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(rules:) ⇒ NegativeList

Returns a new instance of NegativeList.



147
148
149
# File 'lib/insika/harvest/negative_list.rb', line 147

def initialize(rules:)
  @rules = rules
end

Instance Attribute Details

#rulesObject (readonly)

Returns the value of attribute rules.



32
33
34
# File 'lib/insika/harvest/negative_list.rb', line 32

def rules
  @rules
end

Class Method Details

.parse(raw) ⇒ Object

A file line: "- rule-id — <phrase-or-/regex/> — " (the note may be empty; a phrase may be quoted — "concorrente" — and the quotes are not part of the pattern, matching the pack-array shape). A malformed line refuses the WHOLE list — a half-parsed list silently admits what the store banned.



40
41
42
43
44
# File 'lib/insika/harvest/negative_list.rb', line 40

def self.parse(raw)
  parse!(raw)
rescue Insika::ValidationError
  nil
end

.parse!(raw) ⇒ Object

-> NegativeList. Raises Insika::ValidationError naming the defect — the E2 seed path (insika harvest:negative import) uses THIS one.



48
49
50
# File 'lib/insika/harvest/negative_list.rb', line 48

def self.parse!(raw)
  new(rules: parse_rules(raw))
end

Instance Method Details

#matches(text) ⇒ Object

-> [Rule] every rule whose pattern matched the text (word-boundary, case + accent folded for phrases; raw-or-folded for regexes). Empty = clean.



154
155
156
157
158
# File 'lib/insika/harvest/negative_list.rb', line 154

def matches(text)
  haystack = text.to_s
  folded = fold(haystack)
  @rules.select { |r| rule_match?(r, haystack, folded) }
end

#matches_name(name) ⇒ Object

-> [Rule] every rule whose pattern appears as a SUBSTRING (the stricter reading the CI spec uses on skill NAMES — a name containing a banned token is banned even mid-word).



163
164
165
166
167
# File 'lib/insika/harvest/negative_list.rb', line 163

def matches_name(name)
  haystack = name.to_s
  folded = fold(haystack)
  @rules.select { |r| name_match?(r, haystack, folded) }
end

#reject_counts(text) ⇒ Object

-> Hash { rule => count } — what the run log records.



170
171
172
# File 'lib/insika/harvest/negative_list.rb', line 170

def reject_counts(text)
  matches(text).each_with_object({}) { |r, acc| acc[r.rule] = (acc[r.rule] || 0) + 1 }
end