Browse Source

move remaining used code from "shore" module to "shut" module

shut-new-model
Niklas Rosenstein 9 months ago
parent
commit
91285adb79
No known key found for this signature in database GPG Key ID: 6D269B33D25F6C6
14 changed files with 236 additions and 190 deletions
  1. + 0
    - 157
      src/shore/util/version.py
  2. + 4
    - 4
      src/shut/checks/package.py
  3. + 1
    - 2
      src/shut/commands/changelog/__init__.py
  4. + 2
    - 2
      src/shut/commands/classifiers.py
  5. + 1
    - 2
      src/shut/commands/commons/new.py
  6. + 4
    - 3
      src/shut/commands/license.py
  7. + 9
    - 9
      src/shut/commands/pkg/new.py
  8. + 2
    - 1
      src/shut/model/package.py
  9. + 139
    - 1
      src/shut/model/version.py
  10. + 6
    - 2
      src/shut/utils/ast.py
  11. + 49
    - 0
      src/shut/utils/cli.py
  12. + 1
    - 0
      src/shut/utils/external/__init__.py
  13. + 12
    - 5
      src/shut/utils/external/classifiers.py
  14. + 6
    - 2
      src/shut/utils/external/license.py

+ 0
- 157
src/shore/util/version.py

@ -1,157 +0,0 @@
# -*- coding: utf8 -*-
# Copyright (c) 2020 Niklas Rosenstein
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
from packaging.version import Version as _Version
from nr.utils.git import Git
from typing import Optional, Union
import logging
import re
logger = logging.getLogger(__name__)
class Version(_Version):
""" An extension of #packageing.version.Version which supports a
commit-distance and commit SHA suffix in the format of `-X-gY` (where
X is the distance and Y is the lowercase 7-character SHA sum). """
def __init__(self, s: Union['Version', str]):
if isinstance(s, Version):
s = str(s)
elif not isinstance(s, str):
raise TypeError('expected Version or str, got {}'.format(type(s).__name__))
match = re.match(r'(.*)-(\d+)-g([0-9a-f]{7})', s)
if match:
s = match.group(1)
commit_distance = int(match.group(2))
sha = match.group(3)
else:
commit_distance = None
sha = None
super().__init__(s)
self.commit_distance = commit_distance
self.sha = sha
def __str__(self):
s = super().__str__()
if self.commit_distance and self.sha:
s += '-{}-g{}'.format(self.commit_distance, self.sha)
return s
def __lt__(self, other):
if super().__lt__(other):
return True
if super().__eq__(other):
return (self.commit_distance or 0) < (other.commit_distance or 0)
return False
def __gt__(self, other):
return other < self and other != self
def __eq__(self, other):
if super().__eq__(other) is True:
return (self.commit_distance, self.sha) == (other.commit_distance, other.sha)
return False
def __ne__(self, other):
return not (self == other)
@property
def pep440_compliant(self):
return self.sha is None
def parse_version(version_string: str) -> Version:
return Version(version_string)
def bump_version(version: Version, kind: str) -> Version:
major, minor, patch, post = version.major, version.minor, version.micro, \
version.post
if kind == 'post':
if post is None:
post = 1
else:
post += 1
elif kind == 'patch':
post = None
patch += 1
elif kind == 'minor':
post = None
patch = 0
minor += 1
elif kind == 'major':
post = None
patch = minor = 0
major += 1
else:
raise ValueError('invalid kind: {!r}'.format(kind))
string = '%s.%s.%s' % (major, minor, patch)
if post:
string += '.post' + str(post)
return Version(string)
def get_commit_distance_version(repo_dir: str, version: Version, latest_tag: str) -> Optional[Version]:
"""
This function creates a string which describes the version of the
monorepo or package that includes the commit distance and SHA revision
number.
For a mono repository, the full commit distance is used. The same is true
for a single package. For a package inside a mono repository that does not
apply mono versioning, the packages' local commit distance is used.
This is close to what `git describe --tags` does. An example version number
generated by this function is: `0.1.0+24.gd9ade3f`. If the working state is
dirty, `.dirty` will be appended to the local version.
Notes:
- If there is no commit distance from the *latest_tag* to the current
state of the repository, this function returns None.
- The version returned by this function is a PEP440 local version that
cannot be used for packages when submitting them to PyPI.
- If the tag for the version of *subject* does not exist on the repository,
it will fall back to 0.0.0 as the version number which is treated as
"the beginning of the repository", even if no tag for this version exists.
Todo: We could try to find the previous tag for this subject and use that.
"""
git = Git(repo_dir)
dirty = git.has_diff()
if git.rev_parse(latest_tag):
distance = len(git.rev_list(latest_tag + '..HEAD'))
else:
logger.warning('tag "%s" does not exist', latest_tag)
version = Version('0.0.0')
distance = len(git.rev_list('HEAD'))
if distance == 0:
if dirty:
return parse_version(str(version) + '+dirty')
return None
rev = git.rev_parse('HEAD')
local = '+{}.g{}{}'.format(distance, rev[:7], '.dirty' if dirty else '')
return parse_version(str(version) + local)

+ 4
- 4
src/shut/checks/package.py

@ -19,13 +19,13 @@
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
from shore.util.classifiers import get_classifiers
import os
from typing import Iterable, Optional
from .core import CheckResult, CheckStatus, Checker, check, register_checker
from shut.model import MonorepoModel, PackageModel, Project
from typing import Iterable, Optional
from shut.utils.external.classifiers import get_classifiers
from .core import CheckResult, CheckStatus, Checker, check, register_checker
import os
class PackageChecker(Checker[PackageModel]):

+ 1
- 2
src/shut/commands/changelog/__init__.py

@ -29,7 +29,6 @@ from termcolor import colored
import click
import yaml
from shore.__main__ import _edit_text, _editor_open
from .. import shut, commons, project
from shut.changelog import v3
from shut.changelog.manager import ChangelogManager
@ -37,7 +36,7 @@ from shut.changelog.render import render as render_changelogs
from shut.model import registry
from shut.model.version import parse_version
from shut.model.package import PackageModel
from shut.utils.cli import editor_open, edit_text
_git = Git()

+ 2
- 2
src/shut/commands/classifiers.py

@ -23,9 +23,9 @@
List or search package classifiers on PyPI.
"""
from . import shut
from shore.util.classifiers import get_classifiers
import click
from shut.utils.external.classifiers import get_classifiers
from . import shut
@shut.group(help=__doc__)

+ 1
- 2
src/shut/commands/commons/new.py

@ -19,13 +19,12 @@
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
from shore.util.license import get_license_metadata, wrap_license_text # TODO
import subprocess
from typing import Optional
import jinja2
from shut.model.author import Author
from shut.utils.external.license import get_license_metadata, wrap_license_text
from shut.utils.io.virtual import VirtualFiles
from termcolor import colored

+ 4
- 3
src/shut/commands/license.py

@ -23,11 +23,12 @@
Get license information from DejaCode.
"""
from . import shut
from shore.util.license import get_license_metadata, wrap_license_text
import click
import json
import click
from shut.utils.external.license import get_license_metadata, wrap_license_text
from . import shut
@shut.group(help=__doc__)
def license():

+ 9
- 9
src/shut/commands/pkg/new.py

@ -19,7 +19,14 @@
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
from shore.util.license import get_license_metadata, wrap_license_text
import datetime
import os
import subprocess
from typing import Optional
import click
import jinja2
from termcolor import colored
from shut.commands.commons.new import (
load_author_from_git,
@ -33,16 +40,9 @@ from shut.model.author import Author
from shut.model.package import PackageModel, PackageData
from shut.model.requirements import Requirement, VersionSelector
from shut.model.version import Version
from shut.utils.external.license import get_license_metadata, wrap_license_text
from shut.utils.io.virtual import VirtualFiles
from . import pkg
from termcolor import colored
from typing import Optional
import click
import datetime
import jinja2
import os
import subprocess
INIT_TEMPLATE = '''
__author__ = '{{author or "Me <me@me.org>"}}'

+ 2
- 1
src/shut/model/package.py

@ -22,9 +22,10 @@
import ast
import os
from typing import Dict, Iterable, List, Optional
from databind.core import datamodel, field
from shore.util.ast import load_module_members
from shut.utils.ast import load_module_members
from .abstract import AbstractProjectModel
from .author import Author
from .changelog import ChangelogConfiguration

+ 139
- 1
src/shut/model/version.py

@ -19,8 +19,146 @@
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
import logging
import re
from typing import Optional, Union
from databind.core import Context, Converter
from shore.util.version import parse_version, Version
from nr.utils.git import Git
from packaging.version import Version as _Version
logger = logging.getLogger(__name__)
class Version(_Version):
""" An extension of #packageing.version.Version which supports a
commit-distance and commit SHA suffix in the format of `-X-gY` (where
X is the distance and Y is the lowercase 7-character SHA sum). """
def __init__(self, s: Union['Version', str]):
if isinstance(s, Version):
s = str(s)
elif not isinstance(s, str):
raise TypeError('expected Version or str, got {}'.format(type(s).__name__))
match = re.match(r'(.*)-(\d+)-g([0-9a-f]{7})', s)
if match:
s = match.group(1)
commit_distance = int(match.group(2))
sha = match.group(3)
else:
commit_distance = None
sha = None
super().__init__(s)
self.commit_distance = commit_distance
self.sha = sha
def __str__(self):
s = super().__str__()
if self.commit_distance and self.sha:
s += '-{}-g{}'.format(self.commit_distance, self.sha)
return s
def __lt__(self, other):
if super().__lt__(other):
return True
if super().__eq__(other):
return (self.commit_distance or 0) < (other.commit_distance or 0)
return False
def __gt__(self, other):
return other < self and other != self
def __eq__(self, other):
if super().__eq__(other) is True:
return (self.commit_distance, self.sha) == (other.commit_distance, other.sha)
return False
def __ne__(self, other):
return not (self == other)
@property
def pep440_compliant(self):
return self.sha is None
def parse_version(version_string: str) -> Version:
return Version(version_string)
def bump_version(version: Version, kind: str) -> Version:
major, minor, patch, post = version.major, version.minor, version.micro, \
version.post
if kind == 'post':
if post is None:
post = 1
else:
post += 1
elif kind == 'patch':
post = None
patch += 1
elif kind == 'minor':
post = None
patch = 0
minor += 1
elif kind == 'major':
post = None
patch = minor = 0
major += 1
else:
raise ValueError('invalid kind: {!r}'.format(kind))
string = '%s.%s.%s' % (major, minor, patch)
if post:
string += '.post' + str(post)
return Version(string)
def get_commit_distance_version(repo_dir: str, version: Version, latest_tag: str) -> Optional[Version]:
"""
This function creates a string which describes the version of the
monorepo or package that includes the commit distance and SHA revision
number.
For a mono repository, the full commit distance is used. The same is true
for a single package. For a package inside a mono repository that does not
apply mono versioning, the packages' local commit distance is used.
This is close to what `git describe --tags` does. An example version number
generated by this function is: `0.1.0+24.gd9ade3f`. If the working state is
dirty, `.dirty` will be appended to the local version.
Notes:
- If there is no commit distance from the *latest_tag* to the current
state of the repository, this function returns None.
- The version returned by this function is a PEP440 local version that
cannot be used for packages when submitting them to PyPI.
- If the tag for the version of *subject* does not exist on the repository,
it will fall back to 0.0.0 as the version number which is treated as
"the beginning of the repository", even if no tag for this version exists.
Todo: We could try to find the previous tag for this subject and use that.
"""
git = Git(repo_dir)
dirty = git.has_diff()
if git.rev_parse(latest_tag):
distance = len(git.rev_list(latest_tag + '..HEAD'))
else:
logger.warning('tag "%s" does not exist', latest_tag)
version = Version('0.0.0')
distance = len(git.rev_list('HEAD'))
if distance == 0:
if dirty:
return parse_version(str(version) + '+dirty')
return None
rev = git.rev_parse('HEAD')
local = '+{}.g{}{}'.format(distance, rev[:7], '.dirty' if dirty else '')
return parse_version(str(version) + local)
from . import registry

src/shore/util/ast.py → src/shut/utils/ast.py

@ -1,5 +1,5 @@
# -*- coding: utf8 -*-
# Copyright (c) 2019 Niklas Rosenstein
# Copyright (c) 2020 Niklas Rosenstein
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
@ -23,14 +23,18 @@ import ast
def load_module_members(filename):
""" Loads members from *filename* as ast nodes. """
"""
Loads members from *filename* as ast nodes.
"""
with open(filename) as fp:
module = ast.parse(fp.read(), filename)
members = {}
for node in module.body:
if isinstance(node, ast.Assign):
for target in node.targets:
if isinstance(target, ast.Name):
members[target.id] = node.value
return members

+ 49
- 0
src/shut/utils/cli.py

@ -0,0 +1,49 @@
# -*- coding: utf8 -*-
# Copyright (c) 2020 Niklas Rosenstein
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
import os
import shlex
import subprocess
import sys
import nr.fs
__all__ = ['editor_open', 'edit_text']
def editor_open(filename: str):
editor = shlex.split(os.getenv('EDITOR', 'vim'))
return subprocess.call(editor + [filename])
def edit_text(text: str) -> str:
"""
Opens an editor for the user to modify *text*.
"""
with nr.fs.tempfile('.yml', dir=os.getcwd(), text=True) as fp:
fp.write(text)
fp.close()
res = _editor_open(fp.name)
if res != 0:
sys.exit(res)
with open(fp.name) as src:
return src.read()

+ 1
- 0
src/shut/utils/external/__init__.py

@ -0,0 +1 @@
pass

src/shore/util/classifiers.py → src/shut/utils/external/classifiers.py

@ -23,19 +23,26 @@ from datetime import datetime
from typing import List
import logging
import os
import requests
import time
CACHE_FILENAME = os.path.expanduser('~/.local/shore/classifiers-cache.txt')
import requests
CACHE_FILENAME = os.path.expanduser('~/.local/shut/classifiers-cache.txt')
CACHE_TTL = 60 * 60 * 24 * 7 # 7 days
CLASSIFIERS_URL = 'https://pypi.org/pypi?%3Aaction=list_classifiers'
logger = logging.getLogger(__name__)
_runtime_cache = None
def get_classifiers() -> List[str]:
def get_classifiers(force_refresh: bool = False) -> List[str]:
"""
Loads the classifiers list from PyPI. Once loaded, the classifiers are cached on disk and
in memory. The cache on disk is valid for a maxmium of seven days. Specify the *force_refresh*
argument to ignore any caches.
"""
global _runtime_cache
if _runtime_cache is not None:
if not force_refresh and _runtime_cache is not None:
return list(_runtime_cache)
def _load_cachefile():
@ -44,7 +51,7 @@ def get_classifiers() -> List[str]:
_runtime_cache = [x.rstrip('\n') for x in fp]
return list(_runtime_cache)
has_cachefile = os.path.isfile(CACHE_FILENAME)
has_cachefile = not force_frefresh and os.path.isfile(CACHE_FILENAME)
if has_cachefile and (time.time() - os.path.getmtime(CACHE_FILENAME)) < CACHE_TTL:
return _load_cachefile()
try:

src/shore/util/license.py → src/shut/utils/external/license.py

@ -1,5 +1,5 @@
# -*- coding: utf8 -*-
# Copyright (c) 2019 Niklas Rosenstein
# Copyright (c) 2020 Niklas Rosenstein
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
@ -19,7 +19,11 @@
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
""" A tiny client for the DejaCode license library. """
"""
A tiny client for the [DejaCode license library][1].
[1]: https://enterprise.dejacode.com/licenses/
"""
import bs4
import re

Loading…
Cancel
Save