Index: README.md ================================================================== --- README.md +++ README.md @@ -1,8 +1,8 @@ Provides meta data extraction and plugin basename lookup. And it’s meant for in-application feature and option management. -The [descriptor format](https://fossil.include-once.org/pluginspec/index) +The [descriptor format](https://fossil.include-once.org/pluginspec/) (*self-contained* atop each script) is basically: # encoding: utf-8 # api: python # type: handler @@ -56,11 +56,11 @@ Which declares module and plugin basenames, which get used for lookups by just module= names in e.g. `module_list()`. (Works for literal setups and within PYZ bundles). This is unnecessary for plain `plugin_meta(fn=)` extraction. -#### plugin_meta( module= | filename= | src= | frame= ) +#### [plugin_meta](https://fossil.include-once.org/pluginspec/doc/trunk/html/index.html)( module= | filename= | src= | frame= ) Returns a meta data dictionary for the given module name, file, source code, or caller frame: { "title": "Compound★", @@ -182,16 +182,16 @@ [project] name = "projectname" It can be invoked via `python -m pluginconf.flit build` or even -`flit-pluginconf build`. It's not very robust however. +`flit-pluginconf build`. Field mapping isn't very robust yet. ## other modules - * DependencyValidation is now in `pluginconf.depends` + * `pluginconf.depends` provides `Check` for .valid() and .depends() probing * argparse_map() might also end up in a separate module. #### Caveats Index: pluginconf/depends.py ================================================================== --- pluginconf/depends.py +++ pluginconf/depends.py @@ -12,12 +12,12 @@ # # This is a rather basic depends: checker, mostly for local and # installable modules. It's largely built around streamtuner2 # requirements, and should be customized. # -# DependencyValidation().depends()/.valid() -# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ +# Check().depends()/.valid() +# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ # Probes a new plugins` depends: list against installed base modules. # Utilizes each version: fields and allows for virtual modules, or # alternatives and honors alias: names. # @@ -24,26 +24,25 @@ """ Dependency validation and consistency checker for updates """ import sys import re +#import zipfile +import logging import pluginconf try: from distutils.spawn import find_executable except ImportError: try: from compat2and3 import find_executable except ImportError: - def find_executable(name): - pass -import zipfile -import logging + find_executable = lambda name: False # Minimal depends: probing # ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ -class DependencyValidation(object): +class Check(object): """ Now this definitely requires customization. Each plugin can carry a list of (soft-) dependency names. \# depends: config, appcore >= 2.0, bin:wkhtmltoimage, python < 3.5 @@ -67,20 +66,24 @@ log : logging warning handler have : dict accumulated list of existing/virtual plugins """ - - """ supported APIs """ + + # supported APIs api = ["python", "streamtuner2"] - - """ debugging """ + + # debugging log = logging.getLogger("pluginconf.dependency") - # prepare list of known plugins and versions + # ignore bin:… or python:… package in depends + system_deps = False + def __init__(self, add={}, core=["st2", "uikit", "config", "action"]): """ + Prepare list of known plugins and versions in self.have={} + Parameters ---------- add : dict name to pmd list of existing/core/virtual plugins (can define versions or own dependencies) @@ -112,64 +115,70 @@ for name, meta in self.have.copy().items(): if meta.get("alias"): for alias in re.split(r"\s*[,;]\s*", meta["alias"]): self.have[alias] = self.have[name] - # basic plugin pre-screening (skip __init__, filter by api:, - # exclude installed & same-version plugins) def valid(self, new_plugin): - """ check plugin info from repository stream (fields there $name, $file, $dist, api, id, depends, etc) """ + """ + Plugin pre-screening from online repository stream. + Fields are $name, $file, $dist, api, id, depends, etc + Exclude installed or for newer-version presence. + """ if not "$name" in new_plugin: self.log.warning(".valid() checks online plugin lists, requires $name") - id = new_plugin.get("$name", "__invalid") - have_ver = self.have.get(id, {}).get("version", "0") - if id.find("__") == 0: + name = new_plugin.get("$name", "__invalid") + have_ver = self.have.get(name, {}).get("version", "0") + if name.find("__") == 0: self.log.debug("wrong/no id") elif new_plugin.get("api") not in self.api: self.log.debug("not in allowed APIs") elif {new_plugin.get("status"), new_plugin.get("priority")} & {"obsolete", "broken"}: self.log.debug("wrong status (obsolete/broken)") elif have_ver >= new_plugin.get("version", "0.0"): self.log.debug("newer version already installed") else: return True + return False - # Verify depends: and breaks: against existing plugins/modules def depends(self, plugin): - """ test depends: and breaks: """ + """ Verify depends: and breaks: against existing plugins/modules """ result = True if plugin.get("depends"): result &= self.and_or(self.split(plugin["depends"]), self.have) if plugin.get("breaks"): result &= self.neither(self.split(plugin["breaks"]), self.have) self.log.debug("plugin '%s' matching requirements: %i", plugin["id"], result) return result - # Split trivial "pkg | alt, mod>=1, uikit<4.0" string into nested list [[dep],[alt,alt],[dep]] def split(self, dep_str): + """ + Split trivial "pkg | alt, mod>=1, uikit<4.0" string + into nested list [ [alt, alt], [dep], [dep] ]; + with each entry comprised of (name, operator, version). + """ dep_cmp = [] for alt_str in re.split(r"\s*[,;]+\s*", dep_str): alt_cmp = [] # split alternatives | for part in re.split(r"\s*\|+\s*", alt_str): # skip deb:pkg-name, rpm:name, bin:name etc. - if not len(part): + if not part: continue if part.find(":") >= 0: self.have[part] = {"version": self.module_test(*part.split(":"))} # find comparison and version num part += " >= 0" - m = re.search(r"([\w.:-]+)\s*\(?\s*([>==": curr > ver, "<": curr < ver, "!=": curr != ver, } - r = tbl.get(op, True) - #print "log.VERSION_COMPARE: ", name, " → (", curr, op, ver, ") == ", r - return r + result = tbl.get(operator, True) + self.log.debug("VERSION_COMPARE: %s → (%s %s %s) == %s", name, curr, operator, ver, result) + return result - # Compare nested structure of [[dep],[alt,alt]] - def and_or(self, deps, have, r=True): + def and_or(self, deps, have, inner_true=True): + """ Compare nested structure of [[dep],[alt,alt]] """ #print deps return not False in [ - True in [self.cmp(d, have) for d in alternatives] for alternatives in deps + inner_true in [self.cmp(d, have) for d in alternatives] for alternatives in deps ] - # Breaks/Conflicts: check [[or],[or]] def neither(self, deps, have): + """ Breaks/Conflicts: check [[or],[or]] """ return not True in [ self.cmp(d, have, absent=None) for cnd in deps for d in cnd ] - # Resolves/injects complex "bin:name" or "python:name" dependency URNs - def module_test(self, type, name): - return "1" # disabled for now - if "_" + type in dir(self): - return "1" if bool(getattr(self, "_" + type)(name)) else "-1" - - # `bin:name` lookup - def _bin(self, name): + def module_test(self, urn, name): + """ Probes "bin:name" or "python:name" dependency URNs """ + if not self.system_deps: + return "1" + if "_" + urn in dir(self): + if bool(getattr(self, "_" + urn)(name)): + return "1" + return "-1" # basically a negative version -v1 + + @staticmethod + def _bin(name): + """ `bin:name` lookup """ return find_executable(name) - # `python:module` test - def _python(self, name): + @staticmethod + def _python(name): + """ `python:module` test """ return __import__("imp").find_module(name) is not None - Index: test/depends.py ================================================================== --- test/depends.py +++ test/depends.py @@ -15,11 +15,11 @@ logging.basicConfig(level=logging.DEBUG) @pytest.fixture def check(): - deps = pluginconf.depends.DependencyValidation( + deps = pluginconf.depends.Check( add={"core": 2.555, "config": 2.0, "existing": 0.1}, core=["IMPLICIT"], ) deps.api = ["python", "foobar"] return deps