File indexing completed on 2024-11-03 11:24:01
0001 # -*- coding: UTF-8 -*- 0002 0003 """ 0004 Fetch Pology modules, functions, data, etc. by various handles. 0005 0006 @author: Chusslove Illich (Часлав Илић) <caslav.ilic@gmx.net> 0007 @license: GPLv3 0008 """ 0009 0010 import sys 0011 import os 0012 import re 0013 0014 from pology import PologyError, _, n_ 0015 from pology.report import error, warning 0016 0017 0018 def get_module (modpath, lang=None, proj=None, abort=False, wpath=False): 0019 """ 0020 Import a Pology module. 0021 0022 Module is specified by its dotted path in Pology's package structure 0023 relative to (optional) language and project. 0024 For example:: 0025 0026 get_module("remove") 0027 0028 will try to import the L{pology.remove}, while:: 0029 0030 get_module("wconv", lang="sr") 0031 0032 will try to import the C{pology.lang.sr.wconv} module, and:: 0033 0034 get_module("header", proj="kde") 0035 0036 will try to import the C{pology.proj.kde.header} module. 0037 0038 Elements of the relative path can also contain hyphens, which will 0039 be converted into underscores when looking for the module. 0040 0041 If the module cannot be imported, if C{abort} is C{True} the execution 0042 will abort with an error message; otherwise an exception is raised. 0043 0044 If C{wpath} is C{True}, the resolved module path is returned as well. 0045 0046 @param modpath: relative module location 0047 @type modpath: string 0048 @param lang: language code 0049 @type lang: string 0050 @param proj: project code 0051 @type proj: string 0052 @param abort: whether to abort execution if the module cannot be imported 0053 @type abort: bool 0054 @param wpath: whether to also return resolve module path 0055 @type wpath: bool 0056 0057 @returns: imported module, possibly with its path 0058 @rtype: module or (module, string) 0059 """ 0060 0061 modpath = modpath.replace("-", "_") 0062 if lang and proj: 0063 modpath = "pology.lang.%s.proj.%s" % (lang, proj, modpath) 0064 elif lang: 0065 modpath = "pology.lang.%s.%s" % (lang, modpath) 0066 elif proj: 0067 modpath = "pology.proj.%s.%s" % (proj, modpath) 0068 else: 0069 modpath = "pology.%s" % (modpath) 0070 try: 0071 module = __import__(modpath, globals(), locals(), [""]) 0072 except ImportError: 0073 _raise_or_abort(_("@info", 0074 "Cannot import module '%(mod)s'.", 0075 mod=modpath), abort) 0076 0077 # TODO: Make more detailed analysis why importing fails: 0078 # is there such a language or project, is there such a file, etc. 0079 0080 return module if not wpath else (module, modpath) 0081 0082 0083 _valid_lang_rx = re.compile(r"^[a-z]{2,3}(_[A-Z]{2})?(@\w+)?$") 0084 _valid_proj_rx = re.compile(r"^[a-z_]+$") 0085 _valid_path_rx = re.compile(r"^([a-z][\w-]*(\.|$))+", re.I) 0086 _valid_item_rx = re.compile(r"^[a-z][\w-]+$", re.I) 0087 0088 def split_ireq (ireq, abort=False): 0089 """ 0090 Split item request string into distinct elements. 0091 0092 The item request is a string of the form 0093 C{[lang:][proj%]path[/item][~args]} (or C{[proj%][lang:]...}), 0094 which this function parses into C{(path, lang, proj, item, args)} tuple. 0095 If language, project, item or argument strings are not not stated, 0096 their value in the tuple will be C{None}. 0097 The language should be a proper language code, 0098 the project an identifier-like string, 0099 the path a sequence of identifier-like strings connected by dots 0100 (though hyphens are accepted an taken as synonymous to underscores), 0101 item an identifier-like string, 0102 and arguments can be an arbitrary string. 0103 0104 If the item request cannot be parsed, 0105 either the execution is aborted with an error message, 0106 or an exception is raised, depending on value of C{abort}. 0107 0108 @param ireq: item request 0109 @type ireq: string 0110 @param abort: whether to abort execution or if the request cannot be parsed 0111 @type abort: bool 0112 0113 @returns: parsed request elements 0114 @rtype: (string, string or C{None}, string or C{None}, string or C{None}, 0115 string or C{None}) 0116 """ 0117 0118 rest = ireq 0119 0120 lst = rest.split("~", 1) 0121 if len(lst) == 1: 0122 rest, args = lst + [None] 0123 else: 0124 rest, args = lst 0125 0126 lst = rest.split("/", 1) 0127 if len(lst) == 1: 0128 rest, item = lst + [None] 0129 else: 0130 rest, item = lst 0131 0132 lang = None 0133 proj = None 0134 plang = rest.find(":") 0135 pproj = rest.find("%") 0136 if plang >= 0 and pproj >= 0: 0137 p1, p2 = min(plang, pproj), max(plang, pproj) 0138 c1, c2, rest = rest[:p1], rest[p1 + 1:p2], rest[p2 + 1:] 0139 if plang < pproj: 0140 lang, proj = c1, c2 0141 else: 0142 lang, proj = c2, c1 0143 elif plang >= 0: 0144 lang, rest = rest[:plang], rest[plang + 1:] 0145 elif pproj >= 0: 0146 proj, rest = rest[:pproj], rest[pproj + 1:] 0147 0148 path = rest 0149 0150 if not _valid_path_rx.search(path): 0151 _raise_or_abort(_("@info", 0152 "Invalid path '%(path)s' in item request '%(req)s'.", 0153 path=path, req=ireq), abort) 0154 if lang is not None and not _valid_lang_rx.search(lang): 0155 _raise_or_abort(_("@info", 0156 "Invalid language code '%(code)s' " 0157 "in item request '%(req)s.'", 0158 code=lang, req=ireq), abort) 0159 if proj is not None and not _valid_proj_rx.search(proj): 0160 _raise_or_abort(_("@info", 0161 "Invalid project code '%(code)s' " 0162 "in item request '%(req)s.'", 0163 code=proj, req=ireq), abort) 0164 if item is not None and not _valid_item_rx.search(item): 0165 _raise_or_abort(_("@info", 0166 "Invalid item '%(item)s' in item request '%(req)s'.", 0167 item=item, req=ireq), abort) 0168 0169 path = path.replace("-", "_") 0170 if item: 0171 item = item.replace("-", "_") 0172 0173 return path, lang, proj, item, args 0174 0175 0176 def get_hook (modpath, lang=None, proj=None, func=None, args=None, abort=False): 0177 """ 0178 Fetch a hook function. 0179 0180 Loads a hook function from a module obtained by applying L{get_module} 0181 to C{modpath}, C{lang}, and C{proj} parameters. 0182 If C{func} is C{None}, the function name defaults to module name; 0183 if C{func} is not C{None}, but function of that name is not found, 0184 then the function named C{<modname>_<func>} is additionally tried 0185 (where C{<modname>} is the last element in C{modpath}). 0186 If C{args} is not C{None}, then the loaded function is considered 0187 a hook factory, and the hook is created by calling it with C{args} string 0188 as argument list (it should have no surrounding parenthesis). 0189 0190 @param modpath: hook module 0191 @type modpath: string 0192 @param lang: language code 0193 @type lang: string 0194 @param proj: project code 0195 @type proj: string 0196 @param func: function name of hook or hook factory 0197 @type func: string 0198 @param args: argument string to hook factory 0199 @type args: string 0200 @param abort: whether to abort execution or raise exception if the hook 0201 cannot be loaded 0202 @type abort: bool 0203 0204 @returns: the hook 0205 """ 0206 0207 lmod, modpath = get_module(modpath, lang, proj, abort, wpath=True) 0208 modname = modpath.rsplit(".", 1)[-1] 0209 if func is None: 0210 func = modname 0211 func2 = "\0" 0212 else: 0213 func2 = "%s_%s" % (modname, func) 0214 call = getattr(lmod, func, None) or getattr(lmod, func2, None) 0215 if call is None: 0216 _raise_or_abort(_("@info", 0217 "Module '%(mod)s' does not define " 0218 "'%(func)s' function.", 0219 mod=modpath, func=func), abort) 0220 if args is not None: 0221 try: 0222 call = eval("call(%s)" % args) 0223 except Exception as e: 0224 fspec = "%s/%s" % (modpath, func) 0225 _raise_or_abort(_("@info", 0226 "Cannot create hook by applying function " 0227 "'%(func)s' to argument list %(args)s; " 0228 "reported error:\n%(msg)s", 0229 func=fspec, args=repr(args), msg=e), 0230 abort) 0231 0232 return call 0233 0234 0235 def get_hook_ireq (ireq, abort=False): 0236 """ 0237 Like L{get_hook}, but the hook is specified by 0238 L{item request<split_ireq>}. 0239 0240 For a module C{pology.FOO} which defines the C{FOO()} hook function, 0241 the hook specification is simply C{FOO}. 0242 If the hook function is named C{BAR()} instead of C{FOO()}, 0243 the hook specification is given as C{FOO/BAR}; 0244 if the hook function is named C{FOO_BAR()}, i.e. the specification 0245 would be C{FOO/FOO_BAR}, it can be folded to C{FOO/BAR}. 0246 Language-specific hooks (C{pology.lang.LANG.FOO}) are aditionally 0247 preceded by the language code and colon, as C{LANG:FOO} or C{LANG:FOO/BAR}. 0248 Project-specific hooks (C{pology.proj.PROJ.FOO}) are aditionally 0249 preceded by the project code and percent, as C{PROJ%FOO} or C{LANG%FOO/BAR}. 0250 If the hook is both language- and project- specific, language and project 0251 qualifiers can both be added: C{LANG:PROJ%FOO} or C{LANG:PROJ%FOO/BAR}; 0252 ordering, C{LANG:PROJ%...} or C{PROJ%LANG:...}, is not significant. 0253 0254 If the hook is not a plain hook, but a hook factory function, 0255 the factory arguments are supplied after the basic hook specification, 0256 separated by tilde: C{LANG:PROJ%FOO/BAR~ARGLIST} 0257 (where C{LANG:}, C{PROJ%} and C{/BAR} may be omitted under previously 0258 listed conditions). 0259 Argument list is formatted just like it would be passed in Python code 0260 to the factory function, omitting the surrounding parenthesis. 0261 """ 0262 0263 return _by_ireq(ireq, get_hook, abort=abort) 0264 0265 0266 def _by_ireq (ireq, getter, abort=False): 0267 """ 0268 Get item using C{getter(path, lang, proj, item, abort)} 0269 method, by applying it to parsed item request string. 0270 """ 0271 0272 path, lang, proj, item, args = split_ireq(ireq, abort) 0273 return getter(path, lang, proj, item, args, abort) 0274 0275 0276 def _raise_or_abort (errmsg, abort, exc=PologyError): 0277 """ 0278 Raise an exception or abort execution with given error message, 0279 based on the value of C{abort}. 0280 """ 0281 0282 if abort: 0283 error(errmsg) 0284 else: 0285 raise exc(errmsg) 0286 0287 0288 def get_result (modpath, lang=None, proj=None, func=None, args="", abort=False): 0289 """ 0290 Fetch the result of a function evaluation. 0291 0292 Executes function from the module loaded by applying L{get_module} 0293 to C{modpath}, C{lang}, and C{proj} parameters. 0294 If C{func} is not given, the function name defaults to module name. 0295 C{args} is the string representing the argument list 0296 to the function call (without surrounding parenthesis). 0297 0298 @param modpath: function module 0299 @type modpath: string 0300 @param lang: language code 0301 @type lang: string 0302 @param proj: project code 0303 @type proj: string 0304 @param func: function name within the module 0305 @type func: string 0306 @param args: argument string to function call 0307 @type args: string 0308 @param abort: if the function is not found, abort or report C{None} 0309 @type abort: bool 0310 0311 @returns: the value returned by the function call 0312 """ 0313 0314 fmod, modpath = get_module(modpath, lang, proj, abort, wpath=True) 0315 modname = modpath.rsplit(".", 1)[-1] 0316 if func is None: 0317 func = modname 0318 call = getattr(fmod, func, None) 0319 if call is None: 0320 _raise_or_abort(_("@info", 0321 "Module '%(mod)s' does not define " 0322 "function '%(func)s'.", 0323 mod=modpath, func=func), abort) 0324 try: 0325 res = eval("call(%s)" % args) 0326 except Exception as e: 0327 fspec = "%s/%s" % (modpath, func) 0328 _raise_or_abort(_("@info", 0329 "Evaluating function '%(func)s' " 0330 "with argument list %(args)s failed; " 0331 "reported error:\n%(msg)s", 0332 func=fspec, args=repr(args), msg=e), 0333 abort) 0334 0335 return res 0336 0337 0338 def get_result_ireq (ireq, abort=False): 0339 """ 0340 Like L{get_result}, but the function is specified by 0341 L{item request<split_ireq>}. 0342 """ 0343 0344 return _by_ireq(ireq, get_result, abort=abort) 0345