1# pyenchant 2# 3# Copyright (C) 2004-2008, 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 31""" 32 33 enchant._enchant: ctypes-based wrapper for enchant C library 34 35 This module implements the low-level interface to the underlying 36 C library for enchant. The interface is based on ctypes and tries 37 to do as little as possible while making the higher-level components 38 easier to write. 39 40 The following conveniences are provided that differ from the underlying 41 C API: 42 43 * the "enchant" prefix has been removed from all functions, since 44 python has a proper module system 45 * callback functions do not take a user_data argument, since 46 python has proper closures that can manage this internally 47 * string lengths are not passed into functions such as dict_check, 48 since python strings know how long they are 49 50""" 51 52import sys 53import os 54import os.path 55import ctypes 56from ctypes import c_char_p, c_int, c_size_t, c_void_p, pointer, CFUNCTYPE, POINTER 57import ctypes.util 58import platform 59import textwrap 60 61 62def from_prefix(prefix): 63 find_message("finding from prefix ", prefix) 64 assert os.path.exists(prefix), prefix + " does not exist" 65 bin_path = os.path.join(prefix, "bin") 66 enchant_dll_path = os.path.join(bin_path, "libenchant-2.dll") 67 assert os.path.exists(enchant_dll_path), enchant_dll_path + " does not exist" 68 # Make sure all the dlls found next to libenchant-2.dll 69 # (libglib-2.0-0.dll, libgmodule-2.0-0.dll, ...) can be 70 # used without having to modify %PATH% 71 new_path = bin_path + os.pathsep + os.environ["PATH"] 72 find_message("Prepending ", bin_path, " to %PATH%") 73 os.environ["PATH"] = new_path 74 return enchant_dll_path 75 76 77def from_env_var(library_path): 78 find_message("using PYENCHANT_LIBRARY_PATH env var") 79 assert os.path.exists(library_path), library_path + " does not exist" 80 return library_path 81 82 83def from_package_resources(): 84 if sys.platform != "win32": 85 return None 86 bits, _ = platform.architecture() 87 if bits == "64bit": 88 subdir = "mingw64" # hopefully this is compatible 89 else: 90 subdir = "mingw32" # ditto 91 this_path = os.path.dirname(os.path.abspath(__file__)) 92 data_path = os.path.join(this_path, "data", subdir) 93 find_message("looking in ", data_path) 94 if os.path.exists(data_path): 95 return from_prefix(data_path) 96 97 98def from_system(): 99 # Note: keep enchant-2 first 100 find_message("looking in system") 101 candidates = [ 102 "enchant-2", 103 "libenchant-2", 104 "enchant", 105 "libenchant", 106 "enchant-1", 107 "libenchant-1", 108 ] 109 110 for name in candidates: 111 find_message("with name ", name) 112 res = ctypes.util.find_library(name) 113 if res: 114 return res 115 116 117VERBOSE_FIND = False 118 119 120def find_message(*args): 121 if not VERBOSE_FIND: 122 return 123 print("pyenchant:: ", *args, sep="") 124 125 126def find_c_enchant_lib(): 127 verbose = os.environ.get("PYENCHANT_VERBOSE_FIND") 128 if verbose: 129 global VERBOSE_FIND 130 VERBOSE_FIND = True 131 prefix = os.environ.get("PYENCHANT_ENCHANT_PREFIX") 132 if prefix: 133 return from_prefix(prefix) 134 135 library_path = os.environ.get("PYENCHANT_LIBRARY_PATH") 136 if library_path: 137 return from_env_var(library_path) 138 139 from_package = from_package_resources() 140 if from_package: 141 return from_package 142 143 # Last chance 144 return from_system() 145 146 147enchant_lib_path = find_c_enchant_lib() 148 149if enchant_lib_path is None: 150 msg = textwrap.dedent( 151 """\ 152 The 'enchant' C library was not found and maybe needs to be installed. 153 See https://pyenchant.github.io/pyenchant/install.html 154 for details 155 """ 156 ) 157 raise ImportError(msg) 158 159 160find_message("loading library ", enchant_lib_path) 161e = ctypes.cdll.LoadLibrary(enchant_lib_path) 162 163# Always assume the found enchant C dll is inside 164# the correct directory layout 165prefix_dir = os.path.dirname(os.path.dirname(enchant_lib_path)) 166if hasattr(e, "enchant_set_prefix_dir") and prefix_dir: 167 find_message("setting prefix ", prefix_dir) 168 e.enchant_set_prefix_dir(prefix_dir.encode()) 169 170 171def callback(restype, *argtypes): 172 """Factory for generating callback function prototypes. 173 174 This is factored into a factory so I can easily change the definition 175 for experimentation or debugging. 176 """ 177 return CFUNCTYPE(restype, *argtypes) 178 179 180t_broker_desc_func = callback(None, c_char_p, c_char_p, c_char_p, c_void_p) 181t_dict_desc_func = callback(None, c_char_p, c_char_p, c_char_p, c_char_p, c_void_p) 182 183 184# Simple typedefs for readability 185 186t_broker = c_void_p 187t_dict = c_void_p 188 189 190# Now we can define the types of each function we are going to use 191 192broker_init = e.enchant_broker_init 193broker_init.argtypes = [] 194broker_init.restype = t_broker 195 196broker_free = e.enchant_broker_free 197broker_free.argtypes = [t_broker] 198broker_free.restype = None 199 200broker_request_dict = e.enchant_broker_request_dict 201broker_request_dict.argtypes = [t_broker, c_char_p] 202broker_request_dict.restype = t_dict 203 204broker_request_pwl_dict = e.enchant_broker_request_pwl_dict 205broker_request_pwl_dict.argtypes = [t_broker, c_char_p] 206broker_request_pwl_dict.restype = t_dict 207 208broker_free_dict = e.enchant_broker_free_dict 209broker_free_dict.argtypes = [t_broker, t_dict] 210broker_free_dict.restype = None 211 212broker_dict_exists = e.enchant_broker_dict_exists 213broker_dict_exists.argtypes = [t_broker, c_char_p] 214broker_dict_exists.restype = c_int 215 216broker_set_ordering = e.enchant_broker_set_ordering 217broker_set_ordering.argtypes = [t_broker, c_char_p, c_char_p] 218broker_set_ordering.restype = None 219 220broker_get_error = e.enchant_broker_get_error 221broker_get_error.argtypes = [t_broker] 222broker_get_error.restype = c_char_p 223 224broker_describe1 = e.enchant_broker_describe 225broker_describe1.argtypes = [t_broker, t_broker_desc_func, c_void_p] 226broker_describe1.restype = None 227 228 229def broker_describe(broker, cbfunc): 230 def cbfunc1(*args): 231 cbfunc(*args[:-1]) 232 233 broker_describe1(broker, t_broker_desc_func(cbfunc1), None) 234 235 236broker_list_dicts1 = e.enchant_broker_list_dicts 237broker_list_dicts1.argtypes = [t_broker, t_dict_desc_func, c_void_p] 238broker_list_dicts1.restype = None 239 240 241def broker_list_dicts(broker, cbfunc): 242 def cbfunc1(*args): 243 cbfunc(*args[:-1]) 244 245 broker_list_dicts1(broker, t_dict_desc_func(cbfunc1), None) 246 247 248try: 249 broker_get_param = e.enchant_broker_get_param 250except AttributeError: 251 # Make the lookup error occur at runtime 252 def broker_get_param(broker, name): 253 return e.enchant_broker_get_param(broker, name) 254 255 256else: 257 broker_get_param.argtypes = [t_broker, c_char_p] 258 broker_get_param.restype = c_char_p 259 260try: 261 broker_set_param = e.enchant_broker_set_param 262except AttributeError: 263 # Make the lookup error occur at runtime 264 def broker_set_param(broker, name, value): 265 return e.enchant_broker_set_param(broker, name, value) 266 267 268else: 269 broker_set_param.argtypes = [t_broker, c_char_p, c_char_p] 270 broker_set_param.restype = None 271 272try: 273 get_version = e.enchant_get_version 274except AttributeError: 275 # Make the lookup error occur at runtime 276 def get_version(): 277 return e.enchant_get_version() 278 279 280else: 281 get_version.argtypes = [] 282 get_version.restype = c_char_p 283 284try: 285 set_prefix_dir = e.enchant_set_prefix_dir 286except AttributeError: 287 # Make the lookup error occur at runtime 288 def set_prefix_dir(path): 289 return e.enchant_set_prefix_dir(path) 290 291 292else: 293 set_prefix_dir.argtypes = [c_char_p] 294 set_prefix_dir.restype = None 295 296try: 297 get_user_config_dir = e.enchant_get_user_config_dir 298except AttributeError: 299 # Make the lookup error occur at runtime 300 def get_user_config_dir(): 301 return e.enchant_get_user_config_dir() 302 303 304else: 305 get_user_config_dir.argtypes = [] 306 get_user_config_dir.restype = c_char_p 307 308dict_check1 = e.enchant_dict_check 309dict_check1.argtypes = [t_dict, c_char_p, c_size_t] 310dict_check1.restype = c_int 311 312 313def dict_check(dict, word): 314 return dict_check1(dict, word, len(word)) 315 316 317dict_suggest1 = e.enchant_dict_suggest 318dict_suggest1.argtypes = [t_dict, c_char_p, c_size_t, POINTER(c_size_t)] 319dict_suggest1.restype = POINTER(c_char_p) 320 321 322def dict_suggest(dict, word): 323 num_suggs_p = pointer(c_size_t(0)) 324 suggs_c = dict_suggest1(dict, word, len(word), num_suggs_p) 325 suggs = [] 326 n = 0 327 while n < num_suggs_p.contents.value: 328 suggs.append(suggs_c[n]) 329 n = n + 1 330 if num_suggs_p.contents.value > 0: 331 dict_free_string_list(dict, suggs_c) 332 return suggs 333 334 335dict_add1 = e.enchant_dict_add 336dict_add1.argtypes = [t_dict, c_char_p, c_size_t] 337dict_add1.restype = None 338 339 340def dict_add(dict, word): 341 return dict_add1(dict, word, len(word)) 342 343 344dict_add_to_pwl1 = e.enchant_dict_add 345dict_add_to_pwl1.argtypes = [t_dict, c_char_p, c_size_t] 346dict_add_to_pwl1.restype = None 347 348 349def dict_add_to_pwl(dict, word): 350 return dict_add_to_pwl1(dict, word, len(word)) 351 352 353dict_add_to_session1 = e.enchant_dict_add_to_session 354dict_add_to_session1.argtypes = [t_dict, c_char_p, c_size_t] 355dict_add_to_session1.restype = None 356 357 358def dict_add_to_session(dict, word): 359 return dict_add_to_session1(dict, word, len(word)) 360 361 362dict_remove1 = e.enchant_dict_remove 363dict_remove1.argtypes = [t_dict, c_char_p, c_size_t] 364dict_remove1.restype = None 365 366 367def dict_remove(dict, word): 368 return dict_remove1(dict, word, len(word)) 369 370 371dict_remove_from_session1 = e.enchant_dict_remove_from_session 372dict_remove_from_session1.argtypes = [t_dict, c_char_p, c_size_t] 373dict_remove_from_session1.restype = c_int 374 375 376def dict_remove_from_session(dict, word): 377 return dict_remove_from_session1(dict, word, len(word)) 378 379 380dict_is_added1 = e.enchant_dict_is_added 381dict_is_added1.argtypes = [t_dict, c_char_p, c_size_t] 382dict_is_added1.restype = c_int 383 384 385def dict_is_added(dict, word): 386 return dict_is_added1(dict, word, len(word)) 387 388 389dict_is_removed1 = e.enchant_dict_is_removed 390dict_is_removed1.argtypes = [t_dict, c_char_p, c_size_t] 391dict_is_removed1.restype = c_int 392 393 394def dict_is_removed(dict, word): 395 return dict_is_removed1(dict, word, len(word)) 396 397 398dict_store_replacement1 = e.enchant_dict_store_replacement 399dict_store_replacement1.argtypes = [t_dict, c_char_p, c_size_t, c_char_p, c_size_t] 400dict_store_replacement1.restype = None 401 402 403def dict_store_replacement(dict, mis, cor): 404 return dict_store_replacement1(dict, mis, len(mis), cor, len(cor)) 405 406 407dict_free_string_list = e.enchant_dict_free_string_list 408dict_free_string_list.argtypes = [t_dict, POINTER(c_char_p)] 409dict_free_string_list.restype = None 410 411dict_get_error = e.enchant_dict_get_error 412dict_get_error.argtypes = [t_dict] 413dict_get_error.restype = c_char_p 414 415dict_describe1 = e.enchant_dict_describe 416dict_describe1.argtypes = [t_dict, t_dict_desc_func, c_void_p] 417dict_describe1.restype = None 418 419 420def dict_describe(dict, cbfunc): 421 def cbfunc1(tag, name, desc, file, data): 422 cbfunc(tag, name, desc, file) 423 424 dict_describe1(dict, t_dict_desc_func(cbfunc1), None) 425