This repository has been archived by the owner on Jan 12, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 117
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'universal-python-choice'
- Loading branch information
Showing
53 changed files
with
698 additions
and
415 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
"""Actions are functions that used only in commands | ||
""" | ||
|
||
from ._autocomplete import make_bash_autocomplete, make_zsh_autocomplete | ||
from ._converting import attach_deps | ||
from ._downloads import get_total_downloads, get_downloads_by_category | ||
from ._editorconfig import make_editorconfig | ||
from ._entrypoints import get_entrypoints | ||
from ._json import make_json | ||
from ._python import get_python, get_python_env | ||
from ._venv import get_venv | ||
|
||
|
||
__all__ = [ | ||
'attach_deps', | ||
'get_downloads_by_category', | ||
'get_entrypoints', | ||
'get_python_env', | ||
'get_python', | ||
'get_total_downloads', | ||
'get_venv', | ||
'make_bash_autocomplete', | ||
'make_editorconfig', | ||
'make_json', | ||
'make_zsh_autocomplete', | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
from collections import defaultdict | ||
|
||
from jinja2 import Environment, PackageLoader | ||
|
||
|
||
templates = Environment( | ||
loader=PackageLoader('dephell', 'templates'), | ||
) | ||
|
||
|
||
def make_bash_autocomplete() -> str: | ||
from ..commands import commands | ||
|
||
template = templates.get_template('autocomplete.sh.j2') | ||
tree = defaultdict(set) | ||
first_words = set() | ||
for command in commands: | ||
command, _sep, subcommand = command.partition(' ') | ||
first_words.add(command) | ||
if subcommand: | ||
tree[command].add(subcommand) | ||
|
||
arguments = defaultdict(set) | ||
for command_name, command in commands.items(): | ||
for action in command.get_parser()._actions: | ||
arguments[command_name].update(action.option_strings) | ||
|
||
return template.render(first_words=first_words, tree=tree, arguments=arguments) | ||
|
||
|
||
def make_zsh_autocomplete() -> str: | ||
from ..commands import commands | ||
|
||
template = templates.get_template('autocomplete-zsh.sh.j2') | ||
tree = defaultdict(set) | ||
first_words = set() | ||
for command_name, command in commands.items(): | ||
command_name, _sep, subcommand = command_name.partition(' ') | ||
first_words.add(command_name) | ||
if subcommand: | ||
description = command.get_parser().description.lstrip().split('\n', maxsplit=1)[0] | ||
tree[command_name].add((subcommand, description)) | ||
|
||
arguments = defaultdict(list) | ||
for command_name, command in commands.items(): | ||
for action in command.get_parser()._actions: | ||
if action.help: | ||
arguments[command_name].append((action.option_strings, action.help)) | ||
|
||
return template.render(first_words=first_words, tree=tree, arguments=arguments) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
from logging import getLogger | ||
|
||
from ..config import Config | ||
from ..converters import CONVERTERS | ||
|
||
|
||
logger = getLogger('dephell.actions') | ||
|
||
|
||
def attach_deps(*, resolver, config: Config, merge: bool = True) -> bool: | ||
if 'and' not in config: | ||
return True | ||
|
||
# attach | ||
for source in config['and']: | ||
logger.debug('attach dependencies...', extra=dict( | ||
format=source['format'], | ||
path=source['path'], | ||
)) | ||
loader = CONVERTERS[source['format']] | ||
root = loader.load(path=source['path']) | ||
resolver.graph.add(root) | ||
|
||
if not merge: | ||
return True | ||
|
||
# merge (without full graph building) | ||
logger.debug('merging...') | ||
resolved = resolver.resolve(level=1, silent=config['silent']) | ||
if not resolved: | ||
return False | ||
logger.debug('merged') | ||
|
||
return True |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
# built-in | ||
from collections import defaultdict | ||
from datetime import date, timedelta | ||
from itertools import zip_longest | ||
from typing import Iterable, Iterator, Dict, List | ||
|
||
import attr | ||
import requests | ||
|
||
|
||
RECENT_URL = 'https://pypistats.org/api/packages/{}/recent' | ||
CATEGORIES_URLS = dict( | ||
pythons='https://pypistats.org/api/packages/{}/python_minor', | ||
systems='https://pypistats.org/api/packages/{}/system', | ||
) | ||
|
||
|
||
@attr.s() | ||
class DateList: | ||
start = attr.ib() | ||
end = attr.ib() | ||
_data = attr.ib(factory=dict, repr=False) | ||
|
||
def add(self, date: str, value: int): | ||
self._data[date] = value | ||
|
||
def __iter__(self) -> Iterator[int]: | ||
moment = self.start | ||
while moment <= self.end: | ||
yield self._data.get(str(moment), 0) | ||
moment += timedelta(1) | ||
|
||
|
||
def make_chart(values: Iterable[int], group: int = None, ticks: str = '_▁▂▃▄▅▆▇█') -> str: | ||
peek = max(values) | ||
if peek == 0: | ||
chart = ticks[-1] * len(values) | ||
else: | ||
chart = '' | ||
for value in values: | ||
index = round((len(ticks) - 1) * value / peek) | ||
chart += ticks[int(index)] | ||
if group: | ||
chunks = map(''.join, zip_longest(*[iter(chart)] * group, fillvalue=' ')) | ||
chart = ' '.join(chunks).strip() | ||
return chart | ||
|
||
|
||
def get_total_downloads(name: str) -> Dict[str, int]: | ||
url = RECENT_URL.format(name) | ||
response = requests.get(url) | ||
response.raise_for_status() | ||
body = response.json()['data'] | ||
return dict( | ||
day=body['last_day'], | ||
week=body['last_week'], | ||
month=body['last_month'], | ||
) | ||
|
||
|
||
def get_downloads_by_category(*, category: str, name: str) -> List[Dict[str, int]]: | ||
url = CATEGORIES_URLS[category].format(name) | ||
response = requests.get(url) | ||
response.raise_for_status() | ||
body = response.json()['data'] | ||
|
||
yesterday = date.today() - timedelta(1) | ||
grouped = defaultdict(lambda: DateList(start=yesterday - timedelta(30), end=yesterday)) | ||
for line in body: | ||
category = line['category'].replace('.', '') | ||
grouped[category].add(date=line['date'], value=line['downloads']) | ||
|
||
result = [] | ||
for category, downloads in grouped.items(): | ||
downloads = list(downloads) | ||
if sum(downloads) == 0: | ||
continue | ||
result.append(dict( | ||
category=category, | ||
day=downloads[-1], | ||
week=sum(downloads[-7:]), | ||
month=sum(downloads), | ||
chart=make_chart(downloads[-28:], group=7), | ||
)) | ||
return result |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
# built-in | ||
from pathlib import Path | ||
from typing import Tuple | ||
|
||
# external | ||
import attr | ||
|
||
|
||
@attr.s(frozen=True) | ||
class Rule: | ||
header = attr.ib(type=str) | ||
patterns = attr.ib(type=Tuple[str, ...]) | ||
styles = attr.ib(type=Tuple[str, ...]) | ||
|
||
def match(self, path: Path) -> bool: | ||
for pattern in self.patterns: | ||
iterator = path.glob(pattern) | ||
try: | ||
next(iterator) | ||
except StopIteration: | ||
continue | ||
return True | ||
return False | ||
|
||
def __str__(self) -> str: | ||
return '[{header}]\n{styles}'.format( | ||
header=self.header, | ||
styles='\n'.join(self.styles), | ||
) | ||
|
||
|
||
HEADER = """ | ||
# EditorConfig helps developers define and maintain consistent | ||
# coding styles between different editors and IDEs | ||
# https://editorconfig.org | ||
root = true | ||
[*] | ||
end_of_line = lf | ||
charset = utf-8 | ||
trim_trailing_whitespace = true | ||
insert_final_newline = true | ||
""" | ||
|
||
RULES = ( | ||
# 4 spaces | ||
Rule( | ||
header='*.py', | ||
patterns=('**/*.py', ), | ||
styles=('indent_style = space', 'indent_size = 4'), | ||
), | ||
Rule( | ||
header='*.{md,rst,txt}', | ||
patterns=('*.md', '*.rst', '*.txt'), | ||
styles=('indent_style = space', 'indent_size = 4'), | ||
), | ||
Rule( | ||
header='*.{ini,toml}', | ||
patterns=('*.ini', '*.toml'), | ||
styles=('indent_style = space', 'indent_size = 4'), | ||
), | ||
Rule( | ||
header='*Dockerfile', | ||
patterns=('*.Dockerfile', 'Dockerfile'), | ||
styles=('indent_style = space', 'indent_size = 4'), | ||
), | ||
|
||
# 2 spaces | ||
Rule( | ||
header='*.js', | ||
patterns=('**/*.js', ), | ||
styles=('indent_style = space', 'indent_size = 2'), | ||
), | ||
Rule( | ||
header='*.{json,yml,yaml}', | ||
patterns=('*.json', '*.yml', '*.yaml'), | ||
styles=('indent_style = space', 'indent_size = 2'), | ||
), | ||
Rule( | ||
header='*.{html,html.j2}', | ||
patterns=('**/*.html', '**/*.html.j2'), | ||
styles=('indent_style = space', 'indent_size = 2'), | ||
), | ||
|
||
# tabs | ||
Rule( | ||
header='Makefile', | ||
patterns=('Makefile', ), | ||
styles=('indent_style = tab', ), | ||
), | ||
Rule( | ||
header='*.go', | ||
patterns=('*.go', ), | ||
styles=('indent_style = tab', ), | ||
), | ||
) | ||
|
||
|
||
def make_editorconfig(path: Path) -> str: | ||
matched = [] | ||
non_matched = [] | ||
for i, rule in enumerate(RULES): | ||
if rule.match(path): | ||
matched.append(rule) | ||
else: | ||
non_matched.append(rule) | ||
|
||
return HEADER + '\n\n'.join(map(str, matched)) + '\n' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
from logging import getLogger | ||
from typing import Tuple | ||
|
||
from ..converters import EggInfoConverter | ||
from ..models import EntryPoint | ||
from ..venvs import VEnv | ||
|
||
|
||
logger = getLogger('dephell.actions') | ||
|
||
|
||
def get_entrypoints(*, venv: VEnv, name: str) -> Tuple[EntryPoint, ...]: | ||
if not venv.lib_path: | ||
logger.critical('cannot locate lib path in the venv') | ||
return False | ||
paths = list(venv.lib_path.glob('{}*.*-info'.format(name))) | ||
if not paths: | ||
paths = list(venv.lib_path.glob('{}*.*-info'.format(name.replace('-', '_')))) | ||
if not paths: | ||
logger.critical('cannot locate dist-info for installed package') | ||
return False | ||
path = paths[0] / 'entry_points.txt' | ||
if not path.exists(): | ||
logger.error('cannot find any entrypoints for package') | ||
return False | ||
return EggInfoConverter().parse_entrypoints(content=path.read_text()).entrypoints |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.