Check-in [492cb2aacf]
Overview
Comment: | A bit more PEP8, without undoing all readability. |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA1: |
492cb2aacf7d42f25559180a64762cdf |
User & Date: | mario on 2015-05-18 22:21:55 |
Other Links: | manifest | tags |
Context
2015-05-19
| ||
22:37 | Fetch all 5 available pages from Xiph directory. Rewrite to use more directed regex extraction mode. Minor help page updates. check-in: 2590478319 user: mario tags: trunk | |
2015-05-18
| ||
22:21 | A bit more PEP8, without undoing all readability. check-in: 492cb2aacf user: mario tags: trunk | |
22:21 | Allow Exif-wrapped JPEGs as well (not just JFIF regexp check). Comment on merging row["favourite"] merge via prepare_filters list. check-in: 6d03cb84d3 user: mario tags: trunk | |
Changes
Modified action.py from [ec6873c4fb] to [005725eacf].
︙ | ︙ | |||
60 61 62 63 64 65 66 | "*/*": "href", # "href" for unknown responses "url/direct": "srv", "url/youtube": "href", "url/http": "href", "audio/x-pn-realaudio": "ram", "application/json": "json", "application/smil": "smil", | | | | | | 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | "*/*": "href", # "href" for unknown responses "url/direct": "srv", "url/youtube": "href", "url/http": "href", "audio/x-pn-realaudio": "ram", "application/json": "json", "application/smil": "smil", "application/vnd.ms-wpl": "smil", "audio/x-ms-wax": "asx", "video/x-ms-asf": "asx", "x-urn/st2-script": "script", # unused "application/x-shockwave-flash": "href", # fallback } # Audio type MIME map mediafmt_t = { "audio/mpeg": "mp3", "audio/ogg": "ogg", "audio/aac": "aac", "audio/aacp": "aac", "audio/midi": "midi", "audio/mod": "mod", "audio/it+zip": "mod", "audio/s3+zip": "mod", "audio/xm+zip": "mod", } |
︙ | ︙ |
Modified pluginconf.py from [d5e7efa673] to [2b0d85b26d].
1 2 3 4 5 6 7 8 | # encoding: UTF-8 # api: python # type: handler # category: io # title: Plugin configuration # description: Read meta data, pyz/package contents, module locating # version: 0.6 # priority: core | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | # encoding: UTF-8 # api: python # type: handler # category: io # title: Plugin configuration # description: Read meta data, pyz/package contents, module locating # version: 0.6 # priority: core # docs: http://fossil.include-once.org/streamtuner2/wiki/plugin+meta+data # config: - # # Provides plugin lookup and meta data extraction utility functions. # It's used to abstract module+option management in applications. # For consolidating internal use and external/tool accessibility. # # The key:value format is language-agnostic. It's basically YAML in # a topmost script comment. For Python only # hash comments though. # Uses common field names, a documentation block, and an obvious # `config: { .. }` spec for options and defaults. # # It neither imposes a specific module/plugin API, nor config storage, # and doesn't fixate module loading. It's really just meant to look # up meta infos. # This approach avoids in-code values/inspection, externalized meta # descriptors, and any hodgepodge or premature module loading just to # uncover module description fields. # # plugin_meta() # ‾‾‾‾‾‾‾‾‾‾‾‾‾ # Is the primary function to extract a meta dictionary from files. # It either reads from a given module= name, a literal fn=, or just # src= code, and as fallback inspects the last stack frame= else. # # module_list() # ‾‾‾‾‾‾‾‾‾‾‾‾‾ |
︙ | ︙ | |||
72 73 74 75 76 77 78 | # for example. # # Plugin loading thus becomes as simple as __import__("ext.local"). # The attachaed plugin_state config dictionary in most cases can just # list module basenames, if there's only one set to manage. | < > | > | > | > | | < | | | < < | 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | # for example. # # Plugin loading thus becomes as simple as __import__("ext.local"). # The attachaed plugin_state config dictionary in most cases can just # list module basenames, if there's only one set to manage. import sys import os import re import pkgutil import inspect try: from compat2and3 import gzip_decode except: from gzip import decompress as gzip_decode # Py3 only import zipfile import argparse __all__ = [ "get_data", "module_list", "plugin_meta", "dependency", "add_plugin_defaults" ] # Injectables # ‾‾‾‾‾‾‾‾‾‾‾ log_ERR = lambda *x: None # File lookup relation for get_data(), should name a top-level package. module_base = "config" # Package/module names for module_list() and plugin_meta() lookups. # All associated paths will be scanned for module/plugin basenames. plugin_base = ["channels"] # Resource retrieval # ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ # Fetches file content from install path or from within PYZ # archive. This is just an alias and convenience wrapper for # pkgutil.get_data(). # Utilizes the module_base / file_base as top-level reference. # def get_data(fn, decode=False, gz=False, file_base=None): try: bin = pkgutil.get_data(file_base or module_base, fn) if gz: bin = gzip_decode(bin) if decode: return bin.decode("utf-8", errors='ignore') else: return str(bin) except: # log_ERR("get_data() didn't find:", fn, "in", file_base) pass # Plugin name lookup # ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ # Search through ./plugins/ (and other configured plugin_base # names → paths) and get module basenames. # def module_list(extra_paths=[]): # Convert plugin_base package names into paths for iter_modules paths = [] for mp in plugin_base: if sys.modules.get(mp): paths += sys.modules[mp].__path__ elif os.path.exists(mp): paths.append(mp) # Should list plugins within zips as well as local paths ls = pkgutil.iter_modules(paths + extra_paths) return [name for loader, name, ispkg in ls] # Plugin => meta dict # ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ # This is a trivial wrapper to assemble a complete dictionary # of available/installed plugins. It associates each plugin name # with a its meta{} fields. # def all_plugin_meta(): return { name: plugin_meta(module=name) for name in module_list() } # Plugin meta data extraction # ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ # Can fetch infos from different sources: # # fn= read literal files, or .pyz contents |
︙ | ︙ | |||
174 175 176 177 178 179 180 | # (default) # def plugin_meta(fn=None, src=None, module=None, frame=1, extra_base=[]): # Try via pkgutil first, # find any plugins.* modules, or main packages if module: | | | | | | > | | | 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | # (default) # def plugin_meta(fn=None, src=None, module=None, frame=1, extra_base=[]): # Try via pkgutil first, # find any plugins.* modules, or main packages if module: fn = module for base in plugin_base + extra_base: try: src = get_data(fn=fn+".py", decode=True, file_base=base) if src: break except: continue # plugin_meta_extract() will print a notice later # Real filename/path elif fn and os.path.exists(fn): src = open(fn).read(4096) # Else get source directly from caller elif not src and not fn: |
︙ | ︙ | |||
207 208 209 210 211 212 213 | # Extract source comment into meta dict if not src: src = "" if not isinstance(src, str): src = src.decode("utf-8", errors='replace') return plugin_meta_extract(src, fn) | < | 208 209 210 211 212 213 214 215 216 217 218 219 220 221 | # Extract source comment into meta dict if not src: src = "" if not isinstance(src, str): src = src.decode("utf-8", errors='replace') return plugin_meta_extract(src, fn) # Comment and field extraction logic # ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ # Finds the first comment block. Splits key:value header # fields from comment. Turns everything into an dict, with # some stub fields if absent. |
︙ | ︙ | |||
241 242 243 244 245 246 247 | if not literal: src = rx.comment.search(src) if not src: log_ERR("Couldn't read source meta information:", fn) return meta src = src.group(0) src = rx.hash.sub("", src).strip() | | < | 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 | if not literal: src = rx.comment.search(src) if not src: log_ERR("Couldn't read source meta information:", fn) return meta src = src.group(0) src = rx.hash.sub("", src).strip() # Split comment block if src.find("\n\n") > 0: src, meta["doc"] = src.split("\n\n", 1) # Turn key:value lines into dictionary for field in rx.keyval.findall(src): meta[field[0]] = field[1].strip() meta["config"] = plugin_meta_config(meta.get("config") or "") return meta # Unpack config: structures # ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ # Further breaks up the meta['config'] descriptor. # Creates an array from JSON/YAML option lists. # |
︙ | ︙ | |||
281 282 283 284 285 286 287 | } for field in rx.options.findall(entry): opt[field[0]] = (field[1] or field[2] or field[3] or "").strip() config.append(opt) return config | < | | | | | < | 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 | } for field in rx.options.findall(entry): opt[field[0]] = (field[1] or field[2] or field[3] or "").strip() config.append(opt) return config # Comment extraction regexps # ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ # Pretty crude comment splitting approach. But works # well enough already. Technically a YAML parser would # do better; but is likely overkill. # class rx: comment = re.compile(r"""(^ {0,4}#.*\n)+""", re.M) hash = re.compile(r"""(^ {0,4}# *)""", re.M) keyval = re.compile(r""" ^([\w-]+):(.*$(?:\n(?![\w-]+:).+$)*) # plain key:value lines """, re.M | re.X) config = re.compile(r""" [\{\<] (.+?) [\}\>] # JSOL/YAML scheme {...} dicts """, re.X) options = re.compile(r""" ["':$]? (\w*) ["']? # key or ":key" or '$key' \s* [:=] \s* # "=" or ":" (?: " ([^"]*) " | ' ([^']*) ' # "quoted" or 'singl' values | ([^,]*) # or unquoted literals ) """, re.X) # ArgumentParser options conversion # ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ # As variation of in-application config: options, this method converts # cmdline argument specifiers. # |
︙ | ︙ | |||
347 348 349 350 351 352 353 | # Extract --flag names args = opt["arg"].split() + re.findall("-+\w+", opt["name"]) # Prepare mapping options typing = re.findall("bool|str|\[\]|const|false|true", opt["type"]) naming = re.findall("\[\]", opt["name"]) | | | | | > | | > > | > | > | < | 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 | # Extract --flag names args = opt["arg"].split() + re.findall("-+\w+", opt["name"]) # Prepare mapping options typing = re.findall("bool|str|\[\]|const|false|true", opt["type"]) naming = re.findall("\[\]", opt["name"]) name = re.findall("(?<!-)\\b\\w+", opt["name"]) nargs = re.findall("\\b\d+\\b|[\?\*\+]", opt["type"]) or [None] is_arr = "[]" in (naming + typing) and nargs == [None] is_bool = "bool" in typing false_b = "false" in typing or opt["value"] in ("0", "false") # print("\nname=", name, "is_arr=", is_arr, "is_bool=", is_bool, # "bool_d=", false_b, "naming=", naming, "typing=", typing) # Populate combination as far as ArgumentParser permits kwargs = dict( args = args, dest = name[0] if not name[0] in args else None, action = is_arr and "append" or is_bool and false_b and "store_false" or is_bool and "store_true" or "store", nargs = nargs[0], default = opt.get("default") or opt["value"], type = None if is_bool else ("int" in typing and int or "bool" in typing and bool or str), choices = opt["select"].split("|") if "select" in opt else None, required = "required" in opt or None, help = opt["description"] if not "hidden" in opt else argparse.SUPPRESS ) return {k: w for k, w in kwargs.items() if w is not None} # Minimal depends: probing # ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ # Now this definitely requires customization. Each plugin can carry # a list of (soft-) dependency names. # |
︙ | ︙ | |||
400 401 402 403 404 405 406 | self.have = all_plugin_meta() # dependencies on core modules are somewhat more interesting: for name in ("st2", "uikit", "config", "action"): self.have[name] = plugin_meta(module=name, extra_base=["config"]) self.have["streamtuner2"] = self.have["st2"] have = {} | | | > < > | > | > | > > > > | | | | | | | | | | < | | | | | | | | | > | > | 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 | self.have = all_plugin_meta() # dependencies on core modules are somewhat more interesting: for name in ("st2", "uikit", "config", "action"): self.have[name] = plugin_meta(module=name, extra_base=["config"]) self.have["streamtuner2"] = self.have["st2"] have = {} # depends: def depends(self, plugin): if plugin.get("depends"): d = self.deps(plugin["depends"]) if not self.cmp(d, self.have): return False return True # basic list pre-filtering (skip __init__, filter by api:, # exclude installed & same-version plugins) def valid(self, newpl): have_ver = self.have.get(id, {}).get("version", "0") id = newpl.get("$name", "__invalid") if id.find("__") == 0: pass elif newpl.get("api") != "streamtuner2": pass elif have_ver >= newpl.get("version", "0.0"): pass else: return True # Split trivial "pkg, mod >= 1, uikit < 4.0" list def deps(self, dep_str): d = [] for dep in re.split(r"\s*[,;]+\s*", dep_str): # skip deb:pkg-name, rpm:name, bin:name etc. if not len(dep) or dep.find(":") >= 0: continue # find comparison and version num dep += " >= 0" m = re.search(r"([\w.-]+)\s*([>=<!~]+)\s*([\d.]+([-~.]\w+)*)", dep) if m and m.group(2): d.append([m.group(i) for i in (1, 2, 3)]) return d # Do actual comparison def cmp(self, d, have): r = True for name, op, ver in d: # skip unknown plugins, might be python module references if not have.get(name, {}).get("version"): continue curr = have[name]["version"] tbl = { ">=": curr >= ver, "<=": curr <= ver, "==": curr == ver, ">": curr > ver, "<": curr < ver, "!=": curr != ver, } r &= tbl.get(op, True) return r # Add plugin defaults to conf.* store # ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ # Utility function which applies plugin meta data to a config # store. Which in the case of streamtuner2 is really just a # dictionary `conf{}` and a plugin list in `conf.plugins{}`. # # Adds each default option value: to conf_options{}. And sets # first plugin state (enabled/disabled) in conf_plugins{} list, # depending on priority: classifier. # def add_plugin_defaults(conf_options, conf_plugins, meta={}, module=""): # Option defaults, if not yet defined for opt in meta.get("config", []): if "name" in opt and "value" in opt: if opt["name"] not in conf_options: # typemap "bool" and "int" here if opt["type"] in ("bool", "boolean"): val = bool(opt["value"]) elif opt["type"] in ("int", "integer", "numeric"): val = int(opt["value"]) else: val = str(opt["value"]) conf_options[opt["name"]] = val # Initial plugin activation status if module and module not in conf_plugins: conf_plugins[module] = meta.get("priority") in ( "core", "builtin", "always", "default", "standard" ) |