├── .gitignore ├── black-candidates.sh ├── pyproject.toml ├── tests ├── change_tests.py └── test_git_black.py ├── LICENSE ├── README.md ├── src └── git_black │ └── __init__.py └── poetry.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .venv 2 | .vscode 3 | -------------------------------------------------------------------------------- /black-candidates.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # check all directories that contain '*.py' files 4 | find alaya/ tests/ -type f -name '*.py' | xargs -n1 dirname | sort -u | while read dir 5 | do 6 | black --check -q "$dir" && echo "already good: $dir" 7 | done 8 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "git-black" 3 | version = "0.4.0" 4 | description = "" 5 | authors = ["Andy Teijelo "] 6 | 7 | [tool.poetry.dependencies] 8 | python = "^3.8" 9 | click = "^7.1.2" 10 | pygit2 = "^1.2.1" 11 | 12 | [tool.poetry.dev-dependencies] 13 | flake8 = ">=3.8.2,<8" 14 | jinja2 = "^2.11.2" 15 | black = ">=19.10b0,<24" 16 | pytest = ">=5.4.3,<8" 17 | 18 | [tool.poetry.scripts] 19 | "git-black" = "git_black:cli" 20 | 21 | [build-system] 22 | requires = ["poetry>=0.12"] 23 | build-backend = "poetry.masonry.api" 24 | -------------------------------------------------------------------------------- /tests/change_tests.py: -------------------------------------------------------------------------------- 1 | from collections import ( 2 | namedtuple 3 | ) 4 | 5 | 6 | def func1( 7 | a, 8 | b): 9 | pass 10 | 11 | def func2(): 12 | return [ 13 | 'one', 14 | 'two', 15 | 'three', 16 | ] 17 | 18 | def func3(): 19 | return 3 20 | def func4(): 21 | return 4 22 | 23 | @property 24 | def some_long_name(self): 25 | if not self.condition: 26 | return None 27 | return self.some_long_value or \ 28 | (self.object1.property1.property2 29 | if self.object1 and self.object1.property1 else None) 30 | 31 | 32 | 33 | 34 | def func5(): 35 | pass 36 | 37 | 38 | 39 | 40 | def func6(): 41 | pass 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 AlayaCare 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Git Black 2 | 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 4 | 5 | Reformat your source code without losing git's history 6 | 7 | ### Purpose 8 | 9 | Git Black is a way of solving the problem of running an automated source code formatter, 10 | like [Black][black] without overriding the authorship of a large portion of the code. 11 | 12 | The goal is to adopt a consistent code style, but keeping `git blame` useful. 13 | 14 | ### Installation 15 | 16 | In a Python virtualenv: 17 | 18 | ```bash 19 | $ export PIP_EXTRA_INDEX_URL= 20 | $ pip install -U pip # pygit2 has a problem with pip <= 18.1 21 | $ pip install git-black 22 | ``` 23 | 24 | ### Usage 25 | 26 | Make sure your repository staging area is empty. Then do: 27 | 28 | ```bash 29 | $ black my_code # or prettier, or yapf, or gofmt, git-black doesn't care 30 | $ git-black 31 | ``` 32 | 33 | ### How does it work 34 | 35 | Although Git Black was written with the express intention of running Black over a 36 | relatively large code base (and the name reflects it), Git Black doesn't execute 37 | or depend on Black in any way. 38 | 39 | Git Black does this: 40 | 41 | - looks at all uncommitted changes in the repository and keeps track of the 42 | author of each line 43 | - groups all lines from all files that belong to the same original commit and 44 | creates _a new commit_ with the same author and date as the original 45 | - the new commit's committer is the default committer; git-black doesn't try 46 | to hide itself 47 | - adds the IDs of the original commits after the original message. 48 | 49 | 50 | So if you have a blame like this: 51 | 52 | ```python 53 | Adam 2015-04-14 ... def some_func(self, arg): 54 | Adam 2015-04-14 ... assert SomeClass.__name__ in obj.clients, \ 55 | Eve 2018-11-05 ... '{} is adding itself to {} clients.' \ 56 | Eve 2018-11-05 ... .format(self.__class__.__name__, SomeClass.__name__) 57 | John 2016-11-16 ... obj.property = self.property 58 | Eve 2016-12-15 ... obj.long_property_name = self.long_property_name 59 | Adam 2015-04-14 ... obj.clients[ 60 | Adam 2015-04-14 ... self.__class__.__name__ 61 | Adam 2015-04-14 ... ] = self 62 | Adam 2015-04-14 ... 63 | Pete 2015-05-01 ... obj.prop_dic.setdefault(self.SOME_LONG_NAME_CONST, 64 | Pete 2015-05-01 ... SOME_LONG_NAME_CONST_DEFAULT) 65 | ``` 66 | 67 | After running `black` on that code, and then executing `git-black`, the same blame 68 | would look like this: 69 | 70 | ```python 71 | Adam 2015-04-14 ... def some_func(self, arg): 72 | Adam 2015-04-14 ... assert ( 73 | Eve 2018-11-05 ... SomeClass.__name__ in obj.clients 74 | Eve 2018-11-05 ... ), "{} is adding itself to {} clients.".format( 75 | Eve 2018-11-05 ... self.__class__.__name__, SomeClass.__name__ 76 | Eve 2018-11-05 ... ) 77 | John 2016-11-16 ... obj.property = self.property 78 | Eve 2016-12-15 ... obj.long_property_name = self.long_property_name 79 | Adam 2015-04-14 ... obj.clients[self.__class__.__name__] = self 80 | Adam 2015-04-14 ... 81 | Pete 2015-05-01 ... obj.prop_dic.setdefault( 82 | Pete 2015-05-01 ... self.SOME_LONG_NAME_CONST, SOME_LONG_NAME_CONST_DEFAULT 83 | Pete 2015-05-01 ... ) 84 | ``` 85 | -------------------------------------------------------------------------------- /tests/test_git_black.py: -------------------------------------------------------------------------------- 1 | import os 2 | from subprocess import PIPE, Popen, run 3 | from textwrap import dedent 4 | 5 | import py 6 | import pytest 7 | 8 | from git_black import Delta, GitBlack 9 | 10 | 11 | @pytest.fixture 12 | def in_tmpdir(tmpdir): 13 | with tmpdir.as_cwd(): 14 | yield tmpdir 15 | 16 | 17 | @pytest.fixture 18 | def tmp_repo(tmpdir): 19 | with tmpdir.as_cwd(): 20 | run(["git", "init", "--quiet"]) 21 | yield tmpdir 22 | 23 | 24 | @pytest.fixture 25 | def unblacked_file(tmp_repo): 26 | f = tmp_repo.join("unblacked_file.py") 27 | f.write( 28 | dedent( 29 | """ 30 | from collections import ( 31 | namedtuple 32 | ) 33 | 34 | 35 | def func1( 36 | a, 37 | b): 38 | pass 39 | 40 | def func2(): 41 | return [ 42 | 'one', 43 | 'two', 44 | 'three', 45 | ] 46 | 47 | def func3(): 48 | return 3 49 | def func4(): 50 | return 4 51 | 52 | @property 53 | def some_long_name(self): 54 | if not self.condition: 55 | return None 56 | return self.some_long_value or \ 57 | (self.object1.property1.property2 58 | if self.object1 and self.object1.property1 else None) 59 | 60 | 61 | 62 | 63 | def func5(): 64 | pass 65 | 66 | 67 | 68 | 69 | def func6(): 70 | pass 71 | """ 72 | ).encode() 73 | ) 74 | return f 75 | 76 | 77 | def git_add(path): 78 | run(["git", "add", str(path)]) 79 | 80 | 81 | def git_commit(msg): 82 | run(["git", "commit", "-m", msg]) 83 | 84 | 85 | def git_log(): 86 | return [ 87 | line.decode().strip() 88 | for line in Popen(["git", "log", r"--format=format:%s"], stdout=PIPE).stdout 89 | ] 90 | 91 | 92 | def git_black(): 93 | gb = GitBlack() 94 | gb.commit_changes() 95 | 96 | 97 | def test_git_black(tmp_repo, unblacked_file): 98 | 99 | git_add(unblacked_file) 100 | git_commit("testing git-black") 101 | 102 | gb = GitBlack() 103 | gb.commit_changes() 104 | 105 | log = git_log() 106 | assert log == (["testing git-black", "testing git-black"]) 107 | assert run(["black", "--check", "blacktests.py"]).returncode == 0 108 | 109 | 110 | def test_insert_only(tmp_repo): 111 | a = py.path.local("a.py") 112 | a.write( 113 | dedent( 114 | """ 115 | line1 116 | line2 117 | line3 118 | """ 119 | ) 120 | ) 121 | git_add(a) 122 | git_commit("commit1") 123 | 124 | a.write( 125 | dedent( 126 | """ 127 | line1 128 | """ 129 | ) 130 | ) 131 | 132 | git_black() 133 | 134 | assert git_log() == ["commit2", "commit1"] 135 | 136 | 137 | # @pytest.mark.parametrize( 138 | # ("src", "dst", "expected"), 139 | # [ 140 | # ("abc", "abcde", [(0,), (1,), (2,), (2,), (2,)]), 141 | # ("abcde", "abc", [(0,), (1,), (2, 3, 4)]), 142 | # ("", "abc", [(), (), ()]), 143 | # ("abc", "", []), 144 | # ], 145 | # ) 146 | # def test_delta_origins(src, dst, expected): 147 | # delta = Delta(src_start=0, src_lines=src, dst_start=0, dst_lines=dst) 148 | # assert GitBlack.compute_origin(delta) == expected 149 | -------------------------------------------------------------------------------- /src/git_black/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import re 3 | import sys 4 | import time 5 | from bisect import bisect 6 | from collections import namedtuple 7 | from concurrent.futures import ( 8 | FIRST_COMPLETED, 9 | ProcessPoolExecutor, 10 | ThreadPoolExecutor, 11 | wait, 12 | ) 13 | from dataclasses import dataclass 14 | from datetime import datetime, timedelta, timezone 15 | from io import BytesIO 16 | from subprocess import PIPE, Popen 17 | from threading import Lock 18 | from typing import Dict, List, Tuple 19 | 20 | import click 21 | from pygit2 import ( 22 | GIT_DELTA_MODIFIED, 23 | GIT_DIFF_IGNORE_SUBMODULES, 24 | GIT_FILEMODE_BLOB, 25 | GIT_STATUS_INDEX_DELETED, 26 | GIT_STATUS_INDEX_MODIFIED, 27 | GIT_STATUS_INDEX_NEW, 28 | GIT_STATUS_INDEX_RENAMED, 29 | GIT_STATUS_INDEX_TYPECHANGE, 30 | Commit, 31 | DiffHunk, 32 | IndexEntry, 33 | Oid, 34 | Patch, 35 | Repository, 36 | Signature, 37 | ) 38 | 39 | logger = logging.getLogger(__name__) 40 | 41 | index_statuses = ( 42 | GIT_STATUS_INDEX_NEW 43 | | GIT_STATUS_INDEX_MODIFIED 44 | | GIT_STATUS_INDEX_DELETED 45 | | GIT_STATUS_INDEX_RENAMED 46 | | GIT_STATUS_INDEX_TYPECHANGE 47 | ) 48 | 49 | 50 | def commit_datetime(commit: Commit): 51 | tzinfo = timezone(timedelta(minutes=commit.commit_time_offset)) 52 | return datetime.fromtimestamp(float(commit.commit_time), tzinfo) 53 | 54 | 55 | @dataclass(frozen=True) 56 | class Delta: 57 | """this is a simplified version of unidiff.Hunk""" 58 | 59 | filename: str 60 | old_start: int 61 | old_lines: List[bytes] 62 | old_length: int 63 | new_start: int 64 | new_length: int 65 | new_lines: List[bytes] 66 | 67 | @property 68 | def offset(self): 69 | return self.new_length - self.old_length 70 | 71 | @staticmethod 72 | def from_hunk(hunk: DiffHunk, filename): 73 | old_lines = [line.raw_content for line in hunk.lines if line.origin == "-"] 74 | new_lines = [line.raw_content for line in hunk.lines if line.origin == "+"] 75 | return Delta( 76 | filename=filename, 77 | old_start=hunk.old_start, 78 | old_length=hunk.old_lines, 79 | old_lines=old_lines, 80 | new_start=hunk.new_start, 81 | new_length=hunk.new_lines, 82 | new_lines=new_lines, 83 | ) 84 | 85 | def __str__(self): 86 | s = [ 87 | "Delta(", 88 | f" filename={self.filename},", 89 | f" old_start={self.old_start},", 90 | f" old_length={self.old_length}", 91 | " old_lines=[", 92 | ] 93 | for line in self.old_lines: 94 | s.append(" {!r},".format(line)) 95 | s.append(" ],") 96 | s.append(f" new_start={self.new_start},") 97 | s.append(f" new_length={self.new_length},") 98 | s.append(" new_lines=[") 99 | for line in self.new_lines: 100 | s.append(" {!r},".format(line)) 101 | s.append(" ]") 102 | s.append(")") 103 | return "\n".join(s) 104 | 105 | 106 | DeltaBlame = namedtuple("DeltaBlame", "delta commits") 107 | 108 | 109 | blame_re = re.compile(rb"^(?P[0-9a-f]{40}) (\d+) (?P\d+).*") 110 | 111 | 112 | class HunkBlamer: 113 | def __init__(self, repo, patch: Patch): 114 | self.repo = repo 115 | self.patch = patch 116 | self.filename = patch.delta.old_file.path 117 | # self._load_blame() 118 | # self._blame_obj = self.repo.blame(self.filename) 119 | self._load_blame() 120 | 121 | def _load_blame(self): 122 | # libgit2 blame is currently much much slower than calling an external 123 | # git command: https://github.com/libgit2/libgit2/issues/3027 124 | 125 | blame_proc = Popen( 126 | ["git", "blame", "--porcelain", "HEAD", self.filename], stdout=PIPE 127 | ) 128 | self._blame_map = {} 129 | for line in blame_proc.stdout: 130 | m = blame_re.match(line) 131 | if not m: 132 | continue 133 | commit = m.group("commit").decode("ascii") 134 | lineno = int(m.group("lineno")) 135 | self._blame_map[lineno] = commit 136 | 137 | def _blame(self, lineno) -> str: 138 | return self._blame_map[lineno] 139 | 140 | def _map_lines(self, delta: Delta) -> Dict[Tuple, Tuple]: 141 | """ 142 | return a dict that maps tuples of source lines 143 | to tuples of destination lines. Each key/value pair 144 | in the dict represents a set of source lines (the key tuple) 145 | that became the destination lines (the value tuple). 146 | 147 | although weird, the numbers in the tuples are 0-indexed. 148 | 149 | e.g. 150 | { # src: dst 151 | (0,1): (0) # the first 2 lines collapsed into the first line of the output 152 | (2,): (1,2,3) # the third line expanded into lines 2 3 and 4 153 | } 154 | """ 155 | 156 | # this is harder than I thought; I'll start with a super naive 157 | # approach and improve it later (or never) 158 | 159 | if delta.old_length == 0: 160 | return {(): tuple(range(delta.new_length))} 161 | if delta.new_length == 0: 162 | return {tuple(range(delta.old_length)): ()} 163 | 164 | result: Dict[Tuple[int, ...], Tuple[int, ...]] = {} 165 | 166 | for i in range(min(delta.old_length, delta.new_length) - 1): 167 | result[(i,)] = (i,) 168 | 169 | if delta.old_length >= delta.new_length: 170 | result[tuple(range(delta.new_length - 1, delta.old_length))] = ( 171 | delta.new_length - 1, 172 | ) 173 | else: 174 | result[(delta.old_length - 1,)] = tuple( 175 | range(delta.old_length - 1, delta.new_length) 176 | ) 177 | 178 | return result 179 | 180 | def blames(self) -> List[DeltaBlame]: 181 | hunk_deltas = [ 182 | Delta.from_hunk(hunk, self.filename) for hunk in self.patch.hunks 183 | ] 184 | 185 | # let's map each hunk to its source commits and break down the deltas 186 | # in smaller chunks; this will make it possible to prepare and group 187 | # commits with a much smaller granularity 188 | deltas = [] 189 | for hd in hunk_deltas: 190 | for old_linenos, new_linenos in self._map_lines(hd).items(): 191 | old_start = hd.old_start + min(old_linenos, default=0) 192 | old_lines = [hd.old_lines[lineno] for lineno in old_linenos] 193 | new_start = hd.new_start + min(new_linenos, default=0) 194 | new_lines = [hd.new_lines[lineno] for lineno in new_linenos] 195 | delta = Delta( 196 | filename=self.filename, 197 | old_start=old_start, 198 | old_lines=old_lines, 199 | old_length=len(old_linenos), 200 | new_start=new_start, 201 | new_lines=new_lines, 202 | new_length=len(new_lines), 203 | ) 204 | deltas.append(delta) 205 | 206 | blames = [] 207 | for i, delta in enumerate(deltas): 208 | delta_blame = DeltaBlame(delta=delta, commits=set()) 209 | for line in range( 210 | delta.old_start, delta.old_start + max(1, delta.old_length) 211 | ): 212 | commit = self._blame(line) 213 | delta_blame.commits.add(commit) 214 | blames.append(delta_blame) 215 | 216 | return blames 217 | 218 | 219 | class Patcher: 220 | def __init__(self, repo, filename): 221 | self.repo = repo 222 | self.filename = filename 223 | self._load_lines() 224 | self._offsets = {} 225 | self._applied = set() 226 | 227 | def _load_lines(self): 228 | head = self.repo.head.peel() 229 | obj = head.tree 230 | for component in self.filename.split("/"): 231 | obj = obj / component 232 | self._lines = BytesIO(obj.data).readlines() 233 | 234 | def apply(self, delta: Delta): 235 | if (delta.old_start) in self._applied: 236 | return 237 | 238 | old_length = delta.old_length 239 | old_start = delta.old_start 240 | for pos, off in self._offsets.items(): 241 | if delta.old_start > pos: 242 | old_start += off 243 | 244 | # I don't understand why, but hunks need 245 | # this when the old_length is 0 246 | if old_length == 0: 247 | old_start += 1 248 | 249 | i = old_start - 1 250 | j = i + old_length 251 | self._lines[i:j] = delta.new_lines 252 | 253 | self._offsets[delta.old_start] = delta.offset 254 | 255 | self._applied.add(delta.old_start) 256 | 257 | def content(self): 258 | return b"".join(self._lines) 259 | 260 | 261 | class GitIndexNotEmpty(Exception): 262 | pass 263 | 264 | 265 | class GitBlack: 266 | def __init__(self): 267 | self.repo = Repository(".") 268 | self.patchers = {} 269 | 270 | def get_blamed_deltas(self, patch): 271 | filename = patch.delta.old_file.path 272 | self.patchers[filename] = Patcher(self.repo, filename) 273 | hb = HunkBlamer(self.repo, patch) 274 | return hb.blames() 275 | 276 | def group_blame_deltas(self, blames): 277 | for delta_blame in blames: 278 | commits = tuple(sorted(delta_blame.commits)) 279 | self.grouped_deltas.setdefault(commits, []).append(delta_blame.delta) 280 | 281 | self.progress += 1 282 | now = time.monotonic() 283 | if now - self.last_log > 0.04: 284 | sys.stdout.write("Reading file {}/{} \r".format(self.progress, self.total)) 285 | sys.stdout.flush() 286 | self.last_log = now 287 | 288 | def commit_changes(self): 289 | start = time.monotonic() 290 | self.grouped_deltas = {} 291 | 292 | for path, status in self.repo.status().items(): 293 | if status & index_statuses: 294 | raise GitIndexNotEmpty 295 | 296 | patches = [] 297 | self._file_modes = {} 298 | diff = self.repo.diff(context_lines=0, flags=GIT_DIFF_IGNORE_SUBMODULES) 299 | for patch in diff: 300 | if patch.delta.status != GIT_DELTA_MODIFIED: 301 | continue 302 | self._file_modes[patch.delta.old_file.path] = patch.delta.old_file.mode 303 | patches.append(patch) 304 | 305 | self.progress = 0 306 | self.last_log = 0 307 | self.total = len(patches) 308 | 309 | executor = ThreadPoolExecutor(max_workers=8) 310 | tasks = set() 311 | for patch in patches: 312 | tasks.add(executor.submit(self.get_blamed_deltas, patch)) 313 | if len(tasks) > 8: 314 | done, not_done = wait(tasks, return_when=FIRST_COMPLETED) 315 | for task in done: 316 | self.group_blame_deltas(task.result()) 317 | tasks -= set(done) 318 | 319 | for task in tasks: 320 | self.group_blame_deltas(task.result()) 321 | 322 | secs = time.monotonic() - start 323 | sys.stdout.write( 324 | "Reading file {}/{} ({:.2f} secs).\n".format( 325 | self.progress, self.total, secs 326 | ) 327 | ) 328 | 329 | start = time.monotonic() 330 | self.total = len(self.grouped_deltas) 331 | self.progress = 0 332 | self.last_log = 0 333 | 334 | for commits, deltas in self.grouped_deltas.items(): 335 | blobs = self._create_blobs(deltas) 336 | self._commit(commits, blobs) 337 | 338 | secs = time.monotonic() - start 339 | print( 340 | "Making commit {}/{} ({:.2f} secs).".format(self.progress, self.total, secs) 341 | ) 342 | 343 | def _create_blobs(self, deltas): 344 | filenames = set() 345 | for delta in deltas: 346 | self.patchers[delta.filename].apply(delta) 347 | filenames.add(delta.filename) 348 | 349 | blobs = {} 350 | for filename in filenames: 351 | blob_id = self.repo.create_blob(self.patchers[filename].content()) 352 | blobs[filename] = blob_id 353 | 354 | return blobs 355 | 356 | def _commit(self, original_commits, blobs): 357 | for filename, blob_id in blobs.items(): 358 | file_mode = self._file_modes[filename] 359 | index_entry = IndexEntry(filename, blob_id, file_mode) 360 | self.repo.index.add(index_entry) 361 | 362 | commits = [self.repo.get(h) for h in original_commits] 363 | 364 | main_commit = commits[0] 365 | if len(commits) > 1: 366 | # most recent commit 367 | main_commit = sorted(commits, key=commit_datetime)[-1] 368 | 369 | commit_message = main_commit.message 370 | commit_message += "\n\nautomatic commit by git-black, original commits:\n" 371 | commit_message += "\n".join([" {}".format(c) for c in original_commits]) 372 | 373 | committer = Signature( 374 | name=self.repo.config["user.name"], email=self.repo.config["user.email"], 375 | ) 376 | 377 | self.repo.index.write() 378 | tree = self.repo.index.write_tree() 379 | head = self.repo.head.peel() 380 | self.repo.create_commit( 381 | "HEAD", main_commit.author, committer, commit_message, tree, [head.id] 382 | ) 383 | self.progress += 1 384 | now = time.monotonic() 385 | if now - self.last_log > 0.04: 386 | sys.stdout.write("Making commit {}/{} \r".format(self.progress, self.total)) 387 | sys.stdout.flush() 388 | self.last_log = now 389 | 390 | 391 | @click.command() 392 | def cli(): 393 | gb = GitBlack() 394 | try: 395 | gb.commit_changes() 396 | except GitIndexNotEmpty: 397 | raise click.ClickException("staging area must be empty") 398 | 399 | 400 | if __name__ == "__main__": 401 | cli() 402 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "appdirs" 5 | version = "1.4.4" 6 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 7 | category = "dev" 8 | optional = false 9 | python-versions = "*" 10 | files = [ 11 | {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, 12 | {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, 13 | ] 14 | 15 | [[package]] 16 | name = "atomicwrites" 17 | version = "1.4.0" 18 | description = "Atomic file writes." 19 | category = "dev" 20 | optional = false 21 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 22 | files = [ 23 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, 24 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, 25 | ] 26 | 27 | [[package]] 28 | name = "attrs" 29 | version = "19.3.0" 30 | description = "Classes Without Boilerplate" 31 | category = "dev" 32 | optional = false 33 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 34 | files = [ 35 | {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, 36 | {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, 37 | ] 38 | 39 | [package.extras] 40 | azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-azurepipelines", "six", "zope.interface"] 41 | dev = ["coverage", "hypothesis", "pre-commit", "pympler", "pytest (>=4.3.0)", "six", "sphinx", "zope.interface"] 42 | docs = ["sphinx", "zope.interface"] 43 | tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] 44 | 45 | [[package]] 46 | name = "black" 47 | version = "19.10b0" 48 | description = "The uncompromising code formatter." 49 | category = "dev" 50 | optional = false 51 | python-versions = ">=3.6" 52 | files = [ 53 | {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"}, 54 | {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"}, 55 | ] 56 | 57 | [package.dependencies] 58 | appdirs = "*" 59 | attrs = ">=18.1.0" 60 | click = ">=6.5" 61 | pathspec = ">=0.6,<1" 62 | regex = "*" 63 | toml = ">=0.9.4" 64 | typed-ast = ">=1.4.0" 65 | 66 | [package.extras] 67 | d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] 68 | 69 | [[package]] 70 | name = "cached-property" 71 | version = "1.5.1" 72 | description = "A decorator for caching properties in classes." 73 | category = "main" 74 | optional = false 75 | python-versions = "*" 76 | files = [ 77 | {file = "cached-property-1.5.1.tar.gz", hash = "sha256:9217a59f14a5682da7c4b8829deadbfc194ac22e9908ccf7c8820234e80a1504"}, 78 | {file = "cached_property-1.5.1-py2.py3-none-any.whl", hash = "sha256:3a026f1a54135677e7da5ce819b0c690f156f37976f3e30c5430740725203d7f"}, 79 | ] 80 | 81 | [[package]] 82 | name = "cffi" 83 | version = "1.14.0" 84 | description = "Foreign Function Interface for Python calling C code." 85 | category = "main" 86 | optional = false 87 | python-versions = "*" 88 | files = [ 89 | {file = "cffi-1.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384"}, 90 | {file = "cffi-1.14.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:cf16e3cf6c0a5fdd9bc10c21687e19d29ad1fe863372b5543deaec1039581a30"}, 91 | {file = "cffi-1.14.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f2b0fa0c01d8a0c7483afd9f31d7ecf2d71760ca24499c8697aeb5ca37dc090c"}, 92 | {file = "cffi-1.14.0-cp27-cp27m-win32.whl", hash = "sha256:99f748a7e71ff382613b4e1acc0ac83bf7ad167fb3802e35e90d9763daba4d78"}, 93 | {file = "cffi-1.14.0-cp27-cp27m-win_amd64.whl", hash = "sha256:c420917b188a5582a56d8b93bdd8e0f6eca08c84ff623a4c16e809152cd35793"}, 94 | {file = "cffi-1.14.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:399aed636c7d3749bbed55bc907c3288cb43c65c4389964ad5ff849b6370603e"}, 95 | {file = "cffi-1.14.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cab50b8c2250b46fe738c77dbd25ce017d5e6fb35d3407606e7a4180656a5a6a"}, 96 | {file = "cffi-1.14.0-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:001bf3242a1bb04d985d63e138230802c6c8d4db3668fb545fb5005ddf5bb5ff"}, 97 | {file = "cffi-1.14.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:e56c744aa6ff427a607763346e4170629caf7e48ead6921745986db3692f987f"}, 98 | {file = "cffi-1.14.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b8c78301cefcf5fd914aad35d3c04c2b21ce8629b5e4f4e45ae6812e461910fa"}, 99 | {file = "cffi-1.14.0-cp35-cp35m-win32.whl", hash = "sha256:8c0ffc886aea5df6a1762d0019e9cb05f825d0eec1f520c51be9d198701daee5"}, 100 | {file = "cffi-1.14.0-cp35-cp35m-win_amd64.whl", hash = "sha256:8a6c688fefb4e1cd56feb6c511984a6c4f7ec7d2a1ff31a10254f3c817054ae4"}, 101 | {file = "cffi-1.14.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:95cd16d3dee553f882540c1ffe331d085c9e629499ceadfbda4d4fde635f4b7d"}, 102 | {file = "cffi-1.14.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:66e41db66b47d0d8672d8ed2708ba91b2f2524ece3dee48b5dfb36be8c2f21dc"}, 103 | {file = "cffi-1.14.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:028a579fc9aed3af38f4892bdcc7390508adabc30c6af4a6e4f611b0c680e6ac"}, 104 | {file = "cffi-1.14.0-cp36-cp36m-win32.whl", hash = "sha256:cef128cb4d5e0b3493f058f10ce32365972c554572ff821e175dbc6f8ff6924f"}, 105 | {file = "cffi-1.14.0-cp36-cp36m-win_amd64.whl", hash = "sha256:337d448e5a725bba2d8293c48d9353fc68d0e9e4088d62a9571def317797522b"}, 106 | {file = "cffi-1.14.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e577934fc5f8779c554639376beeaa5657d54349096ef24abe8c74c5d9c117c3"}, 107 | {file = "cffi-1.14.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:62ae9af2d069ea2698bf536dcfe1e4eed9090211dbaafeeedf5cb6c41b352f66"}, 108 | {file = "cffi-1.14.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:14491a910663bf9f13ddf2bc8f60562d6bc5315c1f09c704937ef17293fb85b0"}, 109 | {file = "cffi-1.14.0-cp37-cp37m-win32.whl", hash = "sha256:c43866529f2f06fe0edc6246eb4faa34f03fe88b64a0a9a942561c8e22f4b71f"}, 110 | {file = "cffi-1.14.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2089ed025da3919d2e75a4d963d008330c96751127dd6f73c8dc0c65041b4c26"}, 111 | {file = "cffi-1.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3b911c2dbd4f423b4c4fcca138cadde747abdb20d196c4a48708b8a2d32b16dd"}, 112 | {file = "cffi-1.14.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:7e63cbcf2429a8dbfe48dcc2322d5f2220b77b2e17b7ba023d6166d84655da55"}, 113 | {file = "cffi-1.14.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:3d311bcc4a41408cf5854f06ef2c5cab88f9fded37a3b95936c9879c1640d4c2"}, 114 | {file = "cffi-1.14.0-cp38-cp38-win32.whl", hash = "sha256:675686925a9fb403edba0114db74e741d8181683dcf216be697d208857e04ca8"}, 115 | {file = "cffi-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:00789914be39dffba161cfc5be31b55775de5ba2235fe49aa28c148236c4e06b"}, 116 | {file = "cffi-1.14.0.tar.gz", hash = "sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6"}, 117 | ] 118 | 119 | [package.dependencies] 120 | pycparser = "*" 121 | 122 | [[package]] 123 | name = "click" 124 | version = "7.1.2" 125 | description = "Composable command line interface toolkit" 126 | category = "main" 127 | optional = false 128 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 129 | files = [ 130 | {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, 131 | {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, 132 | ] 133 | 134 | [[package]] 135 | name = "colorama" 136 | version = "0.4.3" 137 | description = "Cross-platform colored terminal text." 138 | category = "dev" 139 | optional = false 140 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 141 | files = [ 142 | {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, 143 | {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, 144 | ] 145 | 146 | [[package]] 147 | name = "flake8" 148 | version = "3.8.2" 149 | description = "the modular source code checker: pep8 pyflakes and co" 150 | category = "dev" 151 | optional = false 152 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" 153 | files = [ 154 | {file = "flake8-3.8.2-py2.py3-none-any.whl", hash = "sha256:ccaa799ef9893cebe69fdfefed76865aeaefbb94cb8545617b2298786a4de9a5"}, 155 | {file = "flake8-3.8.2.tar.gz", hash = "sha256:c69ac1668e434d37a2d2880b3ca9aafd54b3a10a3ac1ab101d22f29e29cf8634"}, 156 | ] 157 | 158 | [package.dependencies] 159 | mccabe = ">=0.6.0,<0.7.0" 160 | pycodestyle = ">=2.6.0a1,<2.7.0" 161 | pyflakes = ">=2.2.0,<2.3.0" 162 | 163 | [[package]] 164 | name = "jinja2" 165 | version = "2.11.2" 166 | description = "A very fast and expressive template engine." 167 | category = "dev" 168 | optional = false 169 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 170 | files = [ 171 | {file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"}, 172 | {file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"}, 173 | ] 174 | 175 | [package.dependencies] 176 | MarkupSafe = ">=0.23" 177 | 178 | [package.extras] 179 | i18n = ["Babel (>=0.8)"] 180 | 181 | [[package]] 182 | name = "markupsafe" 183 | version = "1.1.1" 184 | description = "Safely add untrusted strings to HTML/XML markup." 185 | category = "dev" 186 | optional = false 187 | python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" 188 | files = [ 189 | {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, 190 | {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, 191 | {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"}, 192 | {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"}, 193 | {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"}, 194 | {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"}, 195 | {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"}, 196 | {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"}, 197 | {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"}, 198 | {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"}, 199 | {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"}, 200 | {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"}, 201 | {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"}, 202 | {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"}, 203 | {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"}, 204 | {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, 205 | {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, 206 | {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, 207 | {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5"}, 208 | {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, 209 | {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, 210 | {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f"}, 211 | {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0"}, 212 | {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7"}, 213 | {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, 214 | {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, 215 | {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, 216 | {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193"}, 217 | {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, 218 | {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, 219 | {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1"}, 220 | {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1"}, 221 | {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f"}, 222 | {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, 223 | {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, 224 | {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, 225 | {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, 226 | {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, 227 | {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2"}, 228 | {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032"}, 229 | {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b"}, 230 | {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, 231 | {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, 232 | {file = "MarkupSafe-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c"}, 233 | {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb"}, 234 | {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014"}, 235 | {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850"}, 236 | {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85"}, 237 | {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"}, 238 | {file = "MarkupSafe-1.1.1-cp39-cp39-win32.whl", hash = "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39"}, 239 | {file = "MarkupSafe-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8"}, 240 | {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, 241 | ] 242 | 243 | [[package]] 244 | name = "mccabe" 245 | version = "0.6.1" 246 | description = "McCabe checker, plugin for flake8" 247 | category = "dev" 248 | optional = false 249 | python-versions = "*" 250 | files = [ 251 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 252 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 253 | ] 254 | 255 | [[package]] 256 | name = "more-itertools" 257 | version = "8.3.0" 258 | description = "More routines for operating on iterables, beyond itertools" 259 | category = "dev" 260 | optional = false 261 | python-versions = ">=3.5" 262 | files = [ 263 | {file = "more-itertools-8.3.0.tar.gz", hash = "sha256:558bb897a2232f5e4f8e2399089e35aecb746e1f9191b6584a151647e89267be"}, 264 | {file = "more_itertools-8.3.0-py3-none-any.whl", hash = "sha256:7818f596b1e87be009031c7653d01acc46ed422e6656b394b0f765ce66ed4982"}, 265 | ] 266 | 267 | [[package]] 268 | name = "packaging" 269 | version = "20.4" 270 | description = "Core utilities for Python packages" 271 | category = "dev" 272 | optional = false 273 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 274 | files = [ 275 | {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, 276 | {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"}, 277 | ] 278 | 279 | [package.dependencies] 280 | pyparsing = ">=2.0.2" 281 | six = "*" 282 | 283 | [[package]] 284 | name = "pathspec" 285 | version = "0.8.0" 286 | description = "Utility library for gitignore style pattern matching of file paths." 287 | category = "dev" 288 | optional = false 289 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 290 | files = [ 291 | {file = "pathspec-0.8.0-py2.py3-none-any.whl", hash = "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0"}, 292 | {file = "pathspec-0.8.0.tar.gz", hash = "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061"}, 293 | ] 294 | 295 | [[package]] 296 | name = "pluggy" 297 | version = "0.13.1" 298 | description = "plugin and hook calling mechanisms for python" 299 | category = "dev" 300 | optional = false 301 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 302 | files = [ 303 | {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, 304 | {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, 305 | ] 306 | 307 | [package.extras] 308 | dev = ["pre-commit", "tox"] 309 | 310 | [[package]] 311 | name = "py" 312 | version = "1.8.1" 313 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 314 | category = "dev" 315 | optional = false 316 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 317 | files = [ 318 | {file = "py-1.8.1-py2.py3-none-any.whl", hash = "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"}, 319 | {file = "py-1.8.1.tar.gz", hash = "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa"}, 320 | ] 321 | 322 | [[package]] 323 | name = "pycodestyle" 324 | version = "2.6.0" 325 | description = "Python style guide checker" 326 | category = "dev" 327 | optional = false 328 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 329 | files = [ 330 | {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, 331 | {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, 332 | ] 333 | 334 | [[package]] 335 | name = "pycparser" 336 | version = "2.20" 337 | description = "C parser in Python" 338 | category = "main" 339 | optional = false 340 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 341 | files = [ 342 | {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, 343 | {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, 344 | ] 345 | 346 | [[package]] 347 | name = "pyflakes" 348 | version = "2.2.0" 349 | description = "passive checker of Python programs" 350 | category = "dev" 351 | optional = false 352 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 353 | files = [ 354 | {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, 355 | {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, 356 | ] 357 | 358 | [[package]] 359 | name = "pygit2" 360 | version = "1.2.1" 361 | description = "Python bindings for libgit2." 362 | category = "main" 363 | optional = false 364 | python-versions = "*" 365 | files = [ 366 | {file = "pygit2-1.2.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ea756028a8a3e94ee814054e61ad1680abecc51de7281b5184ee2a3b38db836c"}, 367 | {file = "pygit2-1.2.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:9252ee6f728e8d5b3b0b22545b7a7119d1f4a8e5b2fbe4d074c1913875152392"}, 368 | {file = "pygit2-1.2.1-cp36-cp36m-win32.whl", hash = "sha256:efa6aabfcfada75add98374dc36238069239127f7e1865409047f5ef3f7bee58"}, 369 | {file = "pygit2-1.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:cbd2ae41df7ce4ae911d2c51762f651b78e8b6fc00048ff6065e04448afde147"}, 370 | {file = "pygit2-1.2.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d42ff6839e756e83ac65c7a4c3eb063e55967450bc9a4ccc8154269a2d59fb9e"}, 371 | {file = "pygit2-1.2.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bbe671ace219bc02ad0a4d83d7611949d7bf4d8f44e91cedc6f03958cff71cc4"}, 372 | {file = "pygit2-1.2.1-cp37-cp37m-win32.whl", hash = "sha256:97a8313cdb8e33dc47c6beef78b0222c23653186377520dc6811d6d276c19d1f"}, 373 | {file = "pygit2-1.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a159e2a9ecb397ff85b62199956d932ef4b72375bcaa21090a359ea530a187e5"}, 374 | {file = "pygit2-1.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6bf0409149a5cb0863203508d060feea51c32f618e8d9d9d4b910ed3b4edd7bd"}, 375 | {file = "pygit2-1.2.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:db98476484d40d3372d3626947f66ada85caf6f44837350db180083fdf43551a"}, 376 | {file = "pygit2-1.2.1-cp38-cp38-win32.whl", hash = "sha256:efe14d916a75ea50c122bf12478d67b52c2e0c8e3f59d7b64e3b5b560eb60bc0"}, 377 | {file = "pygit2-1.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:44f55b92ca386ff4823be302354de6627634f7a916d590cae4543376b190cafb"}, 378 | {file = "pygit2-1.2.1.tar.gz", hash = "sha256:de9421118a99c79cbba1e512d60e5caed1d63273ce30a0e8d4edef4a2e500387"}, 379 | ] 380 | 381 | [package.dependencies] 382 | cached-property = "*" 383 | cffi = ">=1.4.0" 384 | 385 | [[package]] 386 | name = "pyparsing" 387 | version = "2.4.7" 388 | description = "Python parsing module" 389 | category = "dev" 390 | optional = false 391 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 392 | files = [ 393 | {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, 394 | {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, 395 | ] 396 | 397 | [[package]] 398 | name = "pytest" 399 | version = "5.4.3" 400 | description = "pytest: simple powerful testing with Python" 401 | category = "dev" 402 | optional = false 403 | python-versions = ">=3.5" 404 | files = [ 405 | {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, 406 | {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, 407 | ] 408 | 409 | [package.dependencies] 410 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 411 | attrs = ">=17.4.0" 412 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 413 | more-itertools = ">=4.0.0" 414 | packaging = "*" 415 | pluggy = ">=0.12,<1.0" 416 | py = ">=1.5.0" 417 | wcwidth = "*" 418 | 419 | [package.extras] 420 | checkqa-mypy = ["mypy (==v0.761)"] 421 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 422 | 423 | [[package]] 424 | name = "regex" 425 | version = "2020.6.8" 426 | description = "Alternative regular expression module, to replace re." 427 | category = "dev" 428 | optional = false 429 | python-versions = "*" 430 | files = [ 431 | {file = "regex-2020.6.8-cp27-cp27m-win32.whl", hash = "sha256:fbff901c54c22425a5b809b914a3bfaf4b9570eee0e5ce8186ac71eb2025191c"}, 432 | {file = "regex-2020.6.8-cp27-cp27m-win_amd64.whl", hash = "sha256:112e34adf95e45158c597feea65d06a8124898bdeac975c9087fe71b572bd938"}, 433 | {file = "regex-2020.6.8-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:92d8a043a4241a710c1cf7593f5577fbb832cf6c3a00ff3fc1ff2052aff5dd89"}, 434 | {file = "regex-2020.6.8-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:bae83f2a56ab30d5353b47f9b2a33e4aac4de9401fb582b55c42b132a8ac3868"}, 435 | {file = "regex-2020.6.8-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:b2ba0f78b3ef375114856cbdaa30559914d081c416b431f2437f83ce4f8b7f2f"}, 436 | {file = "regex-2020.6.8-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:95fa7726d073c87141f7bbfb04c284901f8328e2d430eeb71b8ffdd5742a5ded"}, 437 | {file = "regex-2020.6.8-cp36-cp36m-win32.whl", hash = "sha256:e3cdc9423808f7e1bb9c2e0bdb1c9dc37b0607b30d646ff6faf0d4e41ee8fee3"}, 438 | {file = "regex-2020.6.8-cp36-cp36m-win_amd64.whl", hash = "sha256:c78e66a922de1c95a208e4ec02e2e5cf0bb83a36ceececc10a72841e53fbf2bd"}, 439 | {file = "regex-2020.6.8-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:08997a37b221a3e27d68ffb601e45abfb0093d39ee770e4257bd2f5115e8cb0a"}, 440 | {file = "regex-2020.6.8-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2f6f211633ee8d3f7706953e9d3edc7ce63a1d6aad0be5dcee1ece127eea13ae"}, 441 | {file = "regex-2020.6.8-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:55b4c25cbb3b29f8d5e63aeed27b49fa0f8476b0d4e1b3171d85db891938cc3a"}, 442 | {file = "regex-2020.6.8-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:89cda1a5d3e33ec9e231ece7307afc101b5217523d55ef4dc7fb2abd6de71ba3"}, 443 | {file = "regex-2020.6.8-cp37-cp37m-win32.whl", hash = "sha256:690f858d9a94d903cf5cada62ce069b5d93b313d7d05456dbcd99420856562d9"}, 444 | {file = "regex-2020.6.8-cp37-cp37m-win_amd64.whl", hash = "sha256:1700419d8a18c26ff396b3b06ace315b5f2a6e780dad387e4c48717a12a22c29"}, 445 | {file = "regex-2020.6.8-cp38-cp38-manylinux1_i686.whl", hash = "sha256:654cb773b2792e50151f0e22be0f2b6e1c3a04c5328ff1d9d59c0398d37ef610"}, 446 | {file = "regex-2020.6.8-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:52e1b4bef02f4040b2fd547357a170fc1146e60ab310cdbdd098db86e929b387"}, 447 | {file = "regex-2020.6.8-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:cf59bbf282b627130f5ba68b7fa3abdb96372b24b66bdf72a4920e8153fc7910"}, 448 | {file = "regex-2020.6.8-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:5aaa5928b039ae440d775acea11d01e42ff26e1561c0ffcd3d805750973c6baf"}, 449 | {file = "regex-2020.6.8-cp38-cp38-win32.whl", hash = "sha256:97712e0d0af05febd8ab63d2ef0ab2d0cd9deddf4476f7aa153f76feef4b2754"}, 450 | {file = "regex-2020.6.8-cp38-cp38-win_amd64.whl", hash = "sha256:6ad8663c17db4c5ef438141f99e291c4d4edfeaacc0ce28b5bba2b0bf273d9b5"}, 451 | {file = "regex-2020.6.8.tar.gz", hash = "sha256:e9b64e609d37438f7d6e68c2546d2cb8062f3adb27e6336bc129b51be20773ac"}, 452 | ] 453 | 454 | [[package]] 455 | name = "six" 456 | version = "1.15.0" 457 | description = "Python 2 and 3 compatibility utilities" 458 | category = "dev" 459 | optional = false 460 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 461 | files = [ 462 | {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, 463 | {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, 464 | ] 465 | 466 | [[package]] 467 | name = "toml" 468 | version = "0.10.1" 469 | description = "Python Library for Tom's Obvious, Minimal Language" 470 | category = "dev" 471 | optional = false 472 | python-versions = "*" 473 | files = [ 474 | {file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"}, 475 | {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"}, 476 | ] 477 | 478 | [[package]] 479 | name = "typed-ast" 480 | version = "1.4.1" 481 | description = "a fork of Python 2 and 3 ast modules with type comment support" 482 | category = "dev" 483 | optional = false 484 | python-versions = "*" 485 | files = [ 486 | {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, 487 | {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"}, 488 | {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"}, 489 | {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"}, 490 | {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, 491 | {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, 492 | {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, 493 | {file = "typed_ast-1.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:fcf135e17cc74dbfbc05894ebca928ffeb23d9790b3167a674921db19082401f"}, 494 | {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, 495 | {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, 496 | {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, 497 | {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, 498 | {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, 499 | {file = "typed_ast-1.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:f208eb7aff048f6bea9586e61af041ddf7f9ade7caed625742af423f6bae3298"}, 500 | {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, 501 | {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, 502 | {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, 503 | {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, 504 | {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, 505 | {file = "typed_ast-1.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:7e4c9d7658aaa1fc80018593abdf8598bf91325af6af5cce4ce7c73bc45ea53d"}, 506 | {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, 507 | {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, 508 | {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, 509 | {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:92c325624e304ebf0e025d1224b77dd4e6393f18aab8d829b5b7e04afe9b7a2c"}, 510 | {file = "typed_ast-1.4.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d648b8e3bf2fe648745c8ffcee3db3ff903d0817a01a12dd6a6ea7a8f4889072"}, 511 | {file = "typed_ast-1.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:fac11badff8313e23717f3dada86a15389d0708275bddf766cca67a84ead3e91"}, 512 | {file = "typed_ast-1.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:0d8110d78a5736e16e26213114a38ca35cb15b6515d535413b090bd50951556d"}, 513 | {file = "typed_ast-1.4.1-cp39-cp39-win32.whl", hash = "sha256:b52ccf7cfe4ce2a1064b18594381bccf4179c2ecf7f513134ec2f993dd4ab395"}, 514 | {file = "typed_ast-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:3742b32cf1c6ef124d57f95be609c473d7ec4c14d0090e5a5e05a15269fb4d0c"}, 515 | {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, 516 | ] 517 | 518 | [[package]] 519 | name = "wcwidth" 520 | version = "0.2.4" 521 | description = "Measures the displayed width of unicode strings in a terminal" 522 | category = "dev" 523 | optional = false 524 | python-versions = "*" 525 | files = [ 526 | {file = "wcwidth-0.2.4-py2.py3-none-any.whl", hash = "sha256:79375666b9954d4a1a10739315816324c3e73110af9d0e102d906fdb0aec009f"}, 527 | {file = "wcwidth-0.2.4.tar.gz", hash = "sha256:8c6b5b6ee1360b842645f336d9e5d68c55817c26d3050f46b235ef2bc650e48f"}, 528 | ] 529 | 530 | [metadata] 531 | lock-version = "2.0" 532 | python-versions = "^3.8" 533 | content-hash = "9585afd2fe99385205464d3bc285aa4050437d3787ea094081633d36ade7a103" 534 | --------------------------------------------------------------------------------