from datetime import date from subprocess import check_output from urllib.parse import urlparse import click import requests import tomli_w class PypiPackage(): def __init__(self, package, session=None): self.package = package self.package_data = self.get_package_metadata(session) @property def pypi_name(self): return self.package_data["info"]["name"] def get_package_metadata(self, session): pkg_index = f'https://pypi.org/pypi/{self.package}/json' s = session or requests.Session() response = s.get(pkg_index) return response.json() def python_name(self): if self.pypi_name.startswith("python"): return self.pypi_name else: return f"python-{self.pypi_name}" def modules(self): """A naive way to get probable module name: replace any occurrence of '-' in package PyPI name with '_' and return the result.""" return [self.pypi_name.replace("-", "_")] def source_url(self): return "%{pypi_source " + self.pypi_name + "}" def version(self): return self.package_data["info"]["version"] def license(self): return self.package_data["info"]["license"] def summary(self): return self.package_data["info"]["summary"] def project_url(self): try: return self.package_data["info"]["project_urls"]["Homepage"] # It may happen that no Homepage is listed with the project # In this case fall back to the safe PyPI URL except KeyError: return self.package_data["info"]["package_url"] def archive_name(self, version): for entry in self.package_data["releases"][version]: if entry["packagetype"] == "sdist": return "-".join(entry["filename"].split("-")[:-1]) def changelog_head(email, name, changelog_date): """ :returns f'{date} {name} <{email}>'""" if not changelog_date: changelog_date = (date.today()).strftime('%a %b %d %Y') if email or name: return f'{changelog_date} {name} <{email}>' result = check_output(['rpmdev-packager']) result = result.decode().strip() return f'{changelog_date} {result}' def changelog_msg(): """Return a default changelog message.""" return "Package generated with pyp2spec" def get_description(package): """Return a default package description.""" return f"This is package '{package}' generated automatically by pyp2spec." def is_url(package): parsed = urlparse(package) if all((parsed.scheme, parsed.netloc)): return True return False def create_config_contents(package, description=None, release=None, message=None, email=None, name=None, version=None, summary=None, date=None, session=None): """Use `package` and provided kwargs to create the whole config contents. Return contents. """ contents = {} # `package` is not a URL -> it's a package name, look for it on PyPI if not is_url(package): pkg = PypiPackage(package, session) # If the arguments weren't provided via CLI, # get them from the stored package object data or the default values if not message: message = changelog_msg() if not description: description = get_description(package) if not summary: summary = pkg.summary() if not version: version = pkg.version() # These items don't depend on information gathered from the package source contents["changelog_msg"] = message contents["changelog_head"] = changelog_head(email, name, date) contents["release"] = release or "1" contents["description"] = description # There items may be either set via CLI or gotten from package object contents["summary"] = summary contents["version"] = version # Set the values from the package object contents["pypi_name"] = pkg.pypi_name contents["python_name"] = pkg.python_name() contents["modules"] = pkg.modules() contents["license"] = pkg.license() contents["url"] = pkg.project_url() contents["source"] = pkg.source_url() contents["archive_name"] = pkg.archive_name(version) return contents def save_config(contents, output=None): """Write config file to a given destination. If none is provided, save it to current directory with package name as file name. """ if not output: package = contents["python_name"] output = f"./{package}.conf" with open(output, "wb") as f: tomli_w.dump(contents, f, multiline_strings=True) return output def create_config(package, conf_output, description, release, message, email, packagername, version, summary, date): contents = create_config_contents( package, conf_output, description, release, message, email, packagername, version, summary, date ) return save_config(contents, conf_output) @click.command() @click.argument( "package") @click.option( "--conf-output", "-o", help="Provide custom output for configuration file", ) @click.option( "--description", "-d", help="Provide description for the package", ) @click.option( "--release", "-r", help="Provide custom release (corresponds with Release in spec file)", ) @click.option( "--message", "-m", help="Provide changelog message for the package", ) @click.option( "--email", "-e", help="Provide e-mail for changelog", ) @click.option( "--packagername", "-n", help="Provide packager name for changelog", ) @click.option( "--version", "-v", help="Provide package version to query PyPI for", ) @click.option( "--summary", "-s", help="Provide custom package summary", ) @click.option( "--date", help="Provide custom date for changelog", ) def main(package, conf_output, description, release, message, email, packagername, version, summary, date): create_config(package, conf_output, description, release, message, email, packagername, version, summary, date) if __name__ == "__main__": main()