@@ -1,6 +1,5 @@ -# # encoding: UTF-8 # api: streamtuner2 # type: class # title: global config object # description: reads ~/.config/streamtuner/*.json files @@ -12,23 +11,32 @@ # { arg: action, type: str *, name: action[], description: CLI interface commands. } # { arg: -x, type: boolean, name: exit, hidden: 1 } # { arg: --nt, type: boolean, name: nothreads, description: Disable threading/gtk_idle UI. } # version: 2.7 # priority: core +# depends: pluginconf >= 0.1, os, json, re, zlib, pkgutil # -# In the main application or module files which need access -# to a global conf.* object, just import this module as follows: +# Ties together the global conf.* object. It's typically used +# in the main application and modules with: # # from config import * # -# Here conf is already an instantiation of the underlying -# ConfigDoct class. +# The underlying ConfigDict class is already instantiated and +# imported as `conf` then. +# +# With .save() or .load() it handles storage as JSON. Both +# utility functions are also used for other cache files. +# More specific config stores are available per .netrc(), +# and .init_args(). # -# Also provides the logging function log.TYPE(...) and basic -# plugin handling code: plugin_meta() and module_list(), -# and the relative get_data() alias (files from pyzip/path). +# Whereas plugin utility code is available per plugin_meta(), +# module_list(), and get_data(). There's a prepared function +# for add_plugin_config() on initialization. # +# Also provides a simple logging interface with log.TYPE(...), +# which is also pre-instantiated. + from __future__ import print_function import os import sys import json @@ -39,10 +47,13 @@ import zlib import zipfile import inspect import pkgutil import argparse +from pluginconf import plugin_meta, module_list, get_data +import pluginconf + # export symbols __all__ = ["conf", "log", "plugin_meta", "module_list", "get_data", "find_executable"] @@ -135,22 +146,13 @@ self.windows = platform.system()=="Windows" self.pyquery = 1 self.debug = 0 - # each plugin has a .config dict list, we add defaults here - def add_plugin_defaults(self, meta, module=""): - - # options - config = meta.get("config", []) - for opt in config: - if ("name" in opt) and ("value" in opt) and (opt["name"] not in vars(self)): - self.__dict__[opt["name"]] = opt["value"] - - # plugin state - if module and module not in conf.plugins: - conf.plugins[module] = meta.get("priority") in ("core", "builtin", "always", "default", "standard") + # Add plugin names and default config: options from each .meta + def add_plugin_defaults(self, meta, name): + pluginconf.add_plugin_defaults(self, self.plugins, meta, name) # look at system binaries for standard audio players def find_player(self, typ="audio", default="xdg-open"): players = { @@ -291,12 +293,12 @@ # Use config:-style definitions for argv extraction, # such as: { arg: -D, name: debug, type: bool } def init_args(self, ap): - for opt in plugin_meta(frame=0).get("config"): - kwargs = self.argparse_map(opt) + for opt in plugin_meta(frame=1).get("config"): + kwargs = pluginconf.argparse_map(opt) if kwargs: #print kwargs ap.add_argument(*kwargs.pop("args"), **kwargs) return ap.parse_args() @@ -311,189 +313,10 @@ self.plugins[p_id] = 0 for p_id in (args.enable or []): self.plugins[p_id] = 1 - # Transform config: description into quirky ArgumentParser args. - # - # · An option entry requires an arg: parameter - unlike regular plugin options: - # { arg: -i, name: input[], type: str, description: input files } - # · Where list elements are indicated by appending `[]` to names, or `*`onto type - # specifiers (alternatively `?`, `+` or a numeric count). - # · Types `str` or `int` and `bool` are recognized (bool with false/true optionals). - # · Entries can also carry a `hidden: 1` or `required: 1` attribute. - # · And `help:` is an alias to `description:` - # · Same for `default:` instead of the normal `value:` - # · And `type: select` utilizes the `select: a|b|c` format as uaual. - # · ArgParsers const=, metavar= flag, or type=file are not aliased here. - # - def argparse_map(self, opt): - if not ("arg" in opt and opt["name"] and opt["type"]): - return {} - - # 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("(?= 3 and intfn and zipfile.is_zipfile(fn): - src = zipfile.ZipFile(fn, "r").read(intfn.strip("/")) - - if not src: - src = "" - if type(src) is not str: - src = src.decode("utf-8", errors='replace') - - return plugin_meta_extract(src, fn) - - -# Actual comment extraction logic -def plugin_meta_extract(src="", fn=None, literal=False): - - # defaults - meta = { - "id": os.path.splitext(os.path.basename(fn or "")), - "fn": fn, - "title": fn, "description": "no description", "config": [], - "type": "module", "api": "python", "doc": "" - } - - # extract coherent comment block, split doc section - 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) - - # key:value fields into dict - 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 -def plugin_meta_config(str): - config = [] - for entry in rx.config.findall(str): - opt = { "type": None, "name": None, "description": "", "value": None } - 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 -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) - # Simplified print wrapper: `log.err(...)` class log_printer(object): @@ -536,11 +359,16 @@ # instantiate right away log = log_printer() - - - -#-- populate global conf instance +# populate global conf instance conf = ConfigDict() log.PROC("ConfigDict() initialized") + +# tie in pluginconf.* +pluginconf.log_WARN = log.WARN +pluginconf.log_ERR = log.ERR +pluginconf.module_base = "config" +pluginconf.plugin_base = ["channels", conf.share + "/channels", conf.dir + "/plugins"] + +