# -*- coding: utf-8 -*-
"""
Provide general information about the system, user etc.
"""
import copy
import getpass
import os
import socket
from enum import Enum
from operator import itemgetter
from . import shell
from .exceptions import (
GitNotConfigured,
GitNotInstalled,
PyScaffoldTooOld,
ShellCommandException
)
from .templates import licenses
from .update import read_setupcfg
from .utils import chdir, levenshtein
[docs]class GitEnv(Enum):
author_name = "GIT_AUTHOR_NAME"
author_email = "GIT_AUTHOR_EMAIL"
author_date = "GIT_AUTHOR_DATE"
committer_name = "GIT_COMMITTER_NAME"
committer_email = "GIT_COMMITTER_EMAIL"
committer_date = "GIT_COMMITTER_DATE"
[docs]def username():
"""Retrieve the user's name
Returns:
str: user's name
"""
user = os.getenv(GitEnv.author_name.value)
if user is None:
try:
user = next(shell.git("config", "--get", "user.name"))
user = user.strip()
except ShellCommandException:
user = getpass.getuser()
return user
[docs]def email():
"""Retrieve the user's email
Returns:
str: user's email
"""
mail = os.getenv(GitEnv.author_email.value)
if mail is None:
try:
mail = next(shell.git("config", "--get", "user.email"))
mail = mail.strip()
except ShellCommandException:
user = getpass.getuser()
host = socket.gethostname()
mail = "{user}@{host}".format(user=user, host=host)
return mail
[docs]def is_git_installed():
"""Check if git is installed
Returns:
bool: True if git is installed, False otherwise
"""
if shell.git is None:
return False
try:
shell.git("--version")
except ShellCommandException:
return False
return True
[docs]def check_git():
"""Checks for git and raises appropriate exception if not
Raises:
:class:`~.GitNotInstalled`: when git command is not available
:class:`~.GitNotConfigured`: when git does not know user information
"""
if not is_git_installed():
raise GitNotInstalled
if not is_git_configured():
raise GitNotConfigured
[docs]def is_git_workspace_clean(path):
"""Checks if git workspace is clean
Args:
path (str): path to git repository
Returns:
bool: condition if workspace is clean or not
Raises:
:class:`~.GitNotInstalled`: when git command is not available
:class:`~.GitNotConfigured`: when git does not know user information
"""
# ToDo: Change to pathlib for v4
check_git()
try:
with chdir(path):
shell.git('diff-index', '--quiet', 'HEAD', '--')
except ShellCommandException:
return False
return True
[docs]def project(opts):
"""Update user options with the options of an existing PyScaffold project
Params:
opts (dict): options of the project
Returns:
dict: options with updated values
Raises:
:class:`~.PyScaffoldTooOld`: when PyScaffold is to old to update from
:class:`~.NoPyScaffoldProject`: when project was not generated with
PyScaffold
"""
from pkg_resources import iter_entry_points
opts = copy.deepcopy(opts)
cfg = read_setupcfg(opts['project']).to_dict()
if 'pyscaffold' not in cfg:
raise PyScaffoldTooOld
pyscaffold = cfg['pyscaffold']
metadata = cfg['metadata']
# This would be needed in case of inplace updates, see issue #138, v4
# if opts['project'] == '.':
# opts['project'] = metadata['name']
# Overwrite only if user has not provided corresponding cli argument
opts.setdefault('package', pyscaffold['package'])
opts.setdefault('author', metadata['author'])
opts.setdefault('email', metadata['author-email'])
opts.setdefault('url', metadata['url'])
opts.setdefault('description', metadata['description'])
opts.setdefault('license', best_fit_license(metadata['license']))
# Additional parameters compare with `get_default_options`
opts['classifiers'] = metadata['classifiers'].strip().split('\n')
# complement the cli extensions with the ones from configuration
if 'extensions' in pyscaffold:
cfg_extensions = pyscaffold['extensions'].strip().split('\n')
opt_extensions = [ext.name for ext in opts['extensions']]
add_extensions = set(cfg_extensions) - set(opt_extensions)
for extension in iter_entry_points('pyscaffold.cli'):
if extension.name in add_extensions:
extension_obj = extension.load()(extension.name)
if extension.name in pyscaffold:
ext_value = pyscaffold[extension.name]
extension_obj.args = ext_value
opts[extension.name] = ext_value
opts['extensions'].append(extension_obj)
return opts
[docs]def best_fit_license(txt):
"""Finds proper license name for the license defined in txt
Args:
txt (str): license name
Returns:
str: license name
"""
ratings = {lic: levenshtein(txt, lic.lower()) for lic in licenses}
return min(ratings.items(), key=itemgetter(1))[0]