text: # Copyright (c) 2017 SUSE Linux GmbH
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #    http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
  # implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  from __future__ import print_function
  
  import os
  
  from jinja2 import contextfilter
  from jinja2 import contextfunction
  from jinja2.exceptions import TemplateRuntimeError
  from packaging.version import parse
  import pymod2pkg
  
  from renderspec import utils
  
  
  # a variable that needs to be set for some functions in the context
  CONTEXT_VAR_PYPI_NAME = "pypi_name"
  CONTEXT_VAR_UPSTREAM_VERSION = "upstream_version"
  CONTEXT_VAR_RPM_RELEASE = "rpm_release"
  
  
  def _context_check_variable(context, var_name, needed_by):
      """check that the context has a given variable"""
      if var_name not in context.vars:
          raise TemplateRuntimeError("Variable '%s' not available in context but"
                                     " needed for '%s'" % (var_name, needed_by))
  
  
  def _context_fetch_source(context, url):
      """fetch the given url into the output_dir and return the url"""
      if context['output_dir']:
          filename = os.path.basename(url)
          utils._download_file(url, context['output_dir'], filename)
      return url
  
  
  def _context_git_source(context, uri, rev='master'):
      """clone the given ref into the output_dir and return the rev"""
      if not utils._has_git():
          raise TemplateRuntimeError("Context function 'git_source' requires"
                                     " gitpython but it could not be imported")
  
      _context_check_variable(context, CONTEXT_VAR_PYPI_NAME,
                              'pypi_name')
      prj_name = context.vars[CONTEXT_VAR_PYPI_NAME]
      out_dir = context['output_dir']
  
      if out_dir:
          src_dir = os.path.join(out_dir, prj_name)
          sha = utils._git_repo(src_dir, uri, rev)
  
          tarname = '-'.join([prj_name, sha])
          utils._create_archive(tarname, 'gztar', out_dir, prj_name)
  
          return sha
  
      return rev
  
  
  def _context_url_pypi(context):
      """return the full sdist pypi url"""
      # we need the pypi_name and the upstream_version variables to construct
      # the full url
      _context_check_variable(context, CONTEXT_VAR_PYPI_NAME,
                              'pypi_name')
  
      _context_check_variable(context, CONTEXT_VAR_UPSTREAM_VERSION,
                              'upstream_version')
      name = context.vars[CONTEXT_VAR_PYPI_NAME]
      version = context.vars[CONTEXT_VAR_UPSTREAM_VERSION]
      return 'https://files.pythonhosted.org/packages/source/' \
          '%s/%s/%s-%s.tar.gz' % (name[0], name, name, version)
  
  
  def _context_upstream_version(context, pkg_version=None):
      """return the version which should be set to the 'upstream_version'
      variable in the jinja context"""
      if pkg_version:
          return pkg_version
      else:
          # try to auto-detect the version - for that we need the pypi name
          _context_check_variable(context, CONTEXT_VAR_PYPI_NAME,
                                  'upstream_version')
          pypi_name = context.vars[CONTEXT_VAR_PYPI_NAME]
  
          # look for archives in:
          # 1) the output_dir
          # 2) the dir where the input template (.spec.j2) comes from
          # 3) the current working dir
          archives = utils._find_archives([context['output_dir'],
                                           context['input_template_dir'],
                                           '.'], pypi_name)
          for archive in archives:
              with utils._extract_archive_to_tempdir(archive) as tmpdir:
                  pkg_info_file = utils._find_pkg_info(tmpdir)
                  if pkg_info_file:
                      return utils._get_version_from_pkg_info(pkg_info_file)
          # unable to autodetect the version
          raise TemplateRuntimeError("Can not autodetect 'upstream_version' from"
                                     " the following archives: '%s'" % (
                                         ', '.join(archives)))
  
  
  def _context_py2rpmversion(context):
      """get a python PEP0440 compatible version and translate it to an RPM
      version"""
      # the context needs a variable set via {% set upstream_version = 'ver' %}
      _context_check_variable(context, CONTEXT_VAR_UPSTREAM_VERSION,
                              'py2rpmversion')
      version = context.vars[CONTEXT_VAR_UPSTREAM_VERSION]
      v_python = parse(version)
      # fedora does not allow '~' in versions but uses a combination of Version
      # and Release
      # https://fedoraproject.org/wiki/Packaging:Versioning\#Pre-Release_packages
      if context['spec_style'] == 'fedora':
          if len(v_python._version.release) >= 4:
              return "%d.%d.%d" % (v_python._version.release[0:3])
          else:
              return v_python.base_version
      else:
          v_rpm = v_python.public
          if v_python.is_prerelease:
              # we need to add the 'x' in front of alpha/beta releases because
              # in the python world, "1.1a10" > "1.1.dev10"
              # but in the rpm world, "1.1~a10" < "1.1~dev10"
              v_rpm = v_rpm.replace('a', '~xalpha')
              v_rpm = v_rpm.replace('b', '~xbeta')
              v_rpm = v_rpm.replace('rc', '~xrc')
              v_rpm = v_rpm.replace('.dev', '~dev')
          return v_rpm
  
  
  def _context_py2rpmrelease(context):
      if context['spec_style'] == 'fedora':
          # the context needs a var set via {% set upstream_version = 'ver' %}
          _context_check_variable(context, CONTEXT_VAR_UPSTREAM_VERSION,
                                  'py2rpmrelease')
          # the context needs a var set via {% set rpm_release = 'ver' %}
          _context_check_variable(context, CONTEXT_VAR_RPM_RELEASE,
                                  'py2rpmrelease')
          upstream_version = context.vars[CONTEXT_VAR_UPSTREAM_VERSION]
          rpm_release = context.vars[CONTEXT_VAR_RPM_RELEASE]
          v_python = parse(upstream_version)
          if v_python.is_prerelease:
              _, alphatag = v_python.public.split(v_python.base_version)
              return '0.{}.{}%{{?dist}}'.format(rpm_release,
                                                alphatag.lstrip('.'))
          else:
              return '{}%{{?dist}}'.format(rpm_release)
      else:
          # SUSE uses just '0'. The OpenBuildService handles the Release tag
          return '0'
  
  
  def _context_epoch(context, pkg_name):
      """get the epoch (or 0 if unknown) for the given pkg name"""
      return context['epochs'].get(pkg_name, 0)
  
  
  def _pymod2pkg_translate(pkg_name, context, py_versions):
      """translate a given package name for a single or multiple py versions"""
      if py_versions and not isinstance(py_versions, (list, tuple)):
          py_versions = [py_versions]
      kwargs = {}
      if py_versions:
          kwargs['py_vers'] = [i for i in py_versions if i not in
                               set((context['skip_pyversion'],))]
  
      translations = pymod2pkg.module2package(
          pkg_name, context['spec_style'], **kwargs)
      # we want always return a list but module2package() might return a string
      if not isinstance(translations, (list, tuple)):
          translations = [translations]
      return translations
  
  
  def _context_py2name(context, pkg_name=None, pkg_version=None,
                       py_versions=None):
      """
      context: a Jinja2 context
      pkg_name: usually the pypi-name. If None, it tries to get the name
                from the context variable called 'py2name'
      pkg_version: Deprecated and unused
      py_versions: the version pymod2pkg should return. Can be currently 'py',
                   'py2' and 'py3' or a combination of those in a list
      """
      if not pkg_name:
          # if the name is not given, try to get the name from the context
          _context_check_variable(context, CONTEXT_VAR_PYPI_NAME,
                                  'py2name')
          pkg_name = context.vars[CONTEXT_VAR_PYPI_NAME]
      # return always a string to be backwards compat
      return ' '.join(_pymod2pkg_translate(pkg_name, context, py_versions))
  
  
  def _context_py2pkg(context, pkg_name, pkg_version=None, py_versions=None):
      """generate a distro specific package name with optional version tuple."""
      name_list = _pymod2pkg_translate(pkg_name, context, py_versions)
  
      # if no pkg_version is given, look in the requirements and set one
      if not pkg_version:
          if pkg_name in context['requirements']:
              pkg_version = ('>=', context['requirements'][pkg_name])
  
      # pkg_version is a tuple with comparator and number, i.e. "('>=', '1.2.3')"
      if pkg_version:
          # epoch handling
          if pkg_name in context['epochs'].keys():
              epoch = '%s:' % context['epochs'][pkg_name]
          else:
              epoch = ''
          v_comparator, v_number = pkg_version
          v_str = ' %s %s%s' % (v_comparator, epoch, v_number)
      else:
          v_str = ''
  
      return ' '.join(['%s%s' % (name, v_str) for name in name_list])
  
  
  def _context_py2(context, pkg_name, pkg_version=None):
      return _context_py2pkg(context, pkg_name, pkg_version, py_versions=['py2'])
  
  
  def _context_py3(context, pkg_name, pkg_version=None):
      return _context_py2pkg(context, pkg_name, pkg_version, py_versions=['py3'])
  
  
  def _context_license_spdx(context, value):
      """convert a given known spdx license to another one"""
      # more values can be taken from from https://github.com/hughsie/\
      #    appstream-glib/blob/master/libappstream-builder/asb-package-rpm.c#L76
      mapping = {
          "Apache-1.1": "ASL 1.1",
          "Apache-2.0": "ASL 2.0",
          "BSD-3-Clause": "BSD",
          "GPL-1.0+": "GPL+",
          "GPL-2.0": "GPLv2",
          "GPL-2.0+": "GPLv2+",
          "GPL-3.0": "GPLv3",
          "GPL-3.0+": "GPLv3+",
          "LGPL-2.1": "LGPLv2.1",
          "LGPL-2.1+": "LGPLv2+",
          "LGPL-2.0": "LGPLv2 with exceptions",
          "LGPL-2.0+": "LGPLv2+ with exceptions",
          "LGPL-3.0": "LGPLv3",
          "LGPL-3.0+": "LGPLv3+",
          "MIT": "MIT with advertising",
          "MPL-1.0": "MPLv1.0",
          "MPL-1.1": "MPLv1.1",
          "MPL-2.0": "MPLv2.0",
          "OFL-1.1": "OFL",
          "Python-2.0": "Python",
      }
  
      if context['spec_style'] == 'fedora':
          return mapping[value]
      else:
          # just use the spdx license name
          return value
  
  
  ###############
  # jinja2 filter
  ###############
  @contextfilter
  def _filter_epoch(context, value):
      return _context_epoch(context, value)
  
  
  @contextfilter
  def _filter_basename(context, value):
      return os.path.basename(value)
  
  
  ################
  # jinja2 globals
  ################
  @contextfunction
  def _globals_py2pkg(context, pkg_name, pkg_version=None, py_versions=None):
      return _context_py2pkg(context, pkg_name, pkg_version, py_versions)
  
  
  @contextfunction
  def _globals_py2(context, pkg_name, pkg_version=None):
      return _context_py2(context, pkg_name, pkg_version)
  
  
  @contextfunction
  def _globals_py3(context, pkg_name, pkg_version=None):
      return _context_py3(context, pkg_name, pkg_version)
  
  
  @contextfunction
  def _globals_fetch_source(context, url):
      return _context_fetch_source(context, url)
  
  
  @contextfunction
  def _globals_git_source(context, uri, rev='master'):
      return _context_git_source(context, uri, rev)
  
  
  @contextfunction
  def _globals_url_pypi(context):
      return _context_url_pypi(context)
  
  
  @contextfunction
  def _globals_upstream_version(context, pkg_version=None):
      return _context_upstream_version(context, pkg_version)
  
  
  @contextfunction
  def _globals_py2rpmversion(context):
      return _context_py2rpmversion(context)
  
  
  @contextfunction
  def _globals_py2rpmrelease(context):
      return _context_py2rpmrelease(context)
  
  
  @contextfunction
  def _globals_epoch(context, value):
      return _context_epoch(context, value)
  
  
  @contextfunction
  def _globals_license_spdx(context, value):
      return _context_license_spdx(context, value)
  
  
  @contextfunction
  def _globals_py2name(context, value=None, py_versions=None):
      return _context_py2name(context, value, py_versions=py_versions)
  
  
  def env_register_filters_and_globals(env):
      """register all the jinja2 filters we want in the environment"""
      env.filters['epoch'] = _filter_epoch
      env.filters['basename'] = _filter_basename
      env.globals['py2rpmversion'] = _globals_py2rpmversion
      env.globals['py2rpmrelease'] = _globals_py2rpmrelease
      env.globals['py2pkg'] = _globals_py2pkg
      env.globals['py2'] = _globals_py2
      env.globals['py3'] = _globals_py3
      env.globals['py2name'] = _globals_py2name
      env.globals['epoch'] = _globals_epoch
      env.globals['license'] = _globals_license_spdx
      env.globals['upstream_version'] = _globals_upstream_version
      env.globals['fetch_source'] = _globals_fetch_source
      env.globals['git_source'] = _globals_git_source
      env.globals['url_pypi'] = _globals_url_pypi
  