1# pyenchant 2# 3# Copyright (C) 2004-2011, Ryan Kelly 4# 5# This library is free software; you can redistribute it and/or 6# modify it under the terms of the GNU Lesser General Public 7# License as published by the Free Software Foundation; either 8# version 2.1 of the License, or (at your option) any later version. 9# 10# This library is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13# Lesser General Public License for more details. 14# 15# You should have received a copy of the GNU Lesser General Public 16# License along with this library; if not, write to the 17# Free Software Foundation, Inc., 59 Temple Place - Suite 330, 18# Boston, MA 02111-1307, USA. 19# 20# In addition, as a special exception, you are 21# given permission to link the code of this program with 22# non-LGPL Spelling Provider libraries (eg: a MSFT Office 23# spell checker backend) and distribute linked combinations including 24# the two. You must obey the GNU Lesser General Public License in all 25# respects for all of the code used other than said providers. If you modify 26# this file, you may extend this exception to your version of the 27# file, but you are not obligated to do so. If you do not wish to 28# do so, delete this exception statement from your version. 29# 30""" 31enchant: Access to the enchant spellchecking library 32===================================================== 33 34This module provides several classes for performing spell checking 35via the Enchant spellchecking library. For more details on Enchant, 36visit the project website: 37 38 https://abiword.github.io/enchant/ 39 40Spellchecking is performed using 'Dict' objects, which represent 41a language dictionary. Their use is best demonstrated by a quick 42example:: 43 44 >>> import enchant 45 >>> d = enchant.Dict("en_US") # create dictionary for US English 46 >>> d.check("enchant") 47 True 48 >>> d.check("enchnt") 49 False 50 >>> d.suggest("enchnt") 51 ['enchant', 'enchants', 'enchanter', 'penchant', 'incant', 'enchain', 'enchanted'] 52 53Languages are identified by standard string tags such as "en" (English) 54and "fr" (French). Specific language dialects can be specified by 55including an additional code - for example, "en_AU" refers to Australian 56English. The later form is preferred as it is more widely supported. 57 58To check whether a dictionary exists for a given language, the function 59'dict_exists' is available. Dictionaries may also be created using the 60function 'request_dict'. 61 62A finer degree of control over the dictionaries and how they are created 63can be obtained using one or more 'Broker' objects. These objects are 64responsible for locating dictionaries for a specific language. 65 66Note that unicode strings are expected throughout the entire API. 67Bytestrings should not be passed into any function. 68 69Errors that occur in this module are reported by raising subclasses 70of 'Error'. 71 72""" 73_DOC_ERRORS = ["enchnt", "enchnt", "incant", "fr"] 74 75__version__ = "3.2.2" 76 77import os 78import warnings 79 80try: 81 from enchant import _enchant as _e 82except ImportError: 83 if not os.environ.get("PYENCHANT_IGNORE_MISSING_LIB", False): 84 raise 85 _e = None 86 87from enchant.errors import Error, DictNotFoundError 88from enchant.utils import get_default_language 89from enchant.pypwl import PyPWL 90 91 92class ProviderDesc: 93 """Simple class describing an Enchant provider. 94 95 Each provider has the following information associated with it: 96 97 * name: Internal provider name (e.g. "aspell") 98 * desc: Human-readable description (e.g. "Aspell Provider") 99 * file: Location of the library containing the provider 100 101 """ 102 103 _DOC_ERRORS = ["desc"] 104 105 def __init__(self, name, desc, file): 106 self.name = name 107 self.desc = desc 108 self.file = file 109 110 def __str__(self): 111 return "<Enchant: %s>" % self.desc 112 113 def __repr__(self): 114 return str(self) 115 116 def __eq__(self, pd): 117 """Equality operator on ProviderDesc objects.""" 118 return self.name == pd.name and self.desc == pd.desc and self.file == pd.file 119 120 def __hash__(self): 121 """Hash operator on ProviderDesc objects.""" 122 return hash(self.name + self.desc + self.file) 123 124 125class _EnchantObject: 126 """Base class for enchant objects. 127 128 This class implements some general functionality for interfacing with 129 the '_enchant' C-library in a consistent way. All public objects 130 from the 'enchant' module are subclasses of this class. 131 132 All enchant objects have an attribute '_this' which contains the 133 pointer to the underlying C-library object. The method '_check_this' 134 can be called to ensure that this point is not None, raising an 135 exception if it is. 136 """ 137 138 def __init__(self): 139 """_EnchantObject constructor.""" 140 self._this = None 141 # To be importable when enchant C lib is missing, we need 142 # to create a dummy default broker. 143 if _e is not None: 144 self._init_this() 145 146 def _check_this(self, msg=None): 147 """Check that self._this is set to a pointer, rather than None.""" 148 if self._this is None: 149 if msg is None: 150 msg = "%s unusable: the underlying C-library object has been freed." 151 msg = msg % (self.__class__.__name__,) 152 raise Error(msg) 153 154 def _init_this(self): 155 """Initialise the underlying C-library object pointer.""" 156 raise NotImplementedError 157 158 def _raise_error(self, default="Unspecified Error", eclass=Error): 159 """Raise an exception based on available error messages. 160 161 This method causes an Error to be raised. Subclasses should 162 override it to retrieve an error indication from the underlying 163 API if possible. If such a message cannot be retrieved, the 164 argument value <default> is used. The class of the exception 165 can be specified using the argument <eclass> 166 """ 167 raise eclass(default) 168 169 _raise_error._DOC_ERRORS = ["eclass"] 170 171 def __getstate__(self): 172 """Customize pickling of PyEnchant objects. 173 174 Since it's not safe for multiple objects to share the same C-library 175 object, we make sure it's unset when pickling. 176 """ 177 state = self.__dict__.copy() 178 state["_this"] = None 179 return state 180 181 def __setstate__(self, state): 182 self.__dict__.update(state) 183 self._init_this() 184 185 186class Broker(_EnchantObject): 187 """Broker object for the Enchant spellchecker. 188 189 Broker objects are responsible for locating and managing dictionaries. 190 Unless custom functionality is required, there is no need to use Broker 191 objects directly. The 'enchant' module provides a default broker object 192 so that 'Dict' objects can be created directly. 193 194 The most important methods of this class include: 195 196 * :py:meth:`dict_exists`: check existence of a specific language dictionary 197 * :py:meth:`request_dict`: obtain a dictionary for specific language 198 * :py:meth:`set_ordering`: specify which dictionaries to try for a given language. 199 200 """ 201 202 def __init__(self): 203 """Broker object constructor. 204 205 This method is the constructor for the 'Broker' object. No 206 arguments are required. 207 """ 208 super().__init__() 209 210 def _init_this(self): 211 self._this = _e.broker_init() 212 if not self._this: 213 raise Error("Could not initialise an enchant broker.") 214 self._live_dicts = {} 215 216 def __del__(self): 217 """Broker object destructor.""" 218 # Calling free() might fail if python is shutting down 219 try: 220 self._free() 221 except (AttributeError, TypeError): 222 pass 223 224 def __getstate__(self): 225 state = super().__getstate__() 226 state.pop("_live_dicts") 227 return state 228 229 def _raise_error(self, default="Unspecified Error", eclass=Error): 230 """Overrides _EnchantObject._raise_error to check broker errors.""" 231 err = _e.broker_get_error(self._this) 232 if err == "" or err is None: 233 raise eclass(default) 234 raise eclass(err.decode()) 235 236 def _free(self): 237 """Free system resource associated with a Broker object. 238 239 This method can be called to free the underlying system resources 240 associated with a Broker object. It is called automatically when 241 the object is garbage collected. If called explicitly, the 242 Broker and any associated Dict objects must no longer be used. 243 """ 244 if self._this is not None: 245 # During shutdown, this finalizer may be called before 246 # some Dict finalizers. Ensure all pointers are freed. 247 for (dict, count) in list(self._live_dicts.items()): 248 while count: 249 self._free_dict_data(dict) 250 count -= 1 251 _e.broker_free(self._this) 252 self._this = None 253 254 def request_dict(self, tag=None): 255 """Request a Dict object for the language specified by <tag>. 256 257 This method constructs and returns a Dict object for the 258 requested language. 'tag' should be a string of the appropriate 259 form for specifying a language, such as "fr" (French) or "en_AU" 260 (Australian English). The existence of a specific language can 261 be tested using the 'dict_exists' method. 262 263 If <tag> is not given or is None, an attempt is made to determine 264 the current language in use. If this cannot be determined, Error 265 is raised. 266 267 .. note:: 268 this method is functionally equivalent to calling the Dict() 269 constructor and passing in the <broker> argument. 270 271 """ 272 return Dict(tag, self) 273 274 request_dict._DOC_ERRORS = ["fr"] 275 276 def _request_dict_data(self, tag): 277 """Request raw C pointer data for a dictionary. 278 279 This method call passes on the call to the C library, and does 280 some internal bookkeeping. 281 """ 282 self._check_this() 283 new_dict = _e.broker_request_dict(self._this, tag.encode()) 284 if new_dict is None: 285 e_str = "Dictionary for language '%s' could not be found\n" 286 e_str += "Please check https://pyenchant.github.io/pyenchant/ for details" 287 self._raise_error(e_str % (tag,), DictNotFoundError) 288 if new_dict not in self._live_dicts: 289 self._live_dicts[new_dict] = 1 290 else: 291 self._live_dicts[new_dict] += 1 292 return new_dict 293 294 def request_pwl_dict(self, pwl): 295 """Request a Dict object for a personal word list. 296 297 This method behaves as 'request_dict' but rather than returning 298 a dictionary for a specific language, it returns a dictionary 299 referencing a personal word list. A personal word list is a file 300 of custom dictionary entries, one word per line. 301 """ 302 self._check_this() 303 new_dict = _e.broker_request_pwl_dict(self._this, pwl.encode()) 304 if new_dict is None: 305 e_str = "Personal Word List file '%s' could not be loaded" 306 self._raise_error(e_str % (pwl,)) 307 if new_dict not in self._live_dicts: 308 self._live_dicts[new_dict] = 1 309 else: 310 self._live_dicts[new_dict] += 1 311 d = Dict(False) 312 d._switch_this(new_dict, self) 313 return d 314 315 def _free_dict(self, dict): 316 """Free memory associated with a dictionary. 317 318 This method frees system resources associated with a Dict object. 319 It is equivalent to calling the object's 'free' method. Once this 320 method has been called on a dictionary, it must not be used again. 321 """ 322 self._free_dict_data(dict._this) 323 dict._this = None 324 dict._broker = None 325 326 def _free_dict_data(self, dict): 327 """Free the underlying pointer for a dict.""" 328 self._check_this() 329 _e.broker_free_dict(self._this, dict) 330 self._live_dicts[dict] -= 1 331 if self._live_dicts[dict] == 0: 332 del self._live_dicts[dict] 333 334 def dict_exists(self, tag): 335 """Check availability of a dictionary. 336 337 This method checks whether there is a dictionary available for 338 the language specified by 'tag'. It returns True if a dictionary 339 is available, and False otherwise. 340 """ 341 self._check_this() 342 val = _e.broker_dict_exists(self._this, tag.encode()) 343 return bool(val) 344 345 def set_ordering(self, tag, ordering): 346 """Set dictionary preferences for a language. 347 348 The Enchant library supports the use of multiple dictionary programs 349 and multiple languages. This method specifies which dictionaries 350 the broker should prefer when dealing with a given language. 'tag' 351 must be an appropriate language specification and 'ordering' is a 352 string listing the dictionaries in order of preference. For example 353 a valid ordering might be "aspell,myspell,ispell". 354 The value of 'tag' can also be set to "*" to set a default ordering 355 for all languages for which one has not been set explicitly. 356 """ 357 self._check_this() 358 _e.broker_set_ordering(self._this, tag.encode(), ordering.encode()) 359 360 def describe(self): 361 """Return list of provider descriptions. 362 363 This method returns a list of descriptions of each of the 364 dictionary providers available. Each entry in the list is a 365 ProviderDesc object. 366 """ 367 self._check_this() 368 self.__describe_result = [] 369 _e.broker_describe(self._this, self.__describe_callback) 370 return [ProviderDesc(*r) for r in self.__describe_result] 371 372 def __describe_callback(self, name, desc, file): 373 """Collector callback for dictionary description. 374 375 This method is used as a callback into the _enchant function 376 'enchant_broker_describe'. It collects the given arguments in 377 a tuple and appends them to the list '__describe_result'. 378 """ 379 name = name.decode() 380 desc = desc.decode() 381 file = file.decode() 382 self.__describe_result.append((name, desc, file)) 383 384 def list_dicts(self): 385 """Return list of available dictionaries. 386 387 This method returns a list of dictionaries available to the 388 broker. Each entry in the list is a two-tuple of the form: 389 390 (tag,provider) 391 392 where <tag> is the language lag for the dictionary and 393 <provider> is a ProviderDesc object describing the provider 394 through which that dictionary can be obtained. 395 """ 396 self._check_this() 397 self.__list_dicts_result = [] 398 _e.broker_list_dicts(self._this, self.__list_dicts_callback) 399 return [(r[0], ProviderDesc(*r[1])) for r in self.__list_dicts_result] 400 401 def __list_dicts_callback(self, tag, name, desc, file): 402 """Collector callback for listing dictionaries. 403 404 This method is used as a callback into the _enchant function 405 'enchant_broker_list_dicts'. It collects the given arguments into 406 an appropriate tuple and appends them to '__list_dicts_result'. 407 """ 408 tag = tag.decode() 409 name = name.decode() 410 desc = desc.decode() 411 file = file.decode() 412 self.__list_dicts_result.append((tag, (name, desc, file))) 413 414 def list_languages(self): 415 """List languages for which dictionaries are available. 416 417 This function returns a list of language tags for which a 418 dictionary is available. 419 """ 420 langs = [] 421 for (tag, prov) in self.list_dicts(): 422 if tag not in langs: 423 langs.append(tag) 424 return langs 425 426 def __describe_dict(self, dict_data): 427 """Get the description tuple for a dict data object. 428 <dict_data> must be a C-library pointer to an enchant dictionary. 429 The return value is a tuple of the form: 430 (<tag>,<name>,<desc>,<file>) 431 """ 432 # Define local callback function 433 cb_result = [] 434 435 def cb_func(tag, name, desc, file): 436 tag = tag.decode() 437 name = name.decode() 438 desc = desc.decode() 439 file = file.decode() 440 cb_result.append((tag, name, desc, file)) 441 442 # Actually call the describer function 443 _e.dict_describe(dict_data, cb_func) 444 return cb_result[0] 445 446 __describe_dict._DOC_ERRORS = ["desc"] 447 448 def get_param(self, name): 449 """Get the value of a named parameter on this broker. 450 451 Parameters are used to provide runtime information to individual 452 provider backends. See the method :py:meth:`set_param` for more details. 453 454 .. warning:: 455 456 This method does **not** work when using the Enchant C 457 library version 2.0 and above 458 """ 459 param = _e.broker_get_param(self._this, name.encode()) 460 if param is not None: 461 param = param.decode() 462 return param 463 464 get_param._DOC_ERRORS = ["param"] 465 466 def set_param(self, name, value): 467 """Set the value of a named parameter on this broker. 468 469 Parameters are used to provide runtime information to individual 470 provider backends. 471 472 .. warning:: 473 474 This method does **not** work when using the Enchant C 475 library version 2.0 and above 476 """ 477 name = name.encode() 478 if value is not None: 479 value = value.encode() 480 _e.broker_set_param(self._this, name, value) 481 482 483class Dict(_EnchantObject): 484 """Dictionary object for the Enchant spellchecker. 485 486 Dictionary objects are responsible for checking the spelling of words 487 and suggesting possible corrections. Each dictionary is owned by a 488 Broker object, but unless a new Broker has explicitly been created 489 then this will be the 'enchant' module default Broker and is of little 490 interest. 491 492 The important methods of this class include: 493 494 * check(): check whether a word id spelled correctly 495 * suggest(): suggest correct spellings for a word 496 * add(): add a word to the user's personal dictionary 497 * remove(): add a word to the user's personal exclude list 498 * add_to_session(): add a word to the current spellcheck session 499 * store_replacement(): indicate a replacement for a given word 500 501 Information about the dictionary is available using the following 502 attributes: 503 504 * tag: the language tag of the dictionary 505 * provider: a ProviderDesc object for the dictionary provider 506 507 """ 508 509 def __init__(self, tag=None, broker=None): 510 """Dict object constructor. 511 512 A dictionary belongs to a specific language, identified by the 513 string <tag>. If the tag is not given or is None, an attempt to 514 determine the language currently in use is made using the 'locale' 515 module. If the current language cannot be determined, Error is raised. 516 517 If <tag> is instead given the value of False, a 'dead' Dict object 518 is created without any reference to a language. This is typically 519 only useful within PyEnchant itself. Any other non-string value 520 for <tag> raises Error. 521 522 Each dictionary must also have an associated Broker object which 523 obtains the dictionary information from the underlying system. This 524 may be specified using <broker>. If not given, the default broker 525 is used. 526 """ 527 # Initialise misc object attributes to None 528 self.provider = None 529 # If no tag was given, use the default language 530 if tag is None: 531 tag = get_default_language() 532 if tag is None: 533 err = "No tag specified and default language could not " 534 err = err + "be determined." 535 raise Error(err) 536 self.tag = tag 537 # If no broker was given, use the default broker 538 if broker is None: 539 broker = _broker 540 self._broker = broker 541 # Now let the superclass initialise the C-library object 542 super().__init__() 543 544 def _init_this(self): 545 # Create dead object if False was given as the tag. 546 # Otherwise, use the broker to get C-library pointer data. 547 self._this = None 548 if self.tag: 549 this = self._broker._request_dict_data(self.tag) 550 self._switch_this(this, self._broker) 551 552 def __del__(self): 553 """Dict object destructor.""" 554 # Calling free() might fail if python is shutting down 555 try: 556 self._free() 557 except (AttributeError, TypeError): 558 pass 559 560 def _switch_this(self, this, broker): 561 """Switch the underlying C-library pointer for this object. 562 563 As all useful state for a Dict is stored by the underlying C-library 564 pointer, it is very convenient to allow this to be switched at 565 run-time. Pass a new dict data object into this method to affect 566 the necessary changes. The creating Broker object (at the Python 567 level) must also be provided. 568 569 This should *never* *ever* be used by application code. It's 570 a convenience for developers only, replacing the clunkier <data> 571 parameter to __init__ from earlier versions. 572 """ 573 # Free old dict data 574 Dict._free(self) 575 # Hook in the new stuff 576 self._this = this 577 self._broker = broker 578 # Update object properties 579 desc = self.__describe(check_this=False) 580 self.tag = desc[0] 581 self.provider = ProviderDesc(*desc[1:]) 582 583 _switch_this._DOC_ERRORS = ["init"] 584 585 def _check_this(self, msg=None): 586 """Extend _EnchantObject._check_this() to check Broker validity. 587 588 It is possible for the managing Broker object to be freed without 589 freeing the Dict. Thus validity checking must take into account 590 self._broker._this as well as self._this. 591 """ 592 if self._broker is None or self._broker._this is None: 593 self._this = None 594 super()._check_this(msg) 595 596 def _raise_error(self, default="Unspecified Error", eclass=Error): 597 """Overrides _EnchantObject._raise_error to check dict errors.""" 598 err = _e.dict_get_error(self._this) 599 if err == "" or err is None: 600 raise eclass(default) 601 raise eclass(err.decode()) 602 603 def _free(self): 604 """Free the system resources associated with a Dict object. 605 606 This method frees underlying system resources for a Dict object. 607 Once it has been called, the Dict object must no longer be used. 608 It is called automatically when the object is garbage collected. 609 """ 610 if self._this is not None: 611 # The broker may have been freed before the dict. 612 # It will have freed the underlying pointers already. 613 if self._broker is not None and self._broker._this is not None: 614 self._broker._free_dict(self) 615 616 def check(self, word): 617 """Check spelling of a word. 618 619 This method takes a word in the dictionary language and returns 620 True if it is correctly spelled, and false otherwise. 621 """ 622 self._check_this() 623 # Enchant asserts that the word is non-empty. 624 # Check it up-front to avoid nasty warnings on stderr. 625 if len(word) == 0: 626 raise ValueError("can't check spelling of empty string") 627 val = _e.dict_check(self._this, word.encode()) 628 if val == 0: 629 return True 630 if val > 0: 631 return False 632 self._raise_error() 633 634 def suggest(self, word): 635 """Suggest possible spellings for a word. 636 637 This method tries to guess the correct spelling for a given 638 word, returning the possibilities in a list. 639 """ 640 self._check_this() 641 # Enchant asserts that the word is non-empty. 642 # Check it up-front to avoid nasty warnings on stderr. 643 if len(word) == 0: 644 raise ValueError("can't suggest spellings for empty string") 645 suggs = _e.dict_suggest(self._this, word.encode()) 646 return [w.decode() for w in suggs] 647 648 def add(self, word): 649 """Add a word to the user's personal word list.""" 650 self._check_this() 651 _e.dict_add(self._this, word.encode()) 652 653 def remove(self, word): 654 """Add a word to the user's personal exclude list.""" 655 self._check_this() 656 _e.dict_remove(self._this, word.encode()) 657 658 def add_to_pwl(self, word): 659 """Add a word to the user's personal word list.""" 660 warnings.warn( 661 "Dict.add_to_pwl is deprecated, please use Dict.add", 662 category=DeprecationWarning, 663 stacklevel=2, 664 ) 665 self._check_this() 666 _e.dict_add_to_pwl(self._this, word.encode()) 667 668 def add_to_session(self, word): 669 """Add a word to the session personal list.""" 670 self._check_this() 671 _e.dict_add_to_session(self._this, word.encode()) 672 673 def remove_from_session(self, word): 674 """Add a word to the session exclude list.""" 675 self._check_this() 676 _e.dict_remove_from_session(self._this, word.encode()) 677 678 def is_added(self, word): 679 """Check whether a word is in the personal word list.""" 680 self._check_this() 681 return _e.dict_is_added(self._this, word.encode()) 682 683 def is_removed(self, word): 684 """Check whether a word is in the personal exclude list.""" 685 self._check_this() 686 return _e.dict_is_removed(self._this, word.encode()) 687 688 def store_replacement(self, mis, cor): 689 """Store a replacement spelling for a miss-spelled word. 690 691 This method makes a suggestion to the spellchecking engine that the 692 miss-spelled word <mis> is in fact correctly spelled as <cor>. Such 693 a suggestion will typically mean that <cor> appears early in the 694 list of suggested spellings offered for later instances of <mis>. 695 """ 696 if not mis: 697 raise ValueError("can't store replacement for an empty string") 698 if not cor: 699 raise ValueError("can't store empty string as a replacement") 700 self._check_this() 701 _e.dict_store_replacement(self._this, mis.encode(), cor.encode()) 702 703 store_replacement._DOC_ERRORS = ["mis", "mis"] 704 705 def __describe(self, check_this=True): 706 """Return a tuple describing the dictionary. 707 708 This method returns a four-element tuple describing the underlying 709 spellchecker system providing the dictionary. It will contain the 710 following strings: 711 712 * language tag 713 * name of dictionary provider 714 * description of dictionary provider 715 * dictionary file 716 717 Direct use of this method is not recommended - instead, access this 718 information through the 'tag' and 'provider' attributes. 719 """ 720 if check_this: 721 self._check_this() 722 _e.dict_describe(self._this, self.__describe_callback) 723 return self.__describe_result 724 725 def __describe_callback(self, tag, name, desc, file): 726 """Collector callback for dictionary description. 727 728 This method is used as a callback into the _enchant function 729 'enchant_dict_describe'. It collects the given arguments in 730 a tuple and stores them in the attribute '__describe_result'. 731 """ 732 tag = tag.decode() 733 name = name.decode() 734 desc = desc.decode() 735 file = file.decode() 736 self.__describe_result = (tag, name, desc, file) 737 738 739class DictWithPWL(Dict): 740 """Dictionary with separately-managed personal word list. 741 742 .. note:: 743 As of version 1.4.0, enchant manages a per-user pwl and 744 exclude list. This class is now only needed if you want 745 to explicitly maintain a separate word list in addition to 746 the default one. 747 748 This class behaves as the standard Dict class, but also manages a 749 personal word list stored in a separate file. The file must be 750 specified at creation time by the 'pwl' argument to the constructor. 751 Words added to the dictionary are automatically appended to the pwl file. 752 753 A personal exclude list can also be managed, by passing another filename 754 to the constructor in the optional 'pel' argument. If this is not given, 755 requests to exclude words are ignored. 756 757 If either 'pwl' or 'pel' are None, an in-memory word list is used. 758 This will prevent calls to add() and remove() from affecting the user's 759 default word lists. 760 761 The Dict object managing the PWL is available as the 'pwl' attribute. 762 The Dict object managing the PEL is available as the 'pel' attribute. 763 764 To create a DictWithPWL from the user's default language, use None 765 as the 'tag' argument. 766 """ 767 768 _DOC_ERRORS = ["pel", "pel", "PEL", "pel"] 769 770 def __init__(self, tag, pwl=None, pel=None, broker=None): 771 """DictWithPWL constructor. 772 773 The argument 'pwl', if not None, names a file containing the 774 personal word list. If this file does not exist, it is created 775 with default permissions. 776 777 The argument 'pel', if not None, names a file containing the personal 778 exclude list. If this file does not exist, it is created with 779 default permissions. 780 """ 781 super().__init__(tag, broker) 782 if pwl is not None: 783 if not os.path.exists(pwl): 784 f = open(pwl, "wt") 785 f.close() 786 del f 787 self.pwl = self._broker.request_pwl_dict(pwl) 788 else: 789 self.pwl = PyPWL() 790 if pel is not None: 791 if not os.path.exists(pel): 792 f = open(pel, "wt") 793 f.close() 794 del f 795 self.pel = self._broker.request_pwl_dict(pel) 796 else: 797 self.pel = PyPWL() 798 799 def _check_this(self, msg=None): 800 """Extend Dict._check_this() to check PWL validity.""" 801 if self.pwl is None: 802 self._free() 803 if self.pel is None: 804 self._free() 805 super()._check_this(msg) 806 self.pwl._check_this(msg) 807 self.pel._check_this(msg) 808 809 def _free(self): 810 """Extend Dict._free() to free the PWL as well.""" 811 if self.pwl is not None: 812 self.pwl._free() 813 self.pwl = None 814 if self.pel is not None: 815 self.pel._free() 816 self.pel = None 817 super()._free() 818 819 def check(self, word): 820 """Check spelling of a word. 821 822 This method takes a word in the dictionary language and returns 823 True if it is correctly spelled, and false otherwise. It checks 824 both the dictionary and the personal word list. 825 """ 826 if self.pel.check(word): 827 return False 828 if self.pwl.check(word): 829 return True 830 if super().check(word): 831 return True 832 return False 833 834 def suggest(self, word): 835 """Suggest possible spellings for a word. 836 837 This method tries to guess the correct spelling for a given 838 word, returning the possibilities in a list. 839 """ 840 suggs = super().suggest(word) 841 suggs.extend([w for w in self.pwl.suggest(word) if w not in suggs]) 842 for i in range(len(suggs) - 1, -1, -1): 843 if self.pel.check(suggs[i]): 844 del suggs[i] 845 return suggs 846 847 def add(self, word): 848 """Add a word to the associated personal word list. 849 850 This method adds the given word to the personal word list, and 851 automatically saves the list to disk. 852 """ 853 self._check_this() 854 self.pwl.add(word) 855 self.pel.remove(word) 856 857 def remove(self, word): 858 """Add a word to the associated exclude list.""" 859 self._check_this() 860 self.pwl.remove(word) 861 self.pel.add(word) 862 863 def add_to_pwl(self, word): 864 """Add a word to the associated personal word list. 865 866 This method adds the given word to the personal word list, and 867 automatically saves the list to disk. 868 """ 869 self._check_this() 870 self.pwl.add_to_pwl(word) 871 self.pel.remove(word) 872 873 def is_added(self, word): 874 """Check whether a word is in the personal word list.""" 875 self._check_this() 876 return self.pwl.is_added(word) 877 878 def is_removed(self, word): 879 """Check whether a word is in the personal exclude list.""" 880 self._check_this() 881 return self.pel.is_added(word) 882 883 884## Create a module-level default broker object, and make its important 885## methods available at the module level. 886_broker = Broker() 887request_dict = _broker.request_dict 888request_pwl_dict = _broker.request_pwl_dict 889dict_exists = _broker.dict_exists 890list_dicts = _broker.list_dicts 891list_languages = _broker.list_languages 892get_param = _broker.get_param 893set_param = _broker.set_param 894 895# Expose the "get_version" function. 896def get_enchant_version(): 897 """Get the version string for the underlying enchant library.""" 898 return _e.get_version().decode() 899 900 901# Expose the "set_prefix_dir" function. 902def set_prefix_dir(path): 903 """Set the prefix used by the Enchant library to find its plugins 904 905 Called automatically when the Python library is imported when 906 required. 907 """ 908 return _e.set_prefix_dir(path) 909 910 set_prefix_dir._DOC_ERRORS = ["plugins"] 911 912 913def get_user_config_dir(): 914 """Return the path that will be used by some 915 Enchant providers to look for custom dictionaries. 916 """ 917 return _e.get_user_config_dir().decode() 918