ADDED pluginconf/flit.py Index: pluginconf/flit.py ================================================================== --- pluginconf/flit.py +++ pluginconf/flit.py @@ -0,0 +1,179 @@ +# encoding: utf-8 +# api: pep517 +# title: flit backend +# description: wraps flit_core.buildapi +# version: 0.1 +# depends: python:flit (>=3.0, <4.0) +# license: BSD-3-Clause +# priority: extra +# src: ~/.local/lib/python3.8/site-packages/flit_core/ +# +# This is supposed to become an alternative to pluginconf.setup, +# using flit as pep517 build backend. But adding automagic field +# lookup of course. +# +# +# [build-system] +# requires = ["flit_core", "pluginconf"] +# build-backend = "pluginconf.flit" +# +# [project] +# name = "foobar" +# dynamic = ["version", "description"] +# +# + +import os +import re +import os.path +import pathlib +import functools + +import pluginconf +import pluginconf.setup + +import flit_core.common + + +#-- patch functions + +def patch_flit_config(path): + """ read_flit_config() with preset dynamic fields """ + d = tomllib.loads(path.read_text('utf-8')) + + # make fields dynamic + if not "dynamic" in d["project"]: + d["project"]["dynamic"] = [] + for dyn in ['description', 'version']: + if dyn in d["project"]: + del d["project"][dyn] + if not dyn in d["project"]["dynamic"]: + d["project"]["dynamic"].append(dyn) + print(d) + + # turn it into LoadedConfig + return prep_toml_config(d, path) + +# override make_metadata +def patch_metadata(module, ini_info): + meta = { + "name": module.name, + "provides": [module.name] + } + meta.update(ini_info.metadata) + meta.update( + pmd_meta(filename=module.file) + ) + return flit_core.common.Metadata(meta) + +# inject +flit_core.common.make_metadata = patch_metadata + + +#-- map plugin meta to flit Metadata + +def pmd_meta(filename): + """ enjoin PMD fields with flit meta data """ + pmd = pluginconf.plugin_meta(filename=filename) + + meta = dict( + summary = pmd.get("description"), + version = pmd.get("version"), + home_page = pmd.get("url"), + author = pmd.get("author"), # should split this into mail and name + author_email = None, + maintainer = None, + maintainer_email = None, + license = pmd.get("license"), + keywords = pmd.get("keywords"), + download_url = None, + requires_python = pluginconf.setup._python_requires(pmd).get("python_requires", ">= 2.7"), + platform = (), + supported_platform = (), + classifiers = list(pluginconf.setup._classifiers(pmd)) + + pluginconf.setup._trove_license(pmd) + + pluginconf.setup._trove_status(pmd), + provides = (), + requires = pluginconf.setup._install_requires(pmd).get("install_requires") or (), + obsoletes = (), + project_urls = [f"{k}, {v}" for k,v in pluginconf.setup._project_urls(pmd).items()], + provides_dist = (), + requires_dist = pluginconf.setup._install_requires(pmd).get("install_requires") or (), + obsoletes_dist = (), + requires_external = (), + provides_extra = (), + ) + meta.update({k[5:]: v for k,v in pluginconf.setup._plugin_doc(pmd).items()}) + meta.update({k[5:]: v for k,v in pluginconf.setup._get_readme().items()}) + + return meta + + +# 'metadata', 'module', 'referenced_files', 'reqs_by_extra', +# 'sdist_exclude_patterns', 'sdist_include_patterns'] +# +# ini.metadata = +# {'name': 'basename', 'requires_dist': []} +# +# ini.module = +# basename +# +# ini.dynamic_metadata = +# {'version', 'description'} +# +# ini.__dict__ = +# { +# 'module': 'pluginconf', +# 'metadata': { +# 'name': 'pluginconf', +# 'version': '1.0', +# 'summary': '...', +# 'requires_dist': [] +# }, +# 'reqs_by_extra': {}, +# 'entrypoints': {}, +# 'referenced_files': [], +# 'sdist_include_patterns': [], +# 'sdist_exclude_patterns': [], +# 'dynamic_metadata': [], +# 'data_directory': None +# } + +#module = Module(ini.module, pathlib.Path.cwd()) +#print(module.__dict__) +#print(module.file) +# {'name': 'pluginconf', 'path': +# PosixPath('/home/mario/projects/pluginconf/pluginconf'), 'is_package': +# True, 'prefix': '', 'source_dir': +# PosixPath('/home/mario/projects/pluginconf')} + +#MD = patch_metadata(module, ini) +#print(MD.__dict__) + + +import flit_core.buildapi + +flit_core.buildapi.prepare_metadata_for_build_wheel("./build/") + + + +#-- buildapi +# +# These need to be late imports; +# else they'll bind the original helper functions +from flit_core.buildapi import ( + get_requires_for_build_wheel, + get_requires_for_build_sdist, + get_requires_for_build_editable, + prepare_metadata_for_build_wheel, + prepare_metadata_for_build_editable, + build_wheel, + build_editable, + build_sdist, +) + +import flit + +# as invocation point +def main(argv=None): + return flit.main(argv) Index: pluginconf/setup.py ================================================================== --- pluginconf/setup.py +++ pluginconf/setup.py @@ -1,11 +1,11 @@ # encoding: utf-8 # api: setuptools # type: functions # title: setup() wrapper # description: utilizes PMD to populate some package fields -# version: 0.3 +# version: 0.4 # license: PD # # Utilizes plugin meta data to cut down setup.py incantations # to basically just: # @@ -58,23 +58,124 @@ def _get_readme(): """ get README.md contents """ for fn,mime in ("README.md", "text/markdown"), ("README.rst", "text/x-rst"), ("README.txt", "text/plain"): if os.path.exists(fn): with open(fn, "r") as f: - return f.read(), mime - #system("pandoc -f markdown -t rst README.md > README.rst") - return "", "text/plain" + return { + "long_description": f.read(), + "long_description_content_type": mime, + } + return { + "long_description": "", + "long_description_content_type": "text/plain", + } + +def _plugin_doc(pmd): + """ use comment block """ + return { + "long_description": pmd["doc"], + "long_description_content_type": pmd.get("doc_format", "text/plain"), + } + +def _python_requires(pmd): + """ # depends: python >= 3.5 """ + deps = re.findall("python\s*\(?(>=?\s?[\d.]+)", pmd.get("depends", "")) + if deps: + return {"python_requires": deps[0]} + return {} + +def _install_requires(pmd): + """ # depends: python:module, pip:module """ + deps = re.findall("(?:python|pip):([\w\-]+)\s*(\(?[<=>\s\d.\-]+)?", pmd.get("depends", "")) + if deps: + return {"install_requires": [name+re.sub("[^<=>\d.\-]", "", ver) for name,ver in deps]} + return {} + +def _extras_require(pmd): + """ # suggest: line """ + deps = re.findall("(?:python|pip):([\w\-]+)\s*\(?\s*([>=<]+\s*[\d.\-]+)", pmd.get("suggests", "")) + if deps: + return dict(deps) + return {} + +def _project_urls(pmd, exclude=["url"]): + """ # other-url: https://... """ + urls = {} + for k,url in pmd.items(): + if type(url) is str and k not in exclude and re.match("https?://\S+", url): + urls[k.title()] = url + return urls + +def _classifiers(pmd): + """ # classifiers: / keywords: / category: """ + for field in ("api", "category", "type", "keywords", "classifiers"): + field = pmd.get(field, "") + field = re.findall("(\w{4,})", field) + rx = "|".join(field) + if not rx: + continue + for line in topic_trove: + if re.search("::[^:]*("+rx+")[^:]*$", line, re.I): + yield line + +def _trove_license(pmd): + """ license: to License :: """ + trove_licenses = { + "MITL?": "License :: OSI Approved :: MIT License", + "PD|Public Domain": "License :: Public Domain", + "ASL": "License :: OSI Approved :: Apache Software License", + "art": "License :: OSI Approved :: Artistic License", + "BSDL?": "License :: OSI Approved :: BSD License", + "CPL": "License :: OSI Approved :: Common Public License", + "AGPL.*3": "License :: OSI Approved :: GNU Affero General Public License v3", + "AGPLv*3\+": "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)", + "GPL": "License :: OSI Approved :: GNU General Public License (GPL)", + "GPL.*3": "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", + "LGPL": "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", + "MPL": "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)", + "Pyth": "License :: OSI Approved :: Python Software Foundation License" + } + for rx, trove in trove_licenses.items(): + if re.search(rx, pmd["license"], re.I): + return [trove] + return [] + +def _trove_status(pmd): + """ state: to DevStatus :: """ + trove_status = { + "pre|release|cand": "Development Status :: 2 - Pre-Alpha", + "alpha": "Development Status :: 3 - Alpha", + "beta": "Development Status :: 4 - Beta", + "stable": "Development Status :: 5 - Production/Stable", + "mature": "Development Status :: 6 - Mature" + } + for rx, trove in trove_status.items(): + state = pmd.get("state") or pmd.get("status") or "alpha" + if re.search(rx, state, re.I): + return [trove] + return [] + +def _datafiles_man(): + """ data_files= """ + for man in glob.glob("man*/*.[12345678]"): + section = man[-1] + yield ("man/man"+section, [man],) + +def _keywords(pmd): + """ keywords= """ + return pmd.get("keywords") or pmd.get("category") + def setup(debug=0, **kwargs): """ - Wrapper around `setuptools.setup()` which adds some defaults - and plugin meta data import, with two shortcut params: - - fn="pkg/main.py", - long_description="@README.md" + Wrapper around `setuptools.setup()` which adds some defaults + and plugin meta data import, with two shortcut params: + + fn="pkg/main.py", + long_description="@README.md" - Other setup() params work as usual. + Other setup() params work as usual. """ # stub values stub = { "classifiers": [], @@ -91,24 +192,27 @@ for k,v in stub.items(): if not k in kwargs: kwargs[k] = v # package name - if not "name" in kwargs: + if "name" not in kwargs and kwargs.get("packages"): kwargs["name"] = kwargs["packages"][0] # read README - if not "long_description" in kwargs or re.match("^@?([.\w]+/)*README.(\w+)$", kwargs.get("long_description", "-")): - kwargs["long_description"], kwargs["long_description_content_type"] = _get_readme() + if not "long_description" in kwargs: + kwargs.update(_get_readme()) # search name= package if no fn= given + if kwargs.get("filename"): + kwargs["fn"] = kwargs["filename"] + del kwargs["filename"] if not kwargs.get("fn") and kwargs.get("name"): kwargs["fn"] = _name_to_fn(kwargs["name"]) # read plugin meta data (PMD) pmd = {} - pmd = pluginconf.plugin_meta(fn=kwargs["fn"]) + pmd = pluginconf.plugin_meta(filename=kwargs["fn"]) # id: if no name= still if pmd.get("id") and not kwargs.get("name"): if pmd["id"] == "__init__": pmd["id"] = re.findall("([\w\.\-]+)/__init__.+$", kwargs["fn"])[0] @@ -119,87 +223,41 @@ # version:, description:, author: for field in "version", "description", "license", "author", "url": if field in pmd and not field in kwargs: kwargs[field] = pmd[field] + # other urls: - for k,url in pmd.items(): - if type(url) is str and k != "url" and re.match("https?://\S+", url): - kwargs["project_urls"][k.title()] = url + kwargs["project_urls"].update(_project_urls(pmd)) # depends: if "depends" in pmd: - deps = re.findall("python\s*\(?(>=?\s?[\d.]+)", pmd["depends"]) - if deps: - kwargs["python_requires"] = deps[0] + kwargs.update(_python_requires(pmd)) if "depends" in pmd and not kwargs["install_requires"]: - deps = re.findall("(?:python|pip):([\w\-]+)\s*(\(?[<=>\s\d.\-]+)?", pmd["depends"]) - if deps: - kwargs["install_requires"] = [name+re.sub("[^<=>\d.\-]", "", ver) for name,ver in deps] + kwargs.update(_install_requires(pmd)) # suggests: if "suggests" in pmd and not kwargs["extras_require"]: - deps = re.findall("(?:python|pip):([\w\-]+)\s*\(?\s*([>=<]+\s*[\d.\-]+)", pmd["suggests"]) - if deps: - kwargs["extras_require"].update(dict(deps)) + kwargs["extras_require"].update(_extras_require(pmd)) # doc: if not kwargs.get("long_description"): - kwargs["long_description"] = pmd["doc"] - kwargs["long_description_content_type"] = pmd.get("doc_format", "text/plain") + kwargs.update(_plugin_doc(pmd)) + # keywords= if not "keywords" in kwargs: - if "keywords" in pmd: - kwargs["keywords"] = pmd["keywords"] - elif "category" in pmd: - kwargs["keywords"] = pmd["category"] + kwargs["keywords"] = _keywords(pmd) # automatic inclusions - if not "data_files" in kwargs: - kwargs["data_files"] = [] - for man in glob.glob("man*/*.[12345678]"): - section = man[-1] - kwargs["data_files"].append(("man/man"+section, [man],)) + kwargs["data_files"] = kwargs.get("data_files", []) + list(_datafiles_man()) # classifiers= - trove_map = { - "license": { - "MITL?": "License :: OSI Approved :: MIT License", - "PD|Public Domain": "License :: Public Domain", - "ASL": "License :: OSI Approved :: Apache Software License", - "art": "License :: OSI Approved :: Artistic License", - "BSDL?": "License :: OSI Approved :: BSD License", - "CPL": "License :: OSI Approved :: Common Public License", - "AGPL.*3": "License :: OSI Approved :: GNU Affero General Public License v3", - "AGPLv*3\+": "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)", - "GPL": "License :: OSI Approved :: GNU General Public License (GPL)", - "GPL.*3": "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", - "LGPL": "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", - "MPL": "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)", - "Pyth": "License :: OSI Approved :: Python Software Foundation License" - }, - "state": { - "pre|release|cand": "Development Status :: 2 - Pre-Alpha", - "alpha": "Development Status :: 3 - Alpha", - "beta": "Development Status :: 4 - Beta", - "stable": "Development Status :: 5 - Production/Stable", - "mature": "Development Status :: 6 - Mature" - } - } # license: if pmd.get("license") and not any(re.match("License ::", l) for l in kwargs["classifiers"]): - for rx,trove in trove_map["license"].items(): - if re.search(rx, pmd["license"], re.I): - kwargs["classifiers"].append(trove) + kwargs["classifiers"].extend(_trove_license(pmd)) # state: if pmd.get("state", pmd.get("status")) and not any(re.match("Development Status ::", l) for l in kwargs["classifiers"]): - for rx,trove in trove_map["state"].items(): - if re.search(rx, pmd.get("state", pmd.get("status", "stable")), re.I): - kwargs["classifiers"].append(trove) + kwargs["classifiers"].extend(_trove_status(pmd)) # topics:: - rx = "|".join(re.findall("(\w{4,})", " | ".join([pmd.get(f, "---") for f in ("api", "category", "type", "keywords", "classifiers")]))) - for line in topic_trove: - if re.search("::[^:]*("+rx+")[^:]*$", line, re.I): - if line not in kwargs["classifiers"]: - kwargs["classifiers"].append(line) + kwargs["classifiers"].extend(list(_classifiers(pmd))) # handover if debug: import pprint pprint.pprint(kwargs)