11from collections .abc import Iterable
2- from os import path
2+ from difflib import get_close_matches
33
44from mods_base import Game
55
178178# endregion
179179# region Misspellings
180180
181- # Python has code to suggest other names on an attribute or name error - we want to do the same when
182- # someone gives an invalid key name.
183- # Unfortunately, it doesn't seem to be exposed, so we manually replicate it instead
184- # Based on Python/suggestions.c:calculate_suggestions
185-
186- # ruff: noqa: ERA001
187-
188- MOVE_COST = 2
189- CASE_COST = 1
190- MAX_STRING_SIZE = 40
191-
192-
193- def _substitution_cost (a : str , b : str ) -> int :
194- if a == b :
195- return 0
196- if a .lower () == b .lower ():
197- return CASE_COST
198-
199- return MOVE_COST
200-
201-
202- def _levenshtein_distance (a : str , b : str , max_cost : int ) -> int :
203- """Calculate the Levenshtein distance between string1 and string2."""
204-
205- # Both strings are the same
206- if a == b :
207- return 0
208-
209- # Trim away common affixes.
210- common_prefix = path .commonprefix ((a , b ))
211- a = a .removeprefix (common_prefix )
212- b = b .removeprefix (common_prefix )
213-
214- common_suffix = path .commonprefix ((a [::- 1 ], b [::- 1 ]))
215- a = a .removesuffix (common_suffix )
216- b = b .removesuffix (common_suffix )
217-
218- a_size = len (a )
219- b_size = len (b )
220- if a_size == 0 or b_size == 0 :
221- return (a_size + b_size ) * MOVE_COST
222-
223- if a_size > MAX_STRING_SIZE or b_size > MAX_STRING_SIZE :
224- return max_cost + 1
225-
226- # Prefer shorter buffer
227- if b_size < a_size :
228- a , b = b , a
229- a_size , b_size = b_size , a_size
230-
231- # quick fail when a match is impossible.
232- if (b_size - a_size ) * MOVE_COST > max_cost :
233- return max_cost + 1
234-
235- # Instead of producing the whole traditional len(a)-by-len(b)
236- # matrix, we can update just one row in place.
237- # Initialize the buffer row
238- # cost from b[:0] to a[:i+1]
239- buffer = [(i + 1 ) * MOVE_COST for i in range (a_size )]
240-
241- result = 0
242- for b_index in range (b_size ):
243- code = b [b_index ]
244- # cost(b[:b_index], a[:0]) == b_index * MOVE_COST
245- distance = result = b_index * MOVE_COST
246- minimum = None
247- for index in range (a_size ):
248- # cost(b[:b_index+1], a[:index+1]) = min(
249- # # 1) substitute
250- # cost(b[:b_index], a[:index])
251- # + substitution_cost(b[b_index], a[index]),
252- # # 2) delete from b
253- # cost(b[:b_index], a[:index+1]) + MOVE_COST,
254- # # 3) delete from a
255- # cost(b[:b_index+1], a[index]) + MOVE_COST
256- # )
257-
258- # 1) Previous distance in this row is cost(b[:b_index], a[:index])
259- substitute = distance + _substitution_cost (code , a [index ])
260- # 2) cost(b[:b_index], a[:index+1]) from previous row
261- distance = buffer [index ]
262- # 3) existing result is cost(b[:b_index+1], a[index])
263-
264- insert_delete = min (result , distance ) + MOVE_COST
265- result = min (insert_delete , substitute )
266-
267- # cost(b[:b_index+1], a[:index+1])
268- buffer [index ] = result
269- if minimum is None or result < minimum :
270- minimum = result
271- if minimum is None or minimum > max_cost :
272- # Everything in this row is too big, so bail early.
273- return max_cost + 1
274- return result
275-
276181
277182def suggest_misspelt_key (invalid_key : str ) -> Iterable [str ]:
278183 """
@@ -283,34 +188,7 @@ def suggest_misspelt_key(invalid_key: str) -> Iterable[str]:
283188 Returns:
284189 A list of possible misspellings (which may be empty).
285190 """
286- suggestion_distance : int | None = None
287- suggestion : str | None = None
288- for item in KNOWN_KEYS :
289- if item == invalid_key :
290- continue
291-
292- # No more than 1/3 of the involved characters should need changed.
293- max_distance = (len (invalid_key ) + len (item ) + 3 ) * MOVE_COST // 6
294-
295- # Don't take matches we've already beaten.
296- if suggestion_distance is not None and (suggestion_distance - 1 ) < max_distance :
297- max_distance = suggestion_distance - 1
298-
299- current_distance = _levenshtein_distance (invalid_key , item , max_distance )
300-
301- if current_distance > max_distance :
302- continue
303- if (
304- suggestion is None
305- or suggestion_distance is None
306- or current_distance < suggestion_distance
307- ):
308- suggestion = item
309- suggestion_distance = current_distance
310-
311- if suggestion is None :
312- return ()
313- return (suggestion ,)
191+ return get_close_matches (invalid_key , KNOWN_KEYS )
314192
315193
316194# endregion
0 commit comments