Skip to content

Commit a1fb337

Browse files
authored
Merge pull request #93 from sdispater/revamp-1.0
Initial work towards 1.0
2 parents 2257521 + 3cc27e3 commit a1fb337

File tree

134 files changed

+10963
-1660
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

134 files changed

+10963
-1660
lines changed

.coveragerc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
[report]
22
omit =
33
cleo/_compat.py
4+
cleo/_utils.py

.flake8

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[flake8]
2+
max-line-length = 88
3+
ignore = E501, E203, W503
4+
per-file-ignores =
5+
__init__.py:F401
6+
exclude =
7+
.git
8+
__pycache__
9+
setup.py
10+
build
11+
dist
12+
releases
13+
.venv
14+
.tox
15+
.mypy_cache
16+
.pytest_cache
17+
.vscode
18+
.github

.github/workflows/tests.yml

Lines changed: 51 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -16,114 +16,62 @@ jobs:
1616
run: |
1717
pip install pre-commit
1818
pre-commit run --all-files
19-
Linux:
19+
20+
Tests:
2021
needs: Linting
21-
runs-on: ubuntu-latest
22+
name: ${{ matrix.os }} / ${{ matrix.python-version }}
23+
runs-on: ${{ matrix.os }}-latest
2224
strategy:
2325
matrix:
24-
python-version: [2.7, 3.5, 3.6, 3.7, 3.8, pypy2, pypy3]
26+
os: [Ubuntu, MacOS, Windows]
27+
python-version: [3.6, 3.7, 3.8, 3.9]
2528

2629
steps:
27-
- uses: actions/checkout@v2
28-
- name: Set up Python ${{ matrix.python-version }}
29-
uses: actions/setup-python@v1
30-
with:
31-
python-version: ${{ matrix.python-version }}
32-
- name: Get full python version
33-
id: full-python-version
34-
run: |
35-
echo ::set-output name=version::$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))")
36-
- name: Install and set up Poetry
37-
run: |
38-
curl -fsS -o get-poetry.py https://raw.githubusercontent.com/sdispater/poetry/master/get-poetry.py
39-
python get-poetry.py --preview -y
40-
source $HOME/.poetry/env
41-
poetry config virtualenvs.in-project true
42-
- name: Set up cache
43-
uses: actions/cache@v1
44-
with:
45-
path: .venv
46-
key: venv-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('**/poetry.lock') }}
47-
- name: Install dependencies
48-
run: |
49-
source $HOME/.poetry/env
50-
poetry install
51-
- name: Test
52-
run: |
53-
source $HOME/.poetry/env
54-
poetry run pytest -q tests
55-
poetry install
30+
- uses: actions/checkout@v2
5631

57-
MacOS:
58-
needs: Linting
59-
runs-on: macos-latest
60-
strategy:
61-
matrix:
62-
python-version: [2.7, 3.5, 3.6, 3.7, 3.8, pypy2, pypy3]
32+
- name: Set up Python ${{ matrix.python-version }}
33+
uses: actions/setup-python@v1
34+
with:
35+
python-version: ${{ matrix.python-version }}
6336

64-
steps:
65-
- uses: actions/checkout@v2
66-
- name: Set up Python ${{ matrix.python-version }}
67-
uses: actions/setup-python@v1
68-
with:
69-
python-version: ${{ matrix.python-version }}
70-
- name: Get full python version
71-
id: full-python-version
72-
run: |
73-
echo ::set-output name=version::$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))")
74-
- name: Install and set up Poetry
75-
run: |
76-
curl -fsS -o get-poetry.py https://raw.githubusercontent.com/sdispater/poetry/master/get-poetry.py
77-
python get-poetry.py --preview -y
78-
source $HOME/.poetry/env
79-
poetry config virtualenvs.in-project true
80-
- name: Set up cache
81-
uses: actions/cache@v1
82-
with:
83-
path: .venv
84-
key: venv-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('**/poetry.lock') }}
85-
- name: Install dependencies
86-
run: |
87-
source $HOME/.poetry/env
88-
poetry install
89-
- name: Test
90-
run: |
91-
source $HOME/.poetry/env
92-
poetry run pytest -q tests
93-
Windows:
94-
needs: Linting
95-
runs-on: windows-latest
96-
strategy:
97-
matrix:
98-
python-version: [2.7, 3.5, 3.6, 3.7, 3.8]
37+
- name: Get full python version
38+
id: full-python-version
39+
shell: bash
40+
run: |
41+
echo ::set-output name=version::$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))")
42+
43+
- name: Install Poetry
44+
shell: bash
45+
run: |
46+
curl -fsS -o get-poetry.py https://raw.githubusercontent.com/sdispater/poetry/master/get-poetry.py
47+
python get-poetry.py --preview -y
48+
echo "$HOME/.poetry/bin" >> $GITHUB_PATH
49+
echo "%USERPROFILE%/.poetry/bin" >> $GITHUB_PATH
9950
100-
steps:
101-
- uses: actions/checkout@v2
102-
- name: Set up Python ${{ matrix.python-version }}
103-
uses: actions/setup-python@v1
104-
with:
105-
python-version: ${{ matrix.python-version }}
106-
- name: Get full python version
107-
id: full-python-version
108-
shell: bash
109-
run: |
110-
echo ::set-output name=version::$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))")
111-
- name: Install and setup Poetry
112-
run: |
113-
Invoke-WebRequest https://raw.githubusercontent.com/sdispater/poetry/master/get-poetry.py -O get-poetry.py
114-
python get-poetry.py --preview -y
115-
$env:Path += ";$env:Userprofile\.poetry\bin"
116-
poetry config virtualenvs.in-project true
117-
- name: Set up cache
118-
uses: actions/cache@v1
119-
with:
120-
path: .venv
121-
key: venv-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('**/poetry.lock') }}
122-
- name: Install dependencies
123-
run: |
124-
$env:Path += ";$env:Userprofile\.poetry\bin"
125-
poetry install
126-
- name: Test
127-
run: |
128-
$env:Path += ";$env:Userprofile\.poetry\bin"
129-
poetry run pytest -q tests
51+
- name: Setup Poetry
52+
shell: bash
53+
run: |
54+
poetry config virtualenvs.in-project true
55+
56+
- name: Set up cache
57+
uses: actions/cache@v2
58+
id: cache
59+
with:
60+
path: .venv
61+
key: venv-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('**/poetry.lock') }}
62+
63+
- name: Ensure cache is healthy
64+
if: steps.cache.outputs.cache-hit == 'true'
65+
shell: bash
66+
run: timeout 10s poetry run pip --version || rm -rf .venv
67+
68+
- name: Install dependencies
69+
shell: bash
70+
run: |
71+
poetry install
72+
73+
- name: Run tests
74+
shell: bash
75+
run: |
76+
poetry run pytest -q tests
77+
poetry install

.pre-commit-config.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
repos:
22
- repo: https://github.com/ambv/black
3-
rev: stable
3+
rev: 20.8b1
44
hooks:
55
- id: black
6-
python_version: python3.6

cleo/__init__.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +0,0 @@
1-
# -*- coding: utf-8 -*-
2-
3-
from .application import Application
4-
from .commands import Command
5-
from .helpers import argument, option
6-
from .testers import ApplicationTester, CommandTester
7-
8-
9-
__version__ = "0.8.1"

cleo/_compat.py

Lines changed: 7 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,15 @@
11
# -*- coding: utf-8 -*-
22

3+
import shlex
4+
import subprocess
35
import sys
46

5-
PY2 = sys.version_info[0] == 2
67

7-
if PY2:
8-
long = long
9-
unicode = unicode
10-
basestring = basestring
11-
else:
12-
long = int
13-
unicode = str
14-
basestring = str
8+
WINDOWS = sys.platform == "win32"
159

1610

17-
def decode(string, encodings=None):
18-
if not PY2 and not isinstance(string, bytes):
19-
return string
11+
def shell_quote(token: str) -> str:
12+
if WINDOWS:
13+
return subprocess.list2cmdline([token])
2014

21-
if PY2 and isinstance(string, unicode):
22-
return string
23-
24-
if encodings is None:
25-
encodings = ["utf-8", "latin1", "ascii"]
26-
27-
for encoding in encodings:
28-
try:
29-
return string.decode(encoding)
30-
except (UnicodeEncodeError, UnicodeDecodeError):
31-
pass
32-
33-
return string.decode(encodings[0], errors="ignore")
34-
35-
36-
def encode(string, encodings=None):
37-
if not PY2 and isinstance(string, bytes):
38-
return string
39-
40-
if PY2 and isinstance(string, str):
41-
return string
42-
43-
if encodings is None:
44-
encodings = ["utf-8", "latin1", "ascii"]
45-
46-
for encoding in encodings:
47-
try:
48-
return string.encode(encoding)
49-
except (UnicodeEncodeError, UnicodeDecodeError):
50-
pass
51-
52-
return string.encode(encodings[0], errors="ignore")
15+
return shlex.quote(token)

cleo/_utils.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import math
2+
3+
from html.parser import HTMLParser
4+
from typing import List
5+
6+
from pylev import levenshtein
7+
8+
9+
class TagStripper(HTMLParser):
10+
def __init__(self) -> None:
11+
super().__init__(convert_charrefs=False)
12+
13+
self.reset()
14+
self.fed = []
15+
16+
def handle_data(self, d) -> None:
17+
self.fed.append(d)
18+
19+
def handle_entityref(self, name) -> None:
20+
self.fed.append("&%s;" % name)
21+
22+
def handle_charref(self, name) -> None:
23+
self.fed.append("&#%s;" % name)
24+
25+
def get_data(self) -> str:
26+
return "".join(self.fed)
27+
28+
29+
def _strip(value) -> str:
30+
s = TagStripper()
31+
s.feed(value)
32+
s.close()
33+
34+
return s.get_data()
35+
36+
37+
def strip_tags(value):
38+
value = str(value)
39+
while "<" in value and ">" in value:
40+
new_value = _strip(value)
41+
if value.count("<") == new_value.count("<"):
42+
break
43+
44+
value = new_value
45+
46+
return value
47+
48+
49+
def find_similar_names(name, names): # type: (str, List[str]) -> List[str]
50+
"""
51+
Finds names similar to a given command name.
52+
"""
53+
threshold = 1e3
54+
distance_by_name = {}
55+
suggested_names = []
56+
57+
for actual_name in names:
58+
# Get Levenshtein distance between the input and each command name
59+
distance = levenshtein(name, actual_name)
60+
61+
is_similar = distance <= len(name) / 3
62+
is_sub_string = actual_name.find(name) != -1
63+
64+
if is_similar or is_sub_string:
65+
distance_by_name[actual_name] = (
66+
distance,
67+
actual_name.find(name) if is_sub_string else float("inf"),
68+
)
69+
70+
# Only keep results with a distance below the threshold
71+
distance_by_name = {
72+
k: v for k, v in distance_by_name.items() if v[0] < 2 * threshold
73+
}
74+
75+
# Display results with shortest distance first
76+
for k, v in sorted(distance_by_name.items(), key=lambda i: (i[1][0], i[1][1])):
77+
if k not in suggested_names:
78+
suggested_names.append(k)
79+
80+
return suggested_names
81+
82+
83+
_TIME_FORMATS = [
84+
(0, "< 1 sec"),
85+
(2, "1 sec"),
86+
(59, "secs", 1),
87+
(60, "1 min"),
88+
(3600, "mins", 60),
89+
(5400, "1 hr"),
90+
(86400, "hrs", 3600),
91+
(129600, "1 day"),
92+
(604800, "days", 86400),
93+
]
94+
95+
96+
def format_time(secs): # type: (float) -> str
97+
for fmt in _TIME_FORMATS:
98+
if secs > fmt[0]:
99+
continue
100+
101+
if len(fmt) == 2:
102+
return fmt[1]
103+
104+
return "{} {}".format(math.ceil(secs / fmt[2]), fmt[1])

0 commit comments

Comments
 (0)