vicary 0.2.16 → 0.2.17

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4e78fbd85fa4dde2e481bbf8b5349ceda35e4ab98fbf9ad0b336761be3f6379d
4
- data.tar.gz: b9ad9cd2ebeaffa8b438c4b051d378edb47cfc86f1117cce68732f6cd3f2cdd3
3
+ metadata.gz: 381ed16c17b2aa427a72b173af92825ad08db621b13e562fdc7977d41d760a26
4
+ data.tar.gz: 9e7a68508af7f405888c9a0e5ebc9b7993b3f0e7de6149c91156cf7c23b9fe29
5
5
  SHA512:
6
- metadata.gz: 9ef2a213c75f9cf539620a393b20e7f2521173fb14a2bfbc2d6753654a8a19f19875b143a6b4240cc4ce09b01667cccc4d1da8d2e4c52d774bc9678c52eb13cb
7
- data.tar.gz: 7102716c3687292aff85bc8191e0fbbe65ef457930f9fcce2c5b78e58ed3134a8e69d5b07f728a09438f0aeda84f97753a82419e91478ebd401ba99df5c2dd47
6
+ metadata.gz: 87ffdbf4edb505a3c1d5faca88c817a5a1ad44b093fbfc329f99958c05a528b078c3fdcc1d97c9e6ffca2d255be617cb3912d6d6e2a9735f02861600aa0f0ce6
7
+ data.tar.gz: ec871699f441d07106e5f32981eb9a34f35ce2935f75a7f78163fc0ae277dc1afbe8fdd042e3dbf9676b8a4493dc9fb8078c36a0e3ba9cb65804cad79b17c93b
@@ -300,6 +300,39 @@ module Vicary
300
300
  most much many few several enough
301
301
  ]).freeze
302
302
 
303
+ # Phonetic spellings of English stop words, each with the stop word it
304
+ # spells. Young writers, and young Spanish-L1 writers most of all,
305
+ # spell the closed-class words by ear — "dey sai ai smel", "so ai dont get
306
+ # mai clos dirtier" — and several of those spellings are given names the
307
+ # tier knows, so the lowercase route masked "sai ai" and "mai clos" and the
308
+ # sentence-initial rule let the tier vouch for "Ai", "Mi" and "De". A
309
+ # spelling of a stop word is the same word, so in a document that writes
310
+ # this way it is a stop word too. Only the spellings are read; the pairing
311
+ # is documentation. See `phonetic_function_words` for when a document
312
+ # qualifies.
313
+ PHONETIC_FUNCTION_WORDS = {
314
+ "ai" => "i", "becuz" => "because", "bicuz" => "because", "bot" => "but",
315
+ "cud" => "could", "da" => "the", "dan" => "than", "dat" => "that",
316
+ "de" => "the", "deir" => "their", "dem" => "them", "den" => "then",
317
+ "dere" => "there", "dese" => "these", "dey" => "they", "dis" => "this",
318
+ "dose" => "those", "frum" => "from", "hiz" => "his", "iz" => "is",
319
+ "mai" => "my", "mi" => "me", "sai" => "say", "sed" => "said",
320
+ "shud" => "should", "sum" => "some", "tu" => "to", "wat" => "what",
321
+ "wen" => "when", "wud" => "would", "wuz" => "was", "yu" => "you",
322
+ "yur" => "your",
323
+ }.freeze
324
+
325
+ # Distinct phonetic spellings a document must write in lower case before
326
+ # any of them is read as a word rather than a name. One is a coincidence —
327
+ # a friend called Mai — two different ones is a writer spelling by ear.
328
+ PHONETIC_REGISTER_MIN = 2
329
+
330
+ PHONETIC_TOKEN = Regexp.new(
331
+ "(?<![A-Za-z'’-])(?:" +
332
+ PHONETIC_FUNCTION_WORDS.keys.sort_by { |w| [-w.length, w] }.join("|") +
333
+ ")(?![A-Za-z'’-])"
334
+ )
335
+
303
336
  HONORIFIC_SET = Set.new(HONORIFICS.map(&:downcase)).freeze
304
337
 
305
338
  # One capitalised word, hyphens and apostrophes included so
@@ -742,6 +775,20 @@ module Vicary
742
775
  strip(without_clitic(strip(token.downcase, ".,")), "'’")
743
776
  end
744
777
 
778
+ # The phonetic spellings this document uses as words, to be read as
779
+ # stops. Empty unless the document writes at least PHONETIC_REGISTER_MIN
780
+ # distinct spellings in lower case. When it does, the spellings it writes
781
+ # in lower case — the writer's own testimony that the token is a word to
782
+ # them — less any it also capitalises mid-sentence, which is the opposite
783
+ # testimony and wins.
784
+ def phonetic_function_words(text, written_as_a_capital = Set.new)
785
+ seen = Set.new
786
+ each_match(text, PHONETIC_TOKEN) { |m| seen << m[0] }
787
+ return Set.new if seen.size < PHONETIC_REGISTER_MIN
788
+
789
+ Set.new(seen.reject { |w| written_as_a_capital.include?(w) })
790
+ end
791
+
745
792
  # Whether a capital on `token` mid-sentence is a mistake rather than a
746
793
  # construction — the narrow half of the stoplist.
747
794
  #
@@ -846,7 +893,11 @@ module Vicary
846
893
  # the stoplist so that a bare "Mrs." cannot become a candidate on its own,
847
894
  # but "Mrs. Okonkwo" has to stay whole: masking only the surname leaves the
848
895
  # relationship and the surname's position in the text.
849
- def trim(tokens)
896
+ #
897
+ # `phonetic` is `phonetic_function_words` for the document: spellings read
898
+ # as stop words here. Except a particle *inside* a run — "Maria de Cruz"
899
+ # keeps its `de` however the rest of the document spells "the".
900
+ def trim(tokens, phonetic = Set.new)
850
901
  runs = []
851
902
  current = []
852
903
  tokens.each_with_index do |token, index|
@@ -854,7 +905,10 @@ module Vicary
854
905
  HONORIFIC_SET.include?(strip(token.downcase, ".,")) &&
855
906
  index + 1 < tokens.length &&
856
907
  !stop?(tokens[index + 1])
857
- if stop?(token) && !introduces_a_name
908
+ spelled_stop = !phonetic.empty? && phonetic.include?(fold(token)) &&
909
+ !(index.positive? && index < tokens.length - 1 &&
910
+ PARTICLES.include?(token))
911
+ if (stop?(token) || spelled_stop) && !introduces_a_name
858
912
  unless current.empty?
859
913
  runs << current
860
914
  current = []
@@ -1849,7 +1903,7 @@ module Vicary
1849
1903
  # capitalisation signal, and the seed stands on the given-name tier
1850
1904
  # alone.
1851
1905
  def find_lowercase_candidates(text, is_given, protected_span, corroborate = nil,
1852
- settlement = nil)
1906
+ settlement = nil, phonetic = Set.new)
1853
1907
  tokens = each_match(text, LOWER_TOKEN).map do |m|
1854
1908
  [m[0], m.begin(0), m.begin(0) + m[0].length]
1855
1909
  end
@@ -1857,7 +1911,10 @@ module Vicary
1857
1911
  index = 0
1858
1912
  while index < tokens.length
1859
1913
  word, start, = tokens[index]
1860
- if stop?(word) || !is_given.call(word)
1914
+ # A phonetic spelling may not SEED a span ("sai ai smel"), but may
1915
+ # still be the token a real seed reaches ("jeni sai") — see the
1916
+ # reference.
1917
+ if stop?(word) || phonetic.include?(word) || !is_given.call(word)
1861
1918
  index += 1
1862
1919
  next
1863
1920
  end
@@ -1996,6 +2053,10 @@ module Vicary
1996
2053
  )
1997
2054
  written_as_a_capital = casing.written_as_a_capital
1998
2055
  lower_cased = casing.written_in_lower_case
2056
+ # Read off the same casing pass, so a spelling the writer capitalises
2057
+ # mid-sentence is never one of them; and only where a given-name oracle
2058
+ # exists, because it only withdraws what the tier proposed or vouched for.
2059
+ phonetic = given_name.nil? ? Set.new : phonetic_function_words(text, written_as_a_capital)
1999
2060
  stray_capitals = mid_sentence_corroboration && !given_name.nil? &&
2000
2061
  capitalises_ordinary_words?(text, headings)
2001
2062
 
@@ -2007,7 +2068,7 @@ module Vicary
2007
2068
  tokens = words(span)
2008
2069
  # A long all-caps run means the capitalisation told us nothing, so the
2009
2070
  # stoplist is carrying the whole decision.
2010
- trim(tokens).each do |run|
2071
+ trim(tokens, phonetic).each do |run|
2011
2072
  next if run.empty?
2012
2073
 
2013
2074
  joined = run.join(" ")
@@ -2078,7 +2139,7 @@ module Vicary
2078
2139
  # evidence to offer and is better served by it.
2079
2140
  find_lowercase_candidates(
2080
2141
  text, given_name, is_protected,
2081
- habit == LOWERCASE ? nil : written_as_a_capital, settlement
2142
+ habit == LOWERCASE ? nil : written_as_a_capital, settlement, phonetic
2082
2143
  ).each do |candidate|
2083
2144
  next if claimed.any? { |s, e| candidate.start < e && candidate.end > s }
2084
2145
 
@@ -6,5 +6,5 @@ module Vicary
6
6
  # Shared across all three front doors on purpose: one detector, one number. A
7
7
  # gem 0.3.0 that corresponds to nothing on PyPI cannot be reasoned about, and
8
8
  # the parity claim is between *versions*, not between package names.
9
- VERSION = "0.2.16"
9
+ VERSION = "0.2.17"
10
10
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vicary
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.16
4
+ version: 0.2.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - Blake Thomas
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-10-19 00:00:00.000000000 Z
11
+ date: 2026-10-21 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |
14
14
  Finds the names a student writes about — classmates, teachers, relatives — and