├── tests ├── __init__.py └── test_sample.py ├── rmlines ├── rmobject │ ├── __init__.py │ ├── layer.py │ ├── base.py │ ├── lines.py │ ├── segment.py │ └── stroke.py ├── empty.jpg ├── __init__.py ├── constants.py ├── rmcloud.py └── svg.py ├── .gitignore ├── .flake8 ├── pdf_to_rm_format.gif ├── samples ├── 736a2a4e-dd0e-4a6e-9326-797508bcb84d.rm ├── 03f23a6e-c14b-4dba-836d-828707979356.rm ├── 8d75db7b-7716-4899-9ec5-1f89bcb88e10.rm ├── c9c8f92c-772e-49f6-b320-71ce0bbe1862.rm └── ca59e6e3-a850-45e3-94dc-64bb1b1ed254.rm ├── requirements.txt ├── scripts ├── samples.py ├── pen_gallery.py └── pdf_converter.py ├── pyproject.toml ├── LICENSE ├── README.md ├── fonts └── HersheySans1.svg └── poetry.lock /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rmlines/rmobject/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .venv/ 3 | .venv_pypy/ 4 | .vscode/ 5 | .env -------------------------------------------------------------------------------- /rmlines/empty.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsdz/remarkable-layers/HEAD/rmlines/empty.jpg -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 80 3 | select = C,E,F,W,B,B950 4 | ignore = E203, E501, W503 -------------------------------------------------------------------------------- /pdf_to_rm_format.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsdz/remarkable-layers/HEAD/pdf_to_rm_format.gif -------------------------------------------------------------------------------- /samples/736a2a4e-dd0e-4a6e-9326-797508bcb84d.rm: -------------------------------------------------------------------------------- 1 | reMarkable .lines file, version=5  -------------------------------------------------------------------------------- /samples/03f23a6e-c14b-4dba-836d-828707979356.rm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsdz/remarkable-layers/HEAD/samples/03f23a6e-c14b-4dba-836d-828707979356.rm -------------------------------------------------------------------------------- /samples/8d75db7b-7716-4899-9ec5-1f89bcb88e10.rm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsdz/remarkable-layers/HEAD/samples/8d75db7b-7716-4899-9ec5-1f89bcb88e10.rm -------------------------------------------------------------------------------- /samples/c9c8f92c-772e-49f6-b320-71ce0bbe1862.rm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsdz/remarkable-layers/HEAD/samples/c9c8f92c-772e-49f6-b320-71ce0bbe1862.rm -------------------------------------------------------------------------------- /samples/ca59e6e3-a850-45e3-94dc-64bb1b1ed254.rm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsdz/remarkable-layers/HEAD/samples/ca59e6e3-a850-45e3-94dc-64bb1b1ed254.rm -------------------------------------------------------------------------------- /rmlines/__init__.py: -------------------------------------------------------------------------------- 1 | from .constants import Colour, Pen, Width, X_MAX, Y_MAX 2 | from .rmobject.segment import Segment 3 | from .rmobject.stroke import Stroke 4 | from .rmobject.layer import Layer 5 | from .rmobject.lines import RMLines 6 | -------------------------------------------------------------------------------- /rmlines/rmobject/layer.py: -------------------------------------------------------------------------------- 1 | from .base import ByteableList 2 | from .stroke import Stroke 3 | 4 | 5 | class Layer(ByteableList): 6 | __slots__ = "name" 7 | 8 | @classmethod 9 | def child_type(cls): 10 | return Stroke 11 | 12 | def __init__(self, name=None): 13 | self.name = name 14 | super().__init__() 15 | 16 | def __str__(self): 17 | return f"Layer: nobjs={len(self.objects)}" 18 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2020.6.20 2 | chardet==3.0.4 3 | idna==2.9 4 | lxml==4.5.1 5 | numpy==1.19.0 6 | pillow==7.1.2 7 | pyaml==19.4.1 8 | -e git+https://github.com/flupke/pypotrace.git@76c76be2458eb2b56fcbd3bec79b1b4077e35d9e#egg=pypotrace 9 | pyyaml==5.3.1 10 | requests==2.24.0 11 | -e git+https://github.com/bsdz/rmapy.git@d53983e68021402f6b1dc8d736633a9c53f903b2#egg=rmapy 12 | -e git+https://github.com/mathandy/svgpathtools.git@f99f9d6bb3519ecdcb7b167f75a09178595cf89c#egg=svgpathtools 13 | svgwrite==1.4 14 | urllib3==1.25.9 15 | -------------------------------------------------------------------------------- /scripts/samples.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from rmlines.rmcloud import upload_rm_doc 4 | from rmlines import RMLines 5 | 6 | SAMPLES_DIR = Path(__file__).parents[1] / "samples" 7 | 8 | 9 | def main(): 10 | 11 | rms = [] 12 | for p in SAMPLES_DIR.glob("*.rm"): 13 | rm = RMLines.from_bytes(p.open("rb")) 14 | for i, layer in enumerate(rm.objects): 15 | layer.name = f"Layer {i+1}" 16 | rms.append(rm) 17 | 18 | upload_rm_doc("Samples", rms) 19 | 20 | 21 | if __name__ == "__main__": 22 | main() 23 | -------------------------------------------------------------------------------- /tests/test_sample.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pathlib import Path 3 | from io import BytesIO 4 | 5 | from rmlines import RMLines 6 | 7 | SAMPLES_DIR = Path(__file__).parents[1] / "samples" 8 | 9 | 10 | class SampleTest(unittest.TestCase): 11 | def test_roundtrip(self): 12 | # logging.basicConfig(level="INFO") 13 | # logger.setLevel("DEBUG") 14 | for p in SAMPLES_DIR.glob("*.rm"): 15 | with self.subTest(p): 16 | if p.name == "8d75db7b-7716-4899-9ec5-1f89bcb88e10.rm": 17 | # TODO: rubber pen 18 | continue 19 | rm0 = RMLines.from_bytes(p.open("rb")) 20 | rm0.dump() 21 | 22 | buffer = BytesIO() 23 | rm0.to_bytes(buffer) 24 | self.assertEqual(buffer.getvalue(), p.read_bytes()) 25 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "rmlines" 3 | version = "0.1.1" 4 | description = "" 5 | authors = ["Blair Azzopardi "] 6 | 7 | [tool.poetry.dependencies] 8 | python = "^3.7" 9 | lxml = { version = "^4.5.1" } 10 | svgpathtools = { git = "https://github.com/mathandy/svgpathtools.git", rev = "f99f9d6bb3519ecdcb7b167f75a09178595cf89c" } 11 | rmapy = {git = "https://github.com/bsdz/rmapy.git", rev = "master"} 12 | pillow = { version = "^8.0.0" } 13 | numpy = { version = "^1.19.4" } 14 | potrace = { git = "https://github.com/bsdz/pypotrace.git", rev = "master" } 15 | 16 | [tool.poetry.dev-dependencies] 17 | black = "^19.10b0" 18 | flake8 = "^3.8.2" 19 | ipykernel = "^5.3.0" 20 | snakeviz = "^2.1.0" 21 | rope = "^0.17.0" 22 | 23 | [tool.poetry.scripts] 24 | rmlines_pdf_converter = 'scripts.pdf_converter:main' 25 | rmlines_pen_gallery = 'scripts.pen_gallery:main' 26 | rmlines_samples = 'scripts.samples:main' 27 | 28 | [build-system] 29 | requires = ["poetry>=0.12"] 30 | build-backend = "poetry.masonry.api" 31 | -------------------------------------------------------------------------------- /rmlines/constants.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | HEADER_V5 = b"reMarkable .lines file, version=5 " 4 | 5 | 6 | class Colour(Enum): 7 | BLACK = 0 8 | GREY = 1 9 | WHITE = 2 10 | 11 | 12 | class Pen(Enum): 13 | # see https://github.com/ax3l/lines-are-beautiful/blob/develop/include/rmlab/Line.hpp 14 | BRUSH = 0 15 | PENCIL_TILT = 1 16 | BALLPOINT_PEN_1 = 2 17 | MARKER_1 = 3 18 | FINELINER_1 = 4 19 | HIGHLIGHTER = 5 20 | RUBBER = 6 # used in version 5 21 | PENCIL_SHARP = 7 22 | RUBBER_AREA = 8 23 | ERASE_ALL = 9 24 | SELECTION_BRUSH_1 = 10 25 | SELECTION_BRUSH_2 = 11 26 | # below used for version 5 27 | PAINT_BRUSH_1 = 12 28 | MECHANICAL_PENCIL_1 = 13 29 | PENCIL_2 = 14 30 | BALLPOINT_PEN_2 = 15 31 | MARKER_2 = 16 32 | FINELINER_2 = 17 33 | HIGHLIGHTER_2 = 18 34 | DEFAULT = FINELINER_2 35 | 36 | 37 | class Width(Enum): 38 | SMALL = 1.875 39 | MEDIUM = 2.0 40 | LARGE = 2.125 41 | 42 | 43 | X_MAX, Y_MAX = 1404.0, 1872.0 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Blair Azzopardi 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. -------------------------------------------------------------------------------- /rmlines/rmobject/base.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from struct import pack, unpack 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | 7 | class ByteableList: 8 | __slots__ = "objects" 9 | 10 | @classmethod 11 | def child_type(cls): 12 | raise NotImplementedError("Must specify child type for reading byte data") 13 | 14 | def __init__(self): 15 | self.objects = [] 16 | 17 | def append(self, obj): 18 | self.objects.append(obj) 19 | 20 | def extend(self, objs): 21 | self.objects.extend(objs) 22 | 23 | def dump(self): 24 | logger.debug(self) 25 | for obj in self.objects: 26 | obj.dump() 27 | 28 | def to_bytes(self, buffer): 29 | buffer.write(pack("= 12: 32 | strokes = svf.word_to_strokes( 33 | f"{pen}, {width}", pos, 40, pen=pen, width=width, segment_width=2. 34 | ) 35 | layer.extend(strokes) 36 | pos = Pos(pos[0], pos[1] + 40) 37 | if pos[1] > Y_MAX: 38 | break 39 | 40 | rm.append(layer) 41 | 42 | upload_rm_doc("Pen Gallery", [rm]) 43 | 44 | 45 | if __name__ == "__main__": 46 | main() 47 | -------------------------------------------------------------------------------- /rmlines/rmobject/lines.py: -------------------------------------------------------------------------------- 1 | from struct import unpack, calcsize 2 | 3 | from ..constants import HEADER_V5, X_MAX, Y_MAX 4 | from .base import ByteableList 5 | from .layer import Layer 6 | 7 | 8 | class RMLines(ByteableList): 9 | __slots__ = "header" 10 | 11 | @classmethod 12 | def child_type(cls): 13 | return Layer 14 | 15 | def __init__(self, header=HEADER_V5): 16 | self.header = header 17 | super().__init__() 18 | 19 | def __str__(self): 20 | return f'RMLines: header="{self.header.decode("ascii").strip()}", nobjs={len(self.objects)}' 21 | 22 | def to_bytes(self, buffer): 23 | buffer.write(self.header) 24 | super().to_bytes(buffer) 25 | 26 | @classmethod 27 | def from_bytes(cls, buffer): 28 | fmt = "<43s" 29 | (header,) = unpack(fmt, buffer.read(calcsize(fmt))) 30 | ins = super().from_bytes(buffer) 31 | ins.header = header 32 | return ins 33 | 34 | def to_svg(self, buffer): 35 | buffer.write( 36 | f'' 38 | ) 39 | super().to_svg(buffer) 40 | buffer.write("") 41 | 42 | @classmethod 43 | def from_svg(cls, buffer): 44 | from ..svg import svg_to_rmlines 45 | return svg_to_rmlines(buffer) 46 | -------------------------------------------------------------------------------- /rmlines/rmobject/segment.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from struct import pack, unpack, calcsize 3 | import math 4 | 5 | from ..constants import X_MAX, Y_MAX 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | class Segment: 11 | __slots__ = ("x", "y", "speed", "tilt", "width", "pressure") 12 | 13 | def __init__(self, x, y, speed=1.0, tilt=math.pi, width=10.0, pressure=1.0): 14 | assert 0.0 <= x <= X_MAX 15 | assert 0.0 <= y <= Y_MAX 16 | assert 0.0 <= tilt <= 2 * math.pi 17 | assert 0.0 <= pressure <= 1.0 18 | self.x, self.y = x, y 19 | self.speed, self.tilt, self.width, self.pressure = speed, tilt, width, pressure 20 | 21 | def __str__(self): 22 | return f"Segment: x={self.x:.2f}, y={self.y:.2f}, speed={self.speed:.2f}, tilt={self.tilt:.2f}, width={self.width:.2f}, pressure={self.pressure:.2f}" 23 | 24 | def dump(self): 25 | logger.debug(self) 26 | 27 | def to_bytes(self, buffer): 28 | buffer.write( 29 | pack( 30 | "ffffff", 31 | self.x, 32 | self.y, 33 | self.speed, 34 | self.tilt, 35 | self.width, 36 | self.pressure, 37 | ) 38 | ) 39 | 40 | @classmethod 41 | def from_bytes(cls, buffer): 42 | fmt = "ffffff" 43 | seg = cls(*unpack(fmt, buffer.read(calcsize(fmt)))) 44 | return seg 45 | 46 | def to_svg(self, buffer): 47 | buffer.write(f"{self.x},{self.y} ") 48 | -------------------------------------------------------------------------------- /rmlines/rmcloud.py: -------------------------------------------------------------------------------- 1 | from itertools import count 2 | from pathlib import Path 3 | from io import BytesIO 4 | from uuid import uuid4 5 | 6 | from rmapy.api import Client 7 | from rmapy.document import ZipDocument, RmPage 8 | 9 | 10 | def upload_rm_doc(name, rms): 11 | 12 | empty_jpg = Path(__file__).parent / "empty.jpg" 13 | empty_jpg_bytes = empty_jpg.read_bytes() 14 | 15 | rmapy = Client() 16 | if not rmapy.is_auth(): 17 | raise Exception("Not authenticated") 18 | rmapy.renew_token() 19 | 20 | rmps = [] 21 | for rm in rms: 22 | 23 | layer_counter = count(1) 24 | 25 | buffer = BytesIO() 26 | rm.to_bytes(buffer) 27 | buffer.seek(0) 28 | 29 | uuid = str(uuid4()) 30 | 31 | rmp = RmPage( 32 | buffer, 33 | metadata={ 34 | "layers": [ 35 | { 36 | "name": layer.name 37 | if layer.name 38 | else f"Layer {next(layer_counter)}" 39 | } 40 | for layer in rm.objects 41 | ] 42 | }, 43 | thumbnail=BytesIO(empty_jpg_bytes), 44 | order=uuid, 45 | ) 46 | 47 | rmps.append(rmp) 48 | 49 | zd = ZipDocument() 50 | zd.content["fileType"] = "notebook" 51 | zd.content["pages"] = [rmp.order for rmp in rmps] 52 | zd.content["pageCount"] = len(rmps) 53 | zd.metadata["VissibleName"] = name 54 | zd.pagedata = "\n".join(["Blank"] * len(rmps)) 55 | zd.rm.extend(rmps) 56 | 57 | rmapy.upload(zd) 58 | -------------------------------------------------------------------------------- /rmlines/rmobject/stroke.py: -------------------------------------------------------------------------------- 1 | from struct import pack, unpack, calcsize 2 | 3 | from ..constants import Pen, Colour, Width 4 | from .base import ByteableList 5 | from .segment import Segment 6 | 7 | 8 | class Stroke(ByteableList): 9 | __slots__ = ("pen", "colour", "width") 10 | 11 | @classmethod 12 | def child_type(cls): 13 | return Segment 14 | 15 | def __init__( 16 | self, 17 | pen: Pen = Pen.DEFAULT, 18 | colour: Colour = Colour.BLACK, 19 | width: Width = Width.MEDIUM, 20 | ): 21 | self.pen = pen 22 | self.colour = colour 23 | self.width = width 24 | super().__init__() 25 | 26 | def __str__(self): 27 | return f"Stroke: pen={self.pen.name}, colour={self.colour.name}, " 28 | f"width={self.width.name}, nobjs={len(self.objects)}" 29 | 30 | def to_bytes(self, buffer): 31 | buffer.write( 32 | pack("') 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Note: I am no longer using this project and I have archived it. Feel free to fork. 2 | 3 | # remarkable-layers 4 | 5 | Python module for reading and writing Remarkable Lines files (*.rm). 6 | 7 | Currently only supports version 5 of Remarkable Lines files. 8 | 9 | This module and supporting routines are experimental. 10 | 11 | ## Installation 12 | 13 | The module is still in development. 14 | 15 | You can install it with the following pip command. 16 | 17 | ```bash 18 | pip install git+https://github.com/bsdz/remarkable-layers.git#master 19 | ``` 20 | 21 | Alternatively, you can install it by cloning this repository and using the [poetry install](https://python-poetry.org/docs/cli/#install) command. 22 | 23 | 24 | 25 | ## Core Dependencies 26 | 27 | The core module for reading & writing rm line files only uses core python standard library. 28 | 29 | The SVG conversion module utilises numpy and lxml. 30 | 31 | The example scripts introduce other dependencies. 32 | 33 | ## Usage 34 | 35 | Read a RM Lines binary file. 36 | 37 | ```python 38 | from pathlib import Path 39 | from rmlines import RMLines 40 | 41 | p = Path("./samples/03f23a6e-c14b-4dba-836d-828707979356.rm") 42 | rm0 = RMLines.from_bytes(p.open("rb")) 43 | ``` 44 | 45 | Dump internal structure of RM Lines to logger. 46 | 47 | ```python 48 | import logging 49 | logging.basicConfig(level="DEBUG") 50 | # To reduce logging for some binary types, eg: 51 | # logging.getLogger('rmlines.rmobject.segment').setLevel(logging.INFO) 52 | 53 | rm0.dump() 54 | ``` 55 | 56 | Convert to SVG (as strokes). 57 | 58 | ```python 59 | from io import StringIO 60 | from IPython.display import SVG 61 | 62 | sbuffer = StringIO() 63 | rm0.to_svg(sbuffer) 64 | SVG(sbuffer.getvalue().encode()) 65 | ``` 66 | 67 | Convert simple SVG file into RM Lines format. Can only contain paths with simple line segments. For conversion of PDF to simple SVG see pdf_converter.py below. 68 | 69 | ```python 70 | p = Path("./my_simple.svg") 71 | rm0 = RMLines.from_svg(f.open("rb") 72 | ``` 73 | 74 | ## Example Scripts 75 | 76 | ### rmlines_pdf_converter 77 | 78 | This script converts a pdf to several intermediate SVG files, one per page, then generates RM Lines notebook that is uploaded to Remarkable Cloud. The SVG output files are simplified SVG that the RM Lines module can process. That is all beziers have been linearized and raster images traced into paths. This can be time consuming. 79 | 80 | Typical usage: 81 | 82 | ```bash 83 | rmlines_pdf_converter my_file.pdf --first 10 --last 25 84 | ``` 85 | 86 | Use "--help" flag for more options. 87 | 88 | #### Additional Dependencies 89 | 90 | Applications: inkscape and pdfinfo. 91 | Python modules: potrace, svgpathtools, svgwrite, pillow and rmapy. Note that in pyproject.toml some dependencies reference git branches / revisions and/or forks. 92 | 93 | ### pen_gallery 94 | 95 | This script uses hershey stroke fonts to place text in Remarkable lines files with different pen styles. More fonts are available at [SVG Fonts repo](ttps://gitlab.com/oskay/svg-fonts). 96 | 97 | ## Obtaining a cloud token for upload 98 | 99 | Currently, one should initialize their connection to Remarkable Cloud using rmapy directly. You can obtain a one time token from [Remarkable's Mobile Connect](https://my.remarkable.com/connect/mobile) page. 100 | 101 | Then follow example as [described here](https://rmapy.readthedocs.io/en/latest/quickstart.html#registering-the-api-client). Eg, 102 | 103 | ```python 104 | from rmapy.api import Client 105 | rmapy = Client() 106 | rmapy.register_device("") 107 | ``` 108 | You should only need to do this once. 109 | 110 | ## Known issues 111 | 112 | Conversion from RM to SVG is very basic and doesn't support the Remarkable pen tips that are stored as PNG images. 113 | 114 | Conversion from PDF to RM shows all text and objects as outlines. This is a bit annoying but a limitation of Remarkable's Lines format that doesn't support fills. I think the reason for Remarkable's decision to not support SVG directly is because they actually map PNG images over the stroke paths and this might be difficult/impossible to do with SVG. 115 | 116 | The round trip mapping, eg read RM to binary and convert back to again, does not support all pen configurations. 117 | 118 | This has only be tested on Linux and not Windows. 119 | 120 | This is a very experimental package created so I can properly take notes and test mathematical texts. I'm still not sure if it will solve my use case. 121 | 122 | ## Demo 123 | 124 | ![demo](pdf_to_rm_format.gif "Demo") 125 | 126 | 127 | -------------------------------------------------------------------------------- /rmlines/svg.py: -------------------------------------------------------------------------------- 1 | import re 2 | import math 3 | from pathlib import Path 4 | 5 | from lxml import etree as ET 6 | import numpy as np 7 | from numpy.linalg import multi_dot 8 | 9 | from .constants import Pen, Colour, Width 10 | from .rmobject.stroke import Stroke 11 | from .rmobject.segment import Segment 12 | from .rmobject.layer import Layer 13 | from .rmobject.lines import RMLines 14 | 15 | LOCAL_FONTS_DIR = Path(__file__).parents[1] / "fonts" 16 | 17 | 18 | def apply_transform(transform, points): 19 | points = np.c_[points, np.ones(points.shape[0])].T 20 | return np.dot(transform, points).T[:, :-1] 21 | 22 | 23 | def svg_path_d_to_points(d): 24 | points = [] 25 | res = re.split("([MLZHV])", d) 26 | for com, args in zip(res[1::2], res[2::2]): 27 | args = args.replace(",", " ").strip() 28 | if com in ["M", "L"]: 29 | point = [float(f) for f in args.split(" ")] 30 | elif com == "H": 31 | point = [float(args), point[1]] 32 | elif com == "V": 33 | point = [point[0], float(args)] 34 | elif com == "Z": 35 | point = points[0] 36 | else: 37 | raise RuntimeError(f"Unsupported SVG path command: {com}{args}") 38 | points.append(point) 39 | return np.array(points) 40 | 41 | 42 | def svg_path_to_strokes( 43 | path_d, 44 | pen=Pen.DEFAULT, 45 | colour=Colour.BLACK, 46 | width=Width.SMALL, 47 | segment_speed=100.0, 48 | segment_tilt=1 * math.pi - 0.5, 49 | segment_width=1.0, 50 | segment_pressure=1.0, 51 | transform=None, 52 | ): 53 | # split into separate strokes 54 | strokes = [] 55 | move_frags = [d for d in re.split("([M][^M]*)", path_d) if d] 56 | 57 | for d in move_frags: 58 | points = svg_path_d_to_points(d) 59 | if transform is not None: 60 | points = apply_transform(transform, points) 61 | stroke = Stroke(pen, colour, width) 62 | for x, y in points: 63 | stroke.append( 64 | Segment( 65 | x, 66 | y, 67 | speed=segment_speed, 68 | tilt=segment_tilt, 69 | width=segment_width, 70 | pressure=segment_pressure, 71 | ) 72 | ) 73 | strokes.append(stroke) 74 | return strokes 75 | 76 | 77 | def svg_to_rmlines(buffer): 78 | """Creates remarkable file from simple svg. 79 | 80 | The svg data cannot have groups or references 81 | and it must have all beziers converted into 82 | line segments. 83 | 84 | TODO: check above before processing. 85 | """ 86 | root = ET.fromstring(buffer.read()) 87 | 88 | ins = RMLines() 89 | layer = Layer() 90 | for path in root.findall("path", root.nsmap): 91 | layer.extend(svg_path_to_strokes(path.attrib["d"])) 92 | ins.append(layer) 93 | return ins 94 | 95 | 96 | class SVGStrokeFont: 97 | # encapsulate hershey text fonts 98 | # https://gitlab.com/oskay/svg-fonts 99 | def __init__( 100 | self, path=str((LOCAL_FONTS_DIR / "HersheySans1.svg")) 101 | ): 102 | tree = ET.parse(path) 103 | self.root = tree.getroot() 104 | font = self.root.find(".//font", self.root.nsmap) 105 | font_face = self.root.find(".//font-face", self.root.nsmap) 106 | self.default_horiz_adv_x = float(font.attrib["horiz-adv-x"]) 107 | self.units_per_em = float(font_face.attrib["units-per-em"]) 108 | 109 | def word_to_strokes( 110 | self, 111 | word, 112 | pos, 113 | height, 114 | pen=Pen.DEFAULT, 115 | colour=Colour.BLACK, 116 | width=Width.SMALL, 117 | segment_width=1., 118 | ): 119 | strokes = [] 120 | scale = height / self.units_per_em 121 | transforms = [ 122 | np.array([[1.0, 0, pos[0]], [0, 1.0, pos[1]], [0, 0, 1.0]]), # translate 123 | np.diag([1.0, -1.0, 1.0]), # reflect in x-axis 124 | np.diag([scale, scale, 1.0]), # scale 125 | ] 126 | transform = multi_dot(transforms) 127 | horiz_adv_x = 0.0 128 | for char in word: 129 | glyph = self.root.find(f".//glyph[@unicode='{char}']", self.root.nsmap) 130 | hadv = np.array([[1.0, 0, horiz_adv_x], [0, 1.0, 0.0], [0, 0, 1.0]]) 131 | if "d" in glyph.attrib: 132 | char_transform = np.dot( 133 | transform, hadv 134 | ) # advance (TODO: do this before char?) 135 | char_strokes = svg_path_to_strokes( 136 | glyph.attrib["d"], 137 | transform=char_transform, 138 | pen=pen, 139 | colour=colour, 140 | width=width, 141 | segment_width=segment_width, 142 | ) 143 | strokes.extend(char_strokes) 144 | horiz_adv_x += float(glyph.attrib["horiz-adv-x"]) 145 | return strokes 146 | -------------------------------------------------------------------------------- /scripts/pdf_converter.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | from pathlib import Path 3 | import logging 4 | from argparse import ArgumentParser 5 | from io import BytesIO 6 | import base64 7 | from copy import copy 8 | 9 | from lxml import etree as ET 10 | from svgpathtools import parse_path, Path as SVGPath, Line, CubicBezier 11 | from svgpathtools.parser import parse_transform 12 | from svgpathtools.path import transform 13 | import numpy as np 14 | from PIL import Image 15 | import svgwrite 16 | import potrace 17 | 18 | from rmlines import RMLines, Layer, Stroke, Segment, Colour, Pen, Width, X_MAX, Y_MAX 19 | from rmlines.svg import apply_transform 20 | from rmlines.rmcloud import upload_rm_doc 21 | 22 | logger = logging.getLogger(__name__) 23 | 24 | XML_PARSER = ET.XMLParser(huge_tree=True) 25 | 26 | 27 | def pdf_info(filename): 28 | run = subprocess.run(["pdfinfo"] + [filename], capture_output=True,) 29 | return { 30 | line[0 : line.find(":")].strip(): line[line.find(":") + 1 :].strip() 31 | for line in run.stdout.decode("utf8").splitlines() 32 | } 33 | 34 | 35 | def run_inkscape(filename, args=[], actions=[]): 36 | run = subprocess.run( 37 | ["inkscape"] 38 | + args 39 | + (["--actions=%s" % "; ".join(actions)] if actions else []) 40 | + [filename], 41 | capture_output=True, 42 | ) 43 | logger.info(run.stderr.decode("ascii")) 44 | 45 | 46 | def resize_doc(stage_svg): 47 | # resize to remarkable size 48 | root = ET.fromstring(stage_svg.read_bytes(), parser=XML_PARSER) 49 | x_min, y_min, x_max, y_max = [float(s) for s in root.attrib["viewBox"].split(" ")] 50 | 51 | X_MAX, Y_MAX = 1404.0, 1872.0 52 | svg_ratio = x_max / y_max 53 | rm_ratio = X_MAX / Y_MAX 54 | if svg_ratio > rm_ratio: 55 | # fit width 56 | factor = X_MAX / x_max 57 | else: 58 | # fit height 59 | factor = Y_MAX / y_max 60 | 61 | x_max_new, y_max_new = factor * x_max, factor * y_max 62 | 63 | # appending node to another group moves it (lxml) 64 | group = ET.Element("g", transform=f"scale({factor:0.2f})") 65 | for child in root: 66 | group.append(child) 67 | root.append(group) 68 | 69 | root.attrib["width"] = f"{x_max_new:.2f}pt" 70 | root.attrib["height"] = f"{y_max_new:.2f}pt" 71 | root.attrib["viewBox"] = f"0 0 {x_max_new:.2f} {y_max_new:.2f}" 72 | 73 | stage_svg.write_bytes(ET.tostring(root)) 74 | 75 | 76 | def trace_image(data, transform): 77 | im1 = Image.open(BytesIO(base64.b64decode(data))) 78 | 79 | # convert to b&w 80 | im2 = im1.convert("L").point(lambda x: 255 if x > 254 else 0, mode="1") 81 | 82 | bmp = potrace.Bitmap(np.array(im2)) 83 | path = bmp.trace() 84 | 85 | dwg = svgwrite.Drawing(viewBox=(f"0 0 {im1.width} {im1.height}")) 86 | 87 | # TODO: as multiple svg paths? along these lines.. 88 | # for curve in path: 89 | # elements = ["M", *curve.start_point] 90 | # for p in curve.tesselate(): 91 | # elements.extend(["L", *p]) 92 | # dwg.add(dwg.path(d=elements, stroke='black', stroke_width='1', fill='white')) 93 | 94 | # as single path 95 | elements = [] 96 | for curve in path: 97 | elements.extend( 98 | ["M", *apply_transform(transform, np.array([curve.start_point]))[0]] 99 | ) 100 | for p in apply_transform(transform, curve.tesselate()): 101 | elements.extend(["L", *p]) 102 | if elements: 103 | return dwg.path( 104 | d=elements, stroke="black", stroke_width="1", fill="white" 105 | ).tostring() 106 | 107 | 108 | def prepare_images(stage_svg): 109 | """Traces bitmaps or removes them.""" 110 | root = ET.fromstring(stage_svg.read_bytes(), parser=XML_PARSER) 111 | defs = root.find(".//defs", root.nsmap) 112 | 113 | # remove masks (appear to be redundant) 114 | for mask in defs.findall(".//mask", root.nsmap): 115 | mask_image_id = mask[0].attrib["{http://www.w3.org/1999/xlink}href"] 116 | mask_image_id = mask_image_id.replace("#", "") 117 | defs.remove(mask) 118 | mask_image = defs.find(f".//image[@id='{mask_image_id}']", root.nsmap) 119 | defs.remove(mask_image) 120 | 121 | # move images from defs into use (assume no duplicates) 122 | for image in defs.findall(".//image", root.nsmap): 123 | use = root.find(f".//use[@xlink:href='#{image.attrib['id']}']", root.nsmap) 124 | image_data = image.attrib["{http://www.w3.org/1999/xlink}href"] 125 | defs.remove(image) 126 | # image data should be base64 incoded image and start with 127 | # "data:image/(png|jpeg|..);base64,..." so take everything 128 | # after first comma. 129 | char_start = image_data.find(",") + 1 130 | image_data = image_data[char_start:] 131 | trans_matrix = parse_transform(use.attrib["transform"]) 132 | traced_image_path = trace_image(image_data, trans_matrix) 133 | if traced_image_path: 134 | svg_path = ET.XML(traced_image_path) 135 | use.getparent().replace(use, svg_path) 136 | else: 137 | # TODO: failed to trace, delete instead 138 | use.getparent().remove(use) 139 | 140 | # TODO: replace clipped paths with rectangles for now 141 | for clip_path in defs.findall("./clipPath", root.nsmap): 142 | clip_path_id = clip_path.attrib["id"] 143 | clipping_path = clip_path.find("./path", root.nsmap) 144 | for g in root.findall(f".//g[@clip-path='url(#{clip_path_id})']", root.nsmap): 145 | g.getparent().replace(g, copy(clipping_path)) 146 | defs.remove(clip_path) 147 | 148 | stage_svg.write_bytes(ET.tostring(root)) 149 | 150 | 151 | CUBIC_TO_POLY = np.array( 152 | [ 153 | [-1, 3, -3, 1], # transforms cubic bez to standard poly 154 | [3, -6, 3, 0], 155 | [-3, 3, 0, 0], 156 | [1, 0, 0, 0], 157 | ] 158 | ) 159 | 160 | CUBIC_SAMPLE_SPACE = np.linspace(0, 1, 3) 161 | 162 | CUBIC_TO_POLY_SAMPLE = np.dot( 163 | CUBIC_TO_POLY, np.power(CUBIC_SAMPLE_SPACE, [[3], [2], [1], [0]]) 164 | ) 165 | 166 | 167 | def flatten_beziers(svg_d): 168 | sp = parse_path(svg_d) 169 | spn = SVGPath() 170 | 171 | for seg in sp: 172 | if isinstance(seg, Line): 173 | spn.append(seg) 174 | elif isinstance(seg, CubicBezier): 175 | B = [seg.bpoints()] 176 | foo = np.dot(B, CUBIC_TO_POLY_SAMPLE) 177 | spn.extend([Line(x, y) for x, y in zip(foo[0, :-1], foo[0, 1:])]) 178 | else: 179 | raise RuntimeError(f"unsupported {seg}") 180 | 181 | return spn.d() 182 | 183 | 184 | def transform_to_line_segments(stage_svg): 185 | """Converts bezier curves into straight line segments.""" 186 | root = ET.fromstring(stage_svg.read_bytes(), parser=XML_PARSER) 187 | x_min, y_min, x_max, y_max = map(float, root.attrib["viewBox"].split(" ")) 188 | for path in root.findall(".//path", root.nsmap): 189 | if "d" in path.attrib and path.attrib["d"]: 190 | path.attrib["d"] = flatten_beziers(path.attrib["d"]) 191 | 192 | stage_svg.write_bytes(ET.tostring(root)) 193 | 194 | 195 | def transform_paths(stage_svg): 196 | """Inkscapes deep ungroup doesn't handle paths with inline matrix 197 | transforms well. Transform them here instead""" 198 | root = ET.fromstring(stage_svg.read_bytes(), parser=XML_PARSER) 199 | for path in root.findall(".//path[@transform]", root.nsmap): 200 | trans_matrix = parse_transform(path.attrib.pop("transform")) 201 | svg_path = parse_path(path.attrib["d"]) 202 | path.attrib["d"] = transform(svg_path, trans_matrix).d() 203 | 204 | stage_svg.write_bytes(ET.tostring(root)) 205 | 206 | 207 | def svgpathtools_flatten(stage_svg): 208 | # TODO: perhaps use this instead of inkscapes's deep ungroup? 209 | from svgpathtools import Document 210 | 211 | doc = Document(str(stage_svg)) 212 | results = doc.flatten_all_paths() 213 | for result in results: 214 | # TODO: save result.path to new SVG document 215 | # and overwrite stage_svg? 216 | pass 217 | 218 | 219 | def remove_groups(stage_svg): 220 | """Some empty groups left behind. Clean them up.""" 221 | # TODO: assert groups are actually empty 222 | root = ET.fromstring(stage_svg.read_bytes(), parser=XML_PARSER) 223 | for g in root.findall("g", root.nsmap): 224 | path = g.find("path", root.nsmap) 225 | root.insert(0, path) 226 | root.remove(g) 227 | stage_svg.write_bytes(ET.tostring(root)) 228 | 229 | 230 | def extract_svg_page(orig_pdf, page_no, out_dir, overwrite): 231 | # NOTE: stage_svg is passed by reference and 232 | # mutated within calling functions. 233 | # 234 | stage_svg = out_dir / f"page_{page_no}.svg" 235 | 236 | if not overwrite and stage_svg.exists(): 237 | return 238 | 239 | logger.info("Extracting to SVG page %s", page_no) 240 | 241 | # use inkscape to load in page using popplet/cairo import 242 | # TODO: use pdftocairo directly? 243 | # 244 | run_inkscape( 245 | orig_pdf, 246 | ["--pdf-poppler", f"--pdf-page={page_no}", f"--export-filename={stage_svg}"], 247 | ) 248 | 249 | # capture document size and resize to width or height accordingly 250 | # 251 | resize_doc(stage_svg) 252 | 253 | # flatten all cubic bezier paths into lines 254 | # 255 | transform_to_line_segments(stage_svg) 256 | 257 | # remove image masks and prepare for bitmap tracing 258 | # 259 | prepare_images(stage_svg) 260 | 261 | # transform paths with in line matrix transforms 262 | # (inkscape ungroup doesn't work well with these) 263 | # 264 | transform_paths(stage_svg) 265 | 266 | # inkscape tasks 267 | # TODO: optimize by rewriting as pure xml transforms 268 | # TODO: potentially use svgpathtools new flatten_all_paths() function? 269 | # * un group everything (extensions / arrange / deep ungroup) 270 | # * select all (path / objects to path) 271 | # * file / clean up document 272 | # * apply path (extension / modify path / apply transform) - https://stackoverflow.com/questions/13329125/removing-transforms-in-svg-files 273 | # * save as plain SVG 274 | # 275 | run_inkscape( 276 | str(stage_svg), 277 | ["--with-gui", "--batch-process"], 278 | [ 279 | "mcepl.ungroup_deep.noprefs", 280 | "select-all", 281 | "object-to-path", 282 | "select-clear", 283 | "vacuum-defs", 284 | "com.klowner.filter.applytransform.noprefs", 285 | "FileSave", 286 | "file-close", 287 | ], 288 | ) 289 | 290 | # remove remaining unecessary groups 291 | # 292 | remove_groups(stage_svg) 293 | 294 | 295 | def generate_template_layers(vertical_lines=True, horizontal_lines=True): 296 | """Creates two layers for note taking with optional grid lines.""" 297 | 298 | layers = [] 299 | for name, x_min, y_min, x_max, y_max in [ 300 | ("Top Notes", 0, 0, X_MAX, Y_MAX / 2), 301 | ("Bot Notes", 0, Y_MAX / 2, X_MAX, Y_MAX), 302 | ]: 303 | layer = Layer(name) 304 | 305 | # background 306 | for y in range(int(y_min), int(y_max), 10): 307 | st = Stroke(Pen.MARKER_2, Colour.WHITE, Width.LARGE) 308 | st.extend([Segment(x_min, y, width=20), Segment(x_max, y, width=20)]) 309 | layer.append(st) 310 | 311 | # horiz lines 312 | if horizontal_lines: 313 | for y in range(int(y_min), int(y_max), 50): 314 | st = Stroke(Pen.FINELINER_2, Colour.GREY, Width.SMALL) 315 | st.extend([Segment(x_min, y, width=1), Segment(x_max, y, width=1)]) 316 | layer.append(st) 317 | 318 | # vert lines 319 | if vertical_lines: 320 | for x in range(int(x_min), int(x_max), 50): 321 | st = Stroke(Pen.FINELINER_2, Colour.GREY, Width.SMALL) 322 | st.extend([Segment(x, y_min, width=1), Segment(x, y_max, width=1)]) 323 | layer.append(st) 324 | 325 | layers.append(layer) 326 | 327 | return layers 328 | 329 | 330 | def generate_rmlines_and_upload(in_dir, exclude_grid_layers=False): 331 | name = f"{in_dir.name}_rm" 332 | 333 | base_layers = [] if exclude_grid_layers else generate_template_layers() 334 | 335 | def path_to_page_no(p): 336 | return int(p.with_suffix("").name.split("_")[1]) 337 | 338 | rms = [] 339 | for f in sorted(in_dir.glob("*.svg"), key=path_to_page_no): 340 | logger.info("Creating Rm Lines file for %s", f) 341 | rm = RMLines.from_svg(f.open("rb")) 342 | rm.objects[0].name = "Doc" 343 | rm.objects = base_layers + rm.objects 344 | 345 | rms.append(rm) 346 | 347 | logger.info("Uploading to Remarkable cloud as '%s'", name) 348 | upload_rm_doc(name, rms) 349 | 350 | 351 | def main(): 352 | logging.basicConfig(level="INFO") 353 | 354 | parser = ArgumentParser("Convert PDF into simple SVG.") 355 | parser.add_argument( 356 | "pdf_input", metavar="pdf_path", type=str, help="Path to file for conversion" 357 | ) 358 | parser.add_argument( 359 | "--first", dest="first", type=int, default=1, help="Start from this page number" 360 | ) 361 | parser.add_argument( 362 | "--last", dest="last", type=int, default=None, help="Finish on this page number" 363 | ) 364 | parser.add_argument( 365 | "--overwrite-svg", 366 | dest="overwrite_svg", 367 | type=bool, 368 | default=False, 369 | help="Write over intermediate SVG files", 370 | ) 371 | parser.add_argument( 372 | "--exclude-grid-layers", 373 | dest="exclude_grid_layers", 374 | type=bool, 375 | default=False, 376 | help="Exclude note taking grid layers", 377 | ) 378 | parser.add_argument( 379 | "--upload", 380 | dest="upload", 381 | type=bool, 382 | default=True, 383 | help="Upload document to Remarkable Cloud", 384 | ) 385 | args = parser.parse_args() 386 | 387 | pdf_input = Path(args.pdf_input) 388 | if not pdf_input.exists(): 389 | raise RuntimeError(f"PDF file '{pdf_input}' does not exist") 390 | out_dir = Path(pdf_input).with_suffix("") 391 | out_dir.mkdir(parents=True, exist_ok=True) 392 | 393 | logger.info("Processing PDF file '%s'", pdf_input) 394 | 395 | if not args.last: 396 | info = pdf_info(pdf_input) 397 | args.last = int(info["Pages"]) 398 | 399 | for pn in range(args.first, args.last + 1): 400 | extract_svg_page( 401 | orig_pdf=pdf_input, 402 | page_no=pn, 403 | out_dir=out_dir, 404 | overwrite=args.overwrite_svg, 405 | ) 406 | 407 | if args.upload: 408 | generate_rmlines_and_upload( 409 | out_dir, exclude_grid_layers=args.exclude_grid_layers 410 | ) 411 | 412 | 413 | if __name__ == "__main__": 414 | main() 415 | -------------------------------------------------------------------------------- /fonts/HersheySans1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Font name: Hershey Sans 1-stroke 8 | 9 | Originally prepared in 2011 and converted to SVG fonts 10 | in 2019 by Windell H. Oskay, www.evilmadscientist.com 11 | 12 | Contents adapted from emergent.unpythonic.net/software/hershey 13 | by way of "Hershey Fonts in SVG" by Marty McGuire 14 | http://www.thingiverse.com/thing:6168 15 | 16 | ------------------------------------------------------------------- 17 | The Hershey Fonts are a set of vector fonts with a liberal license. 18 | 19 | USE RESTRICTION: 20 | This distribution of the Hershey Fonts may be used by anyone for 21 | any purpose, commercial or otherwise, providing that: 22 | 1. The following acknowledgements must be distributed with 23 | the font data: 24 | - The Hershey Fonts were originally created by Dr. 25 | A. V. Hershey while working at the U. S. 26 | National Bureau of Standards. 27 | - The format of the Font data in this distribution 28 | was originally created by 29 | James Hurt 30 | Cognition, Inc. 31 | 900 Technology Park Drive 32 | Billerica, MA 01821 33 | (mit-eddie!ci-dandelion!hurt) 34 | 2. The font data in this distribution may be converted into 35 | any other format *EXCEPT* the format distributed by 36 | the U.S. NTIS where each point is described 37 | in eight bytes as "xxx yyy:", where xxx and yyy are 38 | the coordinate values as ASCII numbers. 39 | 40 | 41 | 42 | 43 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "appdirs" 3 | version = "1.4.4" 4 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 5 | category = "dev" 6 | optional = false 7 | python-versions = "*" 8 | 9 | [[package]] 10 | name = "appnope" 11 | version = "0.1.0" 12 | description = "Disable App Nap on OS X 10.9" 13 | category = "dev" 14 | optional = false 15 | python-versions = "*" 16 | 17 | [[package]] 18 | name = "attrs" 19 | version = "19.3.0" 20 | description = "Classes Without Boilerplate" 21 | category = "dev" 22 | optional = false 23 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 24 | 25 | [package.extras] 26 | azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] 27 | dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"] 28 | docs = ["sphinx", "zope.interface"] 29 | tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] 30 | 31 | [[package]] 32 | name = "backcall" 33 | version = "0.2.0" 34 | description = "Specifications for callback functions passed in to an API" 35 | category = "dev" 36 | optional = false 37 | python-versions = "*" 38 | 39 | [[package]] 40 | name = "black" 41 | version = "19.10b0" 42 | description = "The uncompromising code formatter." 43 | category = "dev" 44 | optional = false 45 | python-versions = ">=3.6" 46 | 47 | [package.dependencies] 48 | appdirs = "*" 49 | attrs = ">=18.1.0" 50 | click = ">=6.5" 51 | pathspec = ">=0.6,<1" 52 | regex = "*" 53 | toml = ">=0.9.4" 54 | typed-ast = ">=1.4.0" 55 | 56 | [package.extras] 57 | d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] 58 | 59 | [[package]] 60 | name = "certifi" 61 | version = "2020.6.20" 62 | description = "Python package for providing Mozilla's CA Bundle." 63 | category = "main" 64 | optional = false 65 | python-versions = "*" 66 | 67 | [[package]] 68 | name = "chardet" 69 | version = "3.0.4" 70 | description = "Universal encoding detector for Python 2 and 3" 71 | category = "main" 72 | optional = false 73 | python-versions = "*" 74 | 75 | [[package]] 76 | name = "click" 77 | version = "7.1.2" 78 | description = "Composable command line interface toolkit" 79 | category = "dev" 80 | optional = false 81 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 82 | 83 | [[package]] 84 | name = "colorama" 85 | version = "0.4.3" 86 | description = "Cross-platform colored terminal text." 87 | category = "dev" 88 | optional = false 89 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 90 | 91 | [[package]] 92 | name = "decorator" 93 | version = "4.4.2" 94 | description = "Decorators for Humans" 95 | category = "dev" 96 | optional = false 97 | python-versions = ">=2.6, !=3.0.*, !=3.1.*" 98 | 99 | [[package]] 100 | name = "flake8" 101 | version = "3.8.3" 102 | description = "the modular source code checker: pep8 pyflakes and co" 103 | category = "dev" 104 | optional = false 105 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" 106 | 107 | [package.dependencies] 108 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 109 | mccabe = ">=0.6.0,<0.7.0" 110 | pycodestyle = ">=2.6.0a1,<2.7.0" 111 | pyflakes = ">=2.2.0,<2.3.0" 112 | 113 | [[package]] 114 | name = "idna" 115 | version = "2.9" 116 | description = "Internationalized Domain Names in Applications (IDNA)" 117 | category = "main" 118 | optional = false 119 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 120 | 121 | [[package]] 122 | name = "importlib-metadata" 123 | version = "1.6.1" 124 | description = "Read metadata from Python packages" 125 | category = "dev" 126 | optional = false 127 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 128 | 129 | [package.dependencies] 130 | zipp = ">=0.5" 131 | 132 | [package.extras] 133 | docs = ["sphinx", "rst.linker"] 134 | testing = ["packaging", "pep517", "importlib-resources (>=1.3)"] 135 | 136 | [[package]] 137 | name = "ipykernel" 138 | version = "5.3.0" 139 | description = "IPython Kernel for Jupyter" 140 | category = "dev" 141 | optional = false 142 | python-versions = ">=3.5" 143 | 144 | [package.dependencies] 145 | appnope = {version = "*", markers = "platform_system == \"Darwin\""} 146 | ipython = ">=5.0.0" 147 | jupyter-client = "*" 148 | tornado = ">=4.2" 149 | traitlets = ">=4.1.0" 150 | 151 | [package.extras] 152 | test = ["pytest (!=5.3.4)", "pytest-cov", "flaky", "nose"] 153 | 154 | [[package]] 155 | name = "ipython" 156 | version = "7.15.0" 157 | description = "IPython: Productive Interactive Computing" 158 | category = "dev" 159 | optional = false 160 | python-versions = ">=3.6" 161 | 162 | [package.dependencies] 163 | appnope = {version = "*", markers = "sys_platform == \"darwin\""} 164 | backcall = "*" 165 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 166 | decorator = "*" 167 | jedi = ">=0.10" 168 | pexpect = {version = "*", markers = "sys_platform != \"win32\""} 169 | pickleshare = "*" 170 | prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" 171 | pygments = "*" 172 | traitlets = ">=4.2" 173 | 174 | [package.extras] 175 | all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.14)", "pygments", "qtconsole", "requests", "testpath"] 176 | doc = ["Sphinx (>=1.3)"] 177 | kernel = ["ipykernel"] 178 | nbconvert = ["nbconvert"] 179 | nbformat = ["nbformat"] 180 | notebook = ["notebook", "ipywidgets"] 181 | parallel = ["ipyparallel"] 182 | qtconsole = ["qtconsole"] 183 | test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.14)"] 184 | 185 | [[package]] 186 | name = "ipython-genutils" 187 | version = "0.2.0" 188 | description = "Vestigial utilities from IPython" 189 | category = "dev" 190 | optional = false 191 | python-versions = "*" 192 | 193 | [[package]] 194 | name = "jedi" 195 | version = "0.17.1" 196 | description = "An autocompletion tool for Python that can be used for text editors." 197 | category = "dev" 198 | optional = false 199 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 200 | 201 | [package.dependencies] 202 | parso = ">=0.7.0,<0.8.0" 203 | 204 | [package.extras] 205 | qa = ["flake8 (==3.7.9)"] 206 | testing = ["Django (<3.1)", "colorama", "docopt", "pytest (>=3.9.0,<5.0.0)"] 207 | 208 | [[package]] 209 | name = "jupyter-client" 210 | version = "6.1.3" 211 | description = "Jupyter protocol implementation and client libraries" 212 | category = "dev" 213 | optional = false 214 | python-versions = ">=3.5" 215 | 216 | [package.dependencies] 217 | jupyter-core = ">=4.6.0" 218 | python-dateutil = ">=2.1" 219 | pyzmq = ">=13" 220 | tornado = ">=4.1" 221 | traitlets = "*" 222 | 223 | [package.extras] 224 | test = ["ipykernel", "ipython", "mock", "pytest"] 225 | 226 | [[package]] 227 | name = "jupyter-core" 228 | version = "4.6.3" 229 | description = "Jupyter core package. A base package on which Jupyter projects rely." 230 | category = "dev" 231 | optional = false 232 | python-versions = "!=3.0,!=3.1,!=3.2,!=3.3,!=3.4,>=2.7" 233 | 234 | [package.dependencies] 235 | pywin32 = {version = ">=1.0", markers = "sys_platform == \"win32\""} 236 | traitlets = "*" 237 | 238 | [[package]] 239 | name = "lxml" 240 | version = "4.5.1" 241 | description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." 242 | category = "main" 243 | optional = false 244 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" 245 | 246 | [package.extras] 247 | cssselect = ["cssselect (>=0.7)"] 248 | html5 = ["html5lib"] 249 | htmlsoup = ["beautifulsoup4"] 250 | source = ["Cython (>=0.29.7)"] 251 | 252 | [[package]] 253 | name = "mccabe" 254 | version = "0.6.1" 255 | description = "McCabe checker, plugin for flake8" 256 | category = "dev" 257 | optional = false 258 | python-versions = "*" 259 | 260 | [[package]] 261 | name = "numpy" 262 | version = "1.19.4" 263 | description = "NumPy is the fundamental package for array computing with Python." 264 | category = "main" 265 | optional = false 266 | python-versions = ">=3.6" 267 | 268 | [[package]] 269 | name = "parso" 270 | version = "0.7.0" 271 | description = "A Python Parser" 272 | category = "dev" 273 | optional = false 274 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 275 | 276 | [package.extras] 277 | testing = ["docopt", "pytest (>=3.0.7)"] 278 | 279 | [[package]] 280 | name = "pathspec" 281 | version = "0.8.0" 282 | description = "Utility library for gitignore style pattern matching of file paths." 283 | category = "dev" 284 | optional = false 285 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 286 | 287 | [[package]] 288 | name = "pexpect" 289 | version = "4.8.0" 290 | description = "Pexpect allows easy control of interactive console applications." 291 | category = "dev" 292 | optional = false 293 | python-versions = "*" 294 | 295 | [package.dependencies] 296 | ptyprocess = ">=0.5" 297 | 298 | [[package]] 299 | name = "pickleshare" 300 | version = "0.7.5" 301 | description = "Tiny 'shelve'-like database with concurrency support" 302 | category = "dev" 303 | optional = false 304 | python-versions = "*" 305 | 306 | [[package]] 307 | name = "pillow" 308 | version = "8.0.1" 309 | description = "Python Imaging Library (Fork)" 310 | category = "main" 311 | optional = false 312 | python-versions = ">=3.6" 313 | 314 | [[package]] 315 | name = "potrace" 316 | version = "0.2" 317 | description = "potrace Python bindings" 318 | category = "main" 319 | optional = false 320 | python-versions = "^3.7" 321 | develop = false 322 | 323 | [package.dependencies] 324 | numpy = "^1.19.4" 325 | Pillow = "^8.0.1" 326 | 327 | [package.source] 328 | type = "git" 329 | url = "https://github.com/bsdz/pypotrace.git" 330 | reference = "master" 331 | resolved_reference = "3c86def6b19b2e247c5a6e7f152a9fa662dd6ff5" 332 | 333 | [[package]] 334 | name = "prompt-toolkit" 335 | version = "3.0.5" 336 | description = "Library for building powerful interactive command lines in Python" 337 | category = "dev" 338 | optional = false 339 | python-versions = ">=3.6.1" 340 | 341 | [package.dependencies] 342 | wcwidth = "*" 343 | 344 | [[package]] 345 | name = "ptyprocess" 346 | version = "0.6.0" 347 | description = "Run a subprocess in a pseudo terminal" 348 | category = "dev" 349 | optional = false 350 | python-versions = "*" 351 | 352 | [[package]] 353 | name = "pyaml" 354 | version = "19.4.1" 355 | description = "PyYAML-based module to produce pretty and readable YAML-serialized data" 356 | category = "main" 357 | optional = false 358 | python-versions = "*" 359 | 360 | [package.dependencies] 361 | PyYAML = "*" 362 | 363 | [[package]] 364 | name = "pycodestyle" 365 | version = "2.6.0" 366 | description = "Python style guide checker" 367 | category = "dev" 368 | optional = false 369 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 370 | 371 | [[package]] 372 | name = "pyflakes" 373 | version = "2.2.0" 374 | description = "passive checker of Python programs" 375 | category = "dev" 376 | optional = false 377 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 378 | 379 | [[package]] 380 | name = "pygments" 381 | version = "2.6.1" 382 | description = "Pygments is a syntax highlighting package written in Python." 383 | category = "dev" 384 | optional = false 385 | python-versions = ">=3.5" 386 | 387 | [[package]] 388 | name = "python-dateutil" 389 | version = "2.8.1" 390 | description = "Extensions to the standard Python datetime module" 391 | category = "dev" 392 | optional = false 393 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 394 | 395 | [package.dependencies] 396 | six = ">=1.5" 397 | 398 | [[package]] 399 | name = "pywin32" 400 | version = "228" 401 | description = "Python for Window Extensions" 402 | category = "dev" 403 | optional = false 404 | python-versions = "*" 405 | 406 | [[package]] 407 | name = "pyyaml" 408 | version = "5.3.1" 409 | description = "YAML parser and emitter for Python" 410 | category = "main" 411 | optional = false 412 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 413 | 414 | [[package]] 415 | name = "pyzmq" 416 | version = "19.0.1" 417 | description = "Python bindings for 0MQ" 418 | category = "dev" 419 | optional = false 420 | python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*" 421 | 422 | [[package]] 423 | name = "regex" 424 | version = "2020.6.8" 425 | description = "Alternative regular expression module, to replace re." 426 | category = "dev" 427 | optional = false 428 | python-versions = "*" 429 | 430 | [[package]] 431 | name = "requests" 432 | version = "2.24.0" 433 | description = "Python HTTP for Humans." 434 | category = "main" 435 | optional = false 436 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 437 | 438 | [package.dependencies] 439 | certifi = ">=2017.4.17" 440 | chardet = ">=3.0.2,<4" 441 | idna = ">=2.5,<3" 442 | urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" 443 | 444 | [package.extras] 445 | security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] 446 | socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] 447 | 448 | [[package]] 449 | name = "rmapy" 450 | version = "0.2.2" 451 | description = "" 452 | category = "main" 453 | optional = false 454 | python-versions = ">=3.6, <4" 455 | develop = false 456 | 457 | [package.dependencies] 458 | pyaml = "19.4.1" 459 | requests = "*" 460 | 461 | [package.extras] 462 | doc = ["sphinx (==2.2.0)", "sphinx-autodoc-typehints (==1.8.0)", "guzzle-sphinx-theme (==0.7.11)"] 463 | 464 | [package.source] 465 | type = "git" 466 | url = "https://github.com/bsdz/rmapy.git" 467 | reference = "master" 468 | resolved_reference = "d53983e68021402f6b1dc8d736633a9c53f903b2" 469 | 470 | [[package]] 471 | name = "rope" 472 | version = "0.17.0" 473 | description = "a python refactoring library..." 474 | category = "dev" 475 | optional = false 476 | python-versions = "*" 477 | 478 | [package.extras] 479 | dev = ["pytest"] 480 | 481 | [[package]] 482 | name = "six" 483 | version = "1.15.0" 484 | description = "Python 2 and 3 compatibility utilities" 485 | category = "dev" 486 | optional = false 487 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 488 | 489 | [[package]] 490 | name = "snakeviz" 491 | version = "2.1.0" 492 | description = "A web-based viewer for Python profiler output" 493 | category = "dev" 494 | optional = false 495 | python-versions = "*" 496 | 497 | [package.dependencies] 498 | tornado = ">=2.0" 499 | 500 | [[package]] 501 | name = "svgpathtools" 502 | version = "1.3.3" 503 | description = "A collection of tools for manipulating and analyzing SVG Path objects and Bezier curves." 504 | category = "main" 505 | optional = false 506 | python-versions = "*" 507 | develop = true 508 | 509 | [package.dependencies] 510 | numpy = "*" 511 | svgwrite = "*" 512 | 513 | [package.source] 514 | type = "git" 515 | url = "https://github.com/mathandy/svgpathtools.git" 516 | reference = "f99f9d6bb3519ecdcb7b167f75a09178595cf89c" 517 | 518 | [[package]] 519 | name = "svgwrite" 520 | version = "1.4" 521 | description = "A Python library to create SVG drawings." 522 | category = "main" 523 | optional = false 524 | python-versions = ">=3.6" 525 | 526 | [[package]] 527 | name = "toml" 528 | version = "0.10.1" 529 | description = "Python Library for Tom's Obvious, Minimal Language" 530 | category = "dev" 531 | optional = false 532 | python-versions = "*" 533 | 534 | [[package]] 535 | name = "tornado" 536 | version = "6.0.4" 537 | description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." 538 | category = "dev" 539 | optional = false 540 | python-versions = ">= 3.5" 541 | 542 | [[package]] 543 | name = "traitlets" 544 | version = "4.3.3" 545 | description = "Traitlets Python config system" 546 | category = "dev" 547 | optional = false 548 | python-versions = "*" 549 | 550 | [package.dependencies] 551 | decorator = "*" 552 | ipython-genutils = "*" 553 | six = "*" 554 | 555 | [package.extras] 556 | test = ["pytest", "mock"] 557 | 558 | [[package]] 559 | name = "typed-ast" 560 | version = "1.4.1" 561 | description = "a fork of Python 2 and 3 ast modules with type comment support" 562 | category = "dev" 563 | optional = false 564 | python-versions = "*" 565 | 566 | [[package]] 567 | name = "urllib3" 568 | version = "1.25.9" 569 | description = "HTTP library with thread-safe connection pooling, file post, and more." 570 | category = "main" 571 | optional = false 572 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 573 | 574 | [package.extras] 575 | brotli = ["brotlipy (>=0.6.0)"] 576 | secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"] 577 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] 578 | 579 | [[package]] 580 | name = "wcwidth" 581 | version = "0.2.4" 582 | description = "Measures the displayed width of unicode strings in a terminal" 583 | category = "dev" 584 | optional = false 585 | python-versions = "*" 586 | 587 | [[package]] 588 | name = "zipp" 589 | version = "3.1.0" 590 | description = "Backport of pathlib-compatible object wrapper for zip files" 591 | category = "dev" 592 | optional = false 593 | python-versions = ">=3.6" 594 | 595 | [package.extras] 596 | docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] 597 | testing = ["jaraco.itertools", "func-timeout"] 598 | 599 | [metadata] 600 | lock-version = "1.1" 601 | python-versions = "^3.7" 602 | content-hash = "36b14c732f6603d6d8a935d54cce673557d48afd3c4c2d148eca04ad0901efd4" 603 | 604 | [metadata.files] 605 | appdirs = [ 606 | {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, 607 | {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, 608 | ] 609 | appnope = [ 610 | {file = "appnope-0.1.0-py2.py3-none-any.whl", hash = "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0"}, 611 | {file = "appnope-0.1.0.tar.gz", hash = "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"}, 612 | ] 613 | attrs = [ 614 | {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, 615 | {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, 616 | ] 617 | backcall = [ 618 | {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, 619 | {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, 620 | ] 621 | black = [ 622 | {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"}, 623 | {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"}, 624 | ] 625 | certifi = [ 626 | {file = "certifi-2020.6.20-py2.py3-none-any.whl", hash = "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"}, 627 | {file = "certifi-2020.6.20.tar.gz", hash = "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"}, 628 | ] 629 | chardet = [ 630 | {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, 631 | {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, 632 | ] 633 | click = [ 634 | {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, 635 | {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, 636 | ] 637 | colorama = [ 638 | {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, 639 | {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, 640 | ] 641 | decorator = [ 642 | {file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"}, 643 | {file = "decorator-4.4.2.tar.gz", hash = "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"}, 644 | ] 645 | flake8 = [ 646 | {file = "flake8-3.8.3-py2.py3-none-any.whl", hash = "sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c"}, 647 | {file = "flake8-3.8.3.tar.gz", hash = "sha256:f04b9fcbac03b0a3e58c0ab3a0ecc462e023a9faf046d57794184028123aa208"}, 648 | ] 649 | idna = [ 650 | {file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"}, 651 | {file = "idna-2.9.tar.gz", hash = "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb"}, 652 | ] 653 | importlib-metadata = [ 654 | {file = "importlib_metadata-1.6.1-py2.py3-none-any.whl", hash = "sha256:15ec6c0fd909e893e3a08b3a7c76ecb149122fb14b7efe1199ddd4c7c57ea958"}, 655 | {file = "importlib_metadata-1.6.1.tar.gz", hash = "sha256:0505dd08068cfec00f53a74a0ad927676d7757da81b7436a6eefe4c7cf75c545"}, 656 | ] 657 | ipykernel = [ 658 | {file = "ipykernel-5.3.0-py3-none-any.whl", hash = "sha256:a8362e3ae365023ca458effe93b026b8cdadc0b73ff3031472128dd8a2cf0289"}, 659 | {file = "ipykernel-5.3.0.tar.gz", hash = "sha256:731adb3f2c4ebcaff52e10a855ddc87670359a89c9c784d711e62d66fccdafae"}, 660 | ] 661 | ipython = [ 662 | {file = "ipython-7.15.0-py3-none-any.whl", hash = "sha256:1b85d65632211bf5d3e6f1406f3393c8c429a47d7b947b9a87812aa5bce6595c"}, 663 | {file = "ipython-7.15.0.tar.gz", hash = "sha256:0ef1433879816a960cd3ae1ae1dc82c64732ca75cec8dab5a4e29783fb571d0e"}, 664 | ] 665 | ipython-genutils = [ 666 | {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, 667 | {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, 668 | ] 669 | jedi = [ 670 | {file = "jedi-0.17.1-py2.py3-none-any.whl", hash = "sha256:1ddb0ec78059e8e27ec9eb5098360b4ea0a3dd840bedf21415ea820c21b40a22"}, 671 | {file = "jedi-0.17.1.tar.gz", hash = "sha256:807d5d4f96711a2bcfdd5dfa3b1ae6d09aa53832b182090b222b5efb81f52f63"}, 672 | ] 673 | jupyter-client = [ 674 | {file = "jupyter_client-6.1.3-py3-none-any.whl", hash = "sha256:cde8e83aab3ec1c614f221ae54713a9a46d3bf28292609d2db1b439bef5a8c8e"}, 675 | {file = "jupyter_client-6.1.3.tar.gz", hash = "sha256:3a32fa4d0b16d1c626b30c3002a62dfd86d6863ed39eaba3f537fade197bb756"}, 676 | ] 677 | jupyter-core = [ 678 | {file = "jupyter_core-4.6.3-py2.py3-none-any.whl", hash = "sha256:a4ee613c060fe5697d913416fc9d553599c05e4492d58fac1192c9a6844abb21"}, 679 | {file = "jupyter_core-4.6.3.tar.gz", hash = "sha256:394fd5dd787e7c8861741880bdf8a00ce39f95de5d18e579c74b882522219e7e"}, 680 | ] 681 | lxml = [ 682 | {file = "lxml-4.5.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ee2be8b8f72a2772e72ab926a3bccebf47bb727bda41ae070dc91d1fb759b726"}, 683 | {file = "lxml-4.5.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:fadd2a63a2bfd7fb604508e553d1cf68eca250b2fbdbd81213b5f6f2fbf23529"}, 684 | {file = "lxml-4.5.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4f282737d187ae723b2633856085c31ae5d4d432968b7f3f478a48a54835f5c4"}, 685 | {file = "lxml-4.5.1-cp27-cp27m-win32.whl", hash = "sha256:7fd88cb91a470b383aafad554c3fe1ccf6dfb2456ff0e84b95335d582a799804"}, 686 | {file = "lxml-4.5.1-cp27-cp27m-win_amd64.whl", hash = "sha256:0790ddca3f825dd914978c94c2545dbea5f56f008b050e835403714babe62a5f"}, 687 | {file = "lxml-4.5.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:9144ce36ca0824b29ebc2e02ca186e54040ebb224292072250467190fb613b96"}, 688 | {file = "lxml-4.5.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:a636346c6c0e1092ffc202d97ec1843a75937d8c98aaf6771348ad6422e44bb0"}, 689 | {file = "lxml-4.5.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:f95d28193c3863132b1f55c1056036bf580b5a488d908f7d22a04ace8935a3a9"}, 690 | {file = "lxml-4.5.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b26719890c79a1dae7d53acac5f089d66fd8cc68a81f4e4bd355e45470dc25e1"}, 691 | {file = "lxml-4.5.1-cp35-cp35m-win32.whl", hash = "sha256:a9e3b8011388e7e373565daa5e92f6c9cb844790dc18e43073212bb3e76f7007"}, 692 | {file = "lxml-4.5.1-cp35-cp35m-win_amd64.whl", hash = "sha256:2754d4406438c83144f9ffd3628bbe2dcc6d62b20dbc5c1ec4bc4385e5d44b42"}, 693 | {file = "lxml-4.5.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:925baf6ff1ef2c45169f548cc85204433e061360bfa7d01e1be7ae38bef73194"}, 694 | {file = "lxml-4.5.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a87dbee7ad9dce3aaefada2081843caf08a44a8f52e03e0a4cc5819f8398f2f4"}, 695 | {file = "lxml-4.5.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:51bb4edeb36d24ec97eb3e6a6007be128b720114f9a875d6b370317d62ac80b9"}, 696 | {file = "lxml-4.5.1-cp36-cp36m-win32.whl", hash = "sha256:c79e5debbe092e3c93ca4aee44c9a7631bdd407b2871cb541b979fd350bbbc29"}, 697 | {file = "lxml-4.5.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b7462cdab6fffcda853338e1741ce99706cdf880d921b5a769202ea7b94e8528"}, 698 | {file = "lxml-4.5.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06748c7192eab0f48e3d35a7adae609a329c6257495d5e53878003660dc0fec6"}, 699 | {file = "lxml-4.5.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:1aa7a6197c1cdd65d974f3e4953764eee3d9c7b67e3966616b41fab7f8f516b7"}, 700 | {file = "lxml-4.5.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:afb53edf1046599991fb4a7d03e601ab5f5422a5435c47ee6ba91ec3b61416a6"}, 701 | {file = "lxml-4.5.1-cp37-cp37m-win32.whl", hash = "sha256:2d1ddce96cf15f1254a68dba6935e6e0f1fe39247de631c115e84dd404a6f031"}, 702 | {file = "lxml-4.5.1-cp37-cp37m-win_amd64.whl", hash = "sha256:22c6d34fdb0e65d5f782a4d1a1edb52e0a8365858dafb1c08cb1d16546cf0786"}, 703 | {file = "lxml-4.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c47a8a5d00060122ca5908909478abce7bbf62d812e3fc35c6c802df8fb01fe7"}, 704 | {file = "lxml-4.5.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:b77975465234ff49fdad871c08aa747aae06f5e5be62866595057c43f8d2f62c"}, 705 | {file = "lxml-4.5.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2b02c106709466a93ed424454ce4c970791c486d5fcdf52b0d822a7e29789626"}, 706 | {file = "lxml-4.5.1-cp38-cp38-win32.whl", hash = "sha256:7eee37c1b9815e6505847aa5e68f192e8a1b730c5c7ead39ff317fde9ce29448"}, 707 | {file = "lxml-4.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:d8d40e0121ca1606aa9e78c28a3a7d88a05c06b3ca61630242cded87d8ce55fa"}, 708 | {file = "lxml-4.5.1.tar.gz", hash = "sha256:27ee0faf8077c7c1a589573b1450743011117f1aa1a91d5ae776bbc5ca6070f2"}, 709 | ] 710 | mccabe = [ 711 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 712 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 713 | ] 714 | numpy = [ 715 | {file = "numpy-1.19.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e9b30d4bd69498fc0c3fe9db5f62fffbb06b8eb9321f92cc970f2969be5e3949"}, 716 | {file = "numpy-1.19.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:fedbd128668ead37f33917820b704784aff695e0019309ad446a6d0b065b57e4"}, 717 | {file = "numpy-1.19.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8ece138c3a16db8c1ad38f52eb32be6086cc72f403150a79336eb2045723a1ad"}, 718 | {file = "numpy-1.19.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:64324f64f90a9e4ef732be0928be853eee378fd6a01be21a0a8469c4f2682c83"}, 719 | {file = "numpy-1.19.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:ad6f2ff5b1989a4899bf89800a671d71b1612e5ff40866d1f4d8bcf48d4e5764"}, 720 | {file = "numpy-1.19.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:d6c7bb82883680e168b55b49c70af29b84b84abb161cbac2800e8fcb6f2109b6"}, 721 | {file = "numpy-1.19.4-cp36-cp36m-win32.whl", hash = "sha256:13d166f77d6dc02c0a73c1101dd87fdf01339febec1030bd810dcd53fff3b0f1"}, 722 | {file = "numpy-1.19.4-cp36-cp36m-win_amd64.whl", hash = "sha256:448ebb1b3bf64c0267d6b09a7cba26b5ae61b6d2dbabff7c91b660c7eccf2bdb"}, 723 | {file = "numpy-1.19.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:27d3f3b9e3406579a8af3a9f262f5339005dd25e0ecf3cf1559ff8a49ed5cbf2"}, 724 | {file = "numpy-1.19.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:16c1b388cc31a9baa06d91a19366fb99ddbe1c7b205293ed072211ee5bac1ed2"}, 725 | {file = "numpy-1.19.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e5b6ed0f0b42317050c88022349d994fe72bfe35f5908617512cd8c8ef9da2a9"}, 726 | {file = "numpy-1.19.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:18bed2bcb39e3f758296584337966e68d2d5ba6aab7e038688ad53c8f889f757"}, 727 | {file = "numpy-1.19.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:fe45becb4c2f72a0907c1d0246ea6449fe7a9e2293bb0e11c4e9a32bb0930a15"}, 728 | {file = "numpy-1.19.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:6d7593a705d662be5bfe24111af14763016765f43cb6923ed86223f965f52387"}, 729 | {file = "numpy-1.19.4-cp37-cp37m-win32.whl", hash = "sha256:6ae6c680f3ebf1cf7ad1d7748868b39d9f900836df774c453c11c5440bc15b36"}, 730 | {file = "numpy-1.19.4-cp37-cp37m-win_amd64.whl", hash = "sha256:9eeb7d1d04b117ac0d38719915ae169aa6b61fca227b0b7d198d43728f0c879c"}, 731 | {file = "numpy-1.19.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cb1017eec5257e9ac6209ac172058c430e834d5d2bc21961dceeb79d111e5909"}, 732 | {file = "numpy-1.19.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:edb01671b3caae1ca00881686003d16c2209e07b7ef8b7639f1867852b948f7c"}, 733 | {file = "numpy-1.19.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:f29454410db6ef8126c83bd3c968d143304633d45dc57b51252afbd79d700893"}, 734 | {file = "numpy-1.19.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:ec149b90019852266fec2341ce1db513b843e496d5a8e8cdb5ced1923a92faab"}, 735 | {file = "numpy-1.19.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:1aeef46a13e51931c0b1cf8ae1168b4a55ecd282e6688fdb0a948cc5a1d5afb9"}, 736 | {file = "numpy-1.19.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:08308c38e44cc926bdfce99498b21eec1f848d24c302519e64203a8da99a97db"}, 737 | {file = "numpy-1.19.4-cp38-cp38-win32.whl", hash = "sha256:5734bdc0342aba9dfc6f04920988140fb41234db42381cf7ccba64169f9fe7ac"}, 738 | {file = "numpy-1.19.4-cp38-cp38-win_amd64.whl", hash = "sha256:09c12096d843b90eafd01ea1b3307e78ddd47a55855ad402b157b6c4862197ce"}, 739 | {file = "numpy-1.19.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e452dc66e08a4ce642a961f134814258a082832c78c90351b75c41ad16f79f63"}, 740 | {file = "numpy-1.19.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:a5d897c14513590a85774180be713f692df6fa8ecf6483e561a6d47309566f37"}, 741 | {file = "numpy-1.19.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:a09f98011236a419ee3f49cedc9ef27d7a1651df07810ae430a6b06576e0b414"}, 742 | {file = "numpy-1.19.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:50e86c076611212ca62e5a59f518edafe0c0730f7d9195fec718da1a5c2bb1fc"}, 743 | {file = "numpy-1.19.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f0d3929fe88ee1c155129ecd82f981b8856c5d97bcb0d5f23e9b4242e79d1de3"}, 744 | {file = "numpy-1.19.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:c42c4b73121caf0ed6cd795512c9c09c52a7287b04d105d112068c1736d7c753"}, 745 | {file = "numpy-1.19.4-cp39-cp39-win32.whl", hash = "sha256:8cac8790a6b1ddf88640a9267ee67b1aee7a57dfa2d2dd33999d080bc8ee3a0f"}, 746 | {file = "numpy-1.19.4-cp39-cp39-win_amd64.whl", hash = "sha256:4377e10b874e653fe96985c05feed2225c912e328c8a26541f7fc600fb9c637b"}, 747 | {file = "numpy-1.19.4-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:2a2740aa9733d2e5b2dfb33639d98a64c3b0f24765fed86b0fd2aec07f6a0a08"}, 748 | {file = "numpy-1.19.4.zip", hash = "sha256:141ec3a3300ab89c7f2b0775289954d193cc8edb621ea05f99db9cb181530512"}, 749 | ] 750 | parso = [ 751 | {file = "parso-0.7.0-py2.py3-none-any.whl", hash = "sha256:158c140fc04112dc45bca311633ae5033c2c2a7b732fa33d0955bad8152a8dd0"}, 752 | {file = "parso-0.7.0.tar.gz", hash = "sha256:908e9fae2144a076d72ae4e25539143d40b8e3eafbaeae03c1bfe226f4cdf12c"}, 753 | ] 754 | pathspec = [ 755 | {file = "pathspec-0.8.0-py2.py3-none-any.whl", hash = "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0"}, 756 | {file = "pathspec-0.8.0.tar.gz", hash = "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061"}, 757 | ] 758 | pexpect = [ 759 | {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, 760 | {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, 761 | ] 762 | pickleshare = [ 763 | {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, 764 | {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, 765 | ] 766 | pillow = [ 767 | {file = "Pillow-8.0.1-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:b63d4ff734263ae4ce6593798bcfee6dbfb00523c82753a3a03cbc05555a9cc3"}, 768 | {file = "Pillow-8.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5f9403af9c790cc18411ea398a6950ee2def2a830ad0cfe6dc9122e6d528b302"}, 769 | {file = "Pillow-8.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6b4a8fd632b4ebee28282a9fef4c341835a1aa8671e2770b6f89adc8e8c2703c"}, 770 | {file = "Pillow-8.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:cc3ea6b23954da84dbee8025c616040d9aa5eaf34ea6895a0a762ee9d3e12e11"}, 771 | {file = "Pillow-8.0.1-cp36-cp36m-win32.whl", hash = "sha256:d8a96747df78cda35980905bf26e72960cba6d355ace4780d4bdde3b217cdf1e"}, 772 | {file = "Pillow-8.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:7ba0ba61252ab23052e642abdb17fd08fdcfdbbf3b74c969a30c58ac1ade7cd3"}, 773 | {file = "Pillow-8.0.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:795e91a60f291e75de2e20e6bdd67770f793c8605b553cb6e4387ce0cb302e09"}, 774 | {file = "Pillow-8.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0a2e8d03787ec7ad71dc18aec9367c946ef8ef50e1e78c71f743bc3a770f9fae"}, 775 | {file = "Pillow-8.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:006de60d7580d81f4a1a7e9f0173dc90a932e3905cc4d47ea909bc946302311a"}, 776 | {file = "Pillow-8.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:bd7bf289e05470b1bc74889d1466d9ad4a56d201f24397557b6f65c24a6844b8"}, 777 | {file = "Pillow-8.0.1-cp37-cp37m-win32.whl", hash = "sha256:95edb1ed513e68bddc2aee3de66ceaf743590bf16c023fb9977adc4be15bd3f0"}, 778 | {file = "Pillow-8.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:e38d58d9138ef972fceb7aeec4be02e3f01d383723965bfcef14d174c8ccd039"}, 779 | {file = "Pillow-8.0.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:d3d07c86d4efa1facdf32aa878bd508c0dc4f87c48125cc16b937baa4e5b5e11"}, 780 | {file = "Pillow-8.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:fbd922f702582cb0d71ef94442bfca57624352622d75e3be7a1e7e9360b07e72"}, 781 | {file = "Pillow-8.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:92c882b70a40c79de9f5294dc99390671e07fc0b0113d472cbea3fde15db1792"}, 782 | {file = "Pillow-8.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:7c9401e68730d6c4245b8e361d3d13e1035cbc94db86b49dc7da8bec235d0015"}, 783 | {file = "Pillow-8.0.1-cp38-cp38-win32.whl", hash = "sha256:6c1aca8231625115104a06e4389fcd9ec88f0c9befbabd80dc206c35561be271"}, 784 | {file = "Pillow-8.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:cc9ec588c6ef3a1325fa032ec14d97b7309db493782ea8c304666fb10c3bd9a7"}, 785 | {file = "Pillow-8.0.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:eb472586374dc66b31e36e14720747595c2b265ae962987261f044e5cce644b5"}, 786 | {file = "Pillow-8.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:0eeeae397e5a79dc088d8297a4c2c6f901f8fb30db47795113a4a605d0f1e5ce"}, 787 | {file = "Pillow-8.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:81f812d8f5e8a09b246515fac141e9d10113229bc33ea073fec11403b016bcf3"}, 788 | {file = "Pillow-8.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:895d54c0ddc78a478c80f9c438579ac15f3e27bf442c2a9aa74d41d0e4d12544"}, 789 | {file = "Pillow-8.0.1-cp39-cp39-win32.whl", hash = "sha256:2fb113757a369a6cdb189f8df3226e995acfed0a8919a72416626af1a0a71140"}, 790 | {file = "Pillow-8.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:59e903ca800c8cfd1ebe482349ec7c35687b95e98cefae213e271c8c7fffa021"}, 791 | {file = "Pillow-8.0.1-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:5abd653a23c35d980b332bc0431d39663b1709d64142e3652890df4c9b6970f6"}, 792 | {file = "Pillow-8.0.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:4b0ef2470c4979e345e4e0cc1bbac65fda11d0d7b789dbac035e4c6ce3f98adb"}, 793 | {file = "Pillow-8.0.1-pp37-pypy37_pp73-win32.whl", hash = "sha256:8de332053707c80963b589b22f8e0229f1be1f3ca862a932c1bcd48dafb18dd8"}, 794 | {file = "Pillow-8.0.1.tar.gz", hash = "sha256:11c5c6e9b02c9dac08af04f093eb5a2f84857df70a7d4a6a6ad461aca803fb9e"}, 795 | ] 796 | potrace = [] 797 | prompt-toolkit = [ 798 | {file = "prompt_toolkit-3.0.5-py3-none-any.whl", hash = "sha256:df7e9e63aea609b1da3a65641ceaf5bc7d05e0a04de5bd45d05dbeffbabf9e04"}, 799 | {file = "prompt_toolkit-3.0.5.tar.gz", hash = "sha256:563d1a4140b63ff9dd587bda9557cffb2fe73650205ab6f4383092fb882e7dc8"}, 800 | ] 801 | ptyprocess = [ 802 | {file = "ptyprocess-0.6.0-py2.py3-none-any.whl", hash = "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"}, 803 | {file = "ptyprocess-0.6.0.tar.gz", hash = "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0"}, 804 | ] 805 | pyaml = [ 806 | {file = "pyaml-19.4.1-py2.py3-none-any.whl", hash = "sha256:a2dcbc4a8bb00b541efd1c5a064d93474d4f41ded1484fbb08bec9d236523931"}, 807 | {file = "pyaml-19.4.1.tar.gz", hash = "sha256:c79ae98ececda136a034115ca178ee8bf3aa7df236c488c2f55d12f177b88f1e"}, 808 | ] 809 | pycodestyle = [ 810 | {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, 811 | {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, 812 | ] 813 | pyflakes = [ 814 | {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, 815 | {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, 816 | ] 817 | pygments = [ 818 | {file = "Pygments-2.6.1-py3-none-any.whl", hash = "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"}, 819 | {file = "Pygments-2.6.1.tar.gz", hash = "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44"}, 820 | ] 821 | python-dateutil = [ 822 | {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, 823 | {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, 824 | ] 825 | pywin32 = [ 826 | {file = "pywin32-228-cp27-cp27m-win32.whl", hash = "sha256:37dc9935f6a383cc744315ae0c2882ba1768d9b06700a70f35dc1ce73cd4ba9c"}, 827 | {file = "pywin32-228-cp27-cp27m-win_amd64.whl", hash = "sha256:11cb6610efc2f078c9e6d8f5d0f957620c333f4b23466931a247fb945ed35e89"}, 828 | {file = "pywin32-228-cp35-cp35m-win32.whl", hash = "sha256:1f45db18af5d36195447b2cffacd182fe2d296849ba0aecdab24d3852fbf3f80"}, 829 | {file = "pywin32-228-cp35-cp35m-win_amd64.whl", hash = "sha256:6e38c44097a834a4707c1b63efa9c2435f5a42afabff634a17f563bc478dfcc8"}, 830 | {file = "pywin32-228-cp36-cp36m-win32.whl", hash = "sha256:ec16d44b49b5f34e99eb97cf270806fdc560dff6f84d281eb2fcb89a014a56a9"}, 831 | {file = "pywin32-228-cp36-cp36m-win_amd64.whl", hash = "sha256:a60d795c6590a5b6baeacd16c583d91cce8038f959bd80c53bd9a68f40130f2d"}, 832 | {file = "pywin32-228-cp37-cp37m-win32.whl", hash = "sha256:af40887b6fc200eafe4d7742c48417529a8702dcc1a60bf89eee152d1d11209f"}, 833 | {file = "pywin32-228-cp37-cp37m-win_amd64.whl", hash = "sha256:00eaf43dbd05ba6a9b0080c77e161e0b7a601f9a3f660727a952e40140537de7"}, 834 | {file = "pywin32-228-cp38-cp38-win32.whl", hash = "sha256:fa6ba028909cfc64ce9e24bcf22f588b14871980d9787f1e2002c99af8f1850c"}, 835 | {file = "pywin32-228-cp38-cp38-win_amd64.whl", hash = "sha256:9b3466083f8271e1a5eb0329f4e0d61925d46b40b195a33413e0905dccb285e8"}, 836 | {file = "pywin32-228-cp39-cp39-win32.whl", hash = "sha256:ed74b72d8059a6606f64842e7917aeee99159ebd6b8d6261c518d002837be298"}, 837 | {file = "pywin32-228-cp39-cp39-win_amd64.whl", hash = "sha256:8319bafdcd90b7202c50d6014efdfe4fde9311b3ff15fd6f893a45c0868de203"}, 838 | ] 839 | pyyaml = [ 840 | {file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"}, 841 | {file = "PyYAML-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76"}, 842 | {file = "PyYAML-5.3.1-cp35-cp35m-win32.whl", hash = "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2"}, 843 | {file = "PyYAML-5.3.1-cp35-cp35m-win_amd64.whl", hash = "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c"}, 844 | {file = "PyYAML-5.3.1-cp36-cp36m-win32.whl", hash = "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2"}, 845 | {file = "PyYAML-5.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648"}, 846 | {file = "PyYAML-5.3.1-cp37-cp37m-win32.whl", hash = "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"}, 847 | {file = "PyYAML-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf"}, 848 | {file = "PyYAML-5.3.1-cp38-cp38-win32.whl", hash = "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97"}, 849 | {file = "PyYAML-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee"}, 850 | {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"}, 851 | ] 852 | pyzmq = [ 853 | {file = "pyzmq-19.0.1-cp27-cp27m-macosx_10_9_intel.whl", hash = "sha256:58688a2dfa044fad608a8e70ba8d019d0b872ec2acd75b7b5e37da8905605891"}, 854 | {file = "pyzmq-19.0.1-cp27-cp27m-win32.whl", hash = "sha256:87c78f6936e2654397ca2979c1d323ee4a889eef536cc77a938c6b5be33351a7"}, 855 | {file = "pyzmq-19.0.1-cp27-cp27m-win_amd64.whl", hash = "sha256:97b6255ae77328d0e80593681826a0479cb7bac0ba8251b4dd882f5145a2293a"}, 856 | {file = "pyzmq-19.0.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:15b4cb21118f4589c4db8be4ac12b21c8b4d0d42b3ee435d47f686c32fe2e91f"}, 857 | {file = "pyzmq-19.0.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:931339ac2000d12fe212e64f98ce291e81a7ec6c73b125f17cf08415b753c087"}, 858 | {file = "pyzmq-19.0.1-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:2a88b8fabd9cc35bd59194a7723f3122166811ece8b74018147a4ed8489e6421"}, 859 | {file = "pyzmq-19.0.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:bafd651b557dd81d89bd5f9c678872f3e7b7255c1c751b78d520df2caac80230"}, 860 | {file = "pyzmq-19.0.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:8952f6ba6ae598e792703f3134af5a01af8f5c7cf07e9a148f05a12b02412cea"}, 861 | {file = "pyzmq-19.0.1-cp35-cp35m-win32.whl", hash = "sha256:54aa24fd60c4262286fc64ca632f9e747c7cc3a3a1144827490e1dc9b8a3a960"}, 862 | {file = "pyzmq-19.0.1-cp35-cp35m-win_amd64.whl", hash = "sha256:dcbc3f30c11c60d709c30a213dc56e88ac016fe76ac6768e64717bd976072566"}, 863 | {file = "pyzmq-19.0.1-cp36-cp36m-macosx_10_9_intel.whl", hash = "sha256:6ca519309703e95d55965735a667809bbb65f52beda2fdb6312385d3e7a6d234"}, 864 | {file = "pyzmq-19.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:4ee0bfd82077a3ff11c985369529b12853a4064320523f8e5079b630f9551448"}, 865 | {file = "pyzmq-19.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ba6f24431b569aec674ede49cad197cad59571c12deed6ad8e3c596da8288217"}, 866 | {file = "pyzmq-19.0.1-cp36-cp36m-win32.whl", hash = "sha256:956775444d01331c7eb412c5fb9bb62130dfaac77e09f32764ea1865234e2ca9"}, 867 | {file = "pyzmq-19.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b08780e3a55215873b3b8e6e7ca8987f14c902a24b6ac081b344fd430d6ca7cd"}, 868 | {file = "pyzmq-19.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:21f7d91f3536f480cb2c10d0756bfa717927090b7fb863e6323f766e5461ee1c"}, 869 | {file = "pyzmq-19.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:bfff5ffff051f5aa47ba3b379d87bd051c3196b0c8a603e8b7ed68a6b4f217ec"}, 870 | {file = "pyzmq-19.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:07fb8fe6826a229dada876956590135871de60dbc7de5a18c3bcce2ed1f03c98"}, 871 | {file = "pyzmq-19.0.1-cp37-cp37m-win32.whl", hash = "sha256:342fb8a1dddc569bc361387782e8088071593e7eaf3e3ecf7d6bd4976edff112"}, 872 | {file = "pyzmq-19.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:faee2604f279d31312bc455f3d024f160b6168b9c1dde22bf62d8c88a4deca8e"}, 873 | {file = "pyzmq-19.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5b9d21fc56c8aacd2e6d14738021a9d64f3f69b30578a99325a728e38a349f85"}, 874 | {file = "pyzmq-19.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:af0c02cf49f4f9eedf38edb4f3b6bb621d83026e7e5d76eb5526cc5333782fd6"}, 875 | {file = "pyzmq-19.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5f1f2eb22aab606f808163eb1d537ac9a0ba4283fbeb7a62eb48d9103cf015c2"}, 876 | {file = "pyzmq-19.0.1-cp38-cp38-win32.whl", hash = "sha256:f9d7e742fb0196992477415bb34366c12e9bb9a0699b8b3f221ff93b213d7bec"}, 877 | {file = "pyzmq-19.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:5b99c2ae8089ef50223c28bac57510c163bfdff158c9e90764f812b94e69a0e6"}, 878 | {file = "pyzmq-19.0.1-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:cf5d689ba9513b9753959164cf500079383bc18859f58bf8ce06d8d4bef2b054"}, 879 | {file = "pyzmq-19.0.1-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:aaa8b40b676576fd7806839a5de8e6d5d1b74981e6376d862af6c117af2a3c10"}, 880 | {file = "pyzmq-19.0.1.tar.gz", hash = "sha256:13a5638ab24d628a6ade8f794195e1a1acd573496c3b85af2f1183603b7bf5e0"}, 881 | ] 882 | regex = [ 883 | {file = "regex-2020.6.8-cp27-cp27m-win32.whl", hash = "sha256:fbff901c54c22425a5b809b914a3bfaf4b9570eee0e5ce8186ac71eb2025191c"}, 884 | {file = "regex-2020.6.8-cp27-cp27m-win_amd64.whl", hash = "sha256:112e34adf95e45158c597feea65d06a8124898bdeac975c9087fe71b572bd938"}, 885 | {file = "regex-2020.6.8-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:92d8a043a4241a710c1cf7593f5577fbb832cf6c3a00ff3fc1ff2052aff5dd89"}, 886 | {file = "regex-2020.6.8-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:bae83f2a56ab30d5353b47f9b2a33e4aac4de9401fb582b55c42b132a8ac3868"}, 887 | {file = "regex-2020.6.8-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:b2ba0f78b3ef375114856cbdaa30559914d081c416b431f2437f83ce4f8b7f2f"}, 888 | {file = "regex-2020.6.8-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:95fa7726d073c87141f7bbfb04c284901f8328e2d430eeb71b8ffdd5742a5ded"}, 889 | {file = "regex-2020.6.8-cp36-cp36m-win32.whl", hash = "sha256:e3cdc9423808f7e1bb9c2e0bdb1c9dc37b0607b30d646ff6faf0d4e41ee8fee3"}, 890 | {file = "regex-2020.6.8-cp36-cp36m-win_amd64.whl", hash = "sha256:c78e66a922de1c95a208e4ec02e2e5cf0bb83a36ceececc10a72841e53fbf2bd"}, 891 | {file = "regex-2020.6.8-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:08997a37b221a3e27d68ffb601e45abfb0093d39ee770e4257bd2f5115e8cb0a"}, 892 | {file = "regex-2020.6.8-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2f6f211633ee8d3f7706953e9d3edc7ce63a1d6aad0be5dcee1ece127eea13ae"}, 893 | {file = "regex-2020.6.8-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:55b4c25cbb3b29f8d5e63aeed27b49fa0f8476b0d4e1b3171d85db891938cc3a"}, 894 | {file = "regex-2020.6.8-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:89cda1a5d3e33ec9e231ece7307afc101b5217523d55ef4dc7fb2abd6de71ba3"}, 895 | {file = "regex-2020.6.8-cp37-cp37m-win32.whl", hash = "sha256:690f858d9a94d903cf5cada62ce069b5d93b313d7d05456dbcd99420856562d9"}, 896 | {file = "regex-2020.6.8-cp37-cp37m-win_amd64.whl", hash = "sha256:1700419d8a18c26ff396b3b06ace315b5f2a6e780dad387e4c48717a12a22c29"}, 897 | {file = "regex-2020.6.8-cp38-cp38-manylinux1_i686.whl", hash = "sha256:654cb773b2792e50151f0e22be0f2b6e1c3a04c5328ff1d9d59c0398d37ef610"}, 898 | {file = "regex-2020.6.8-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:52e1b4bef02f4040b2fd547357a170fc1146e60ab310cdbdd098db86e929b387"}, 899 | {file = "regex-2020.6.8-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:cf59bbf282b627130f5ba68b7fa3abdb96372b24b66bdf72a4920e8153fc7910"}, 900 | {file = "regex-2020.6.8-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:5aaa5928b039ae440d775acea11d01e42ff26e1561c0ffcd3d805750973c6baf"}, 901 | {file = "regex-2020.6.8-cp38-cp38-win32.whl", hash = "sha256:97712e0d0af05febd8ab63d2ef0ab2d0cd9deddf4476f7aa153f76feef4b2754"}, 902 | {file = "regex-2020.6.8-cp38-cp38-win_amd64.whl", hash = "sha256:6ad8663c17db4c5ef438141f99e291c4d4edfeaacc0ce28b5bba2b0bf273d9b5"}, 903 | {file = "regex-2020.6.8.tar.gz", hash = "sha256:e9b64e609d37438f7d6e68c2546d2cb8062f3adb27e6336bc129b51be20773ac"}, 904 | ] 905 | requests = [ 906 | {file = "requests-2.24.0-py2.py3-none-any.whl", hash = "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"}, 907 | {file = "requests-2.24.0.tar.gz", hash = "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"}, 908 | ] 909 | rmapy = [] 910 | rope = [ 911 | {file = "rope-0.17.0.tar.gz", hash = "sha256:658ad6705f43dcf3d6df379da9486529cf30e02d9ea14c5682aa80eb33b649e1"}, 912 | ] 913 | six = [ 914 | {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, 915 | {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, 916 | ] 917 | snakeviz = [ 918 | {file = "snakeviz-2.1.0-py2.py3-none-any.whl", hash = "sha256:8ce375b18ae4a749516d7e6c6fbbf8be6177c53974f53534d8eadb646cd279b1"}, 919 | {file = "snakeviz-2.1.0.tar.gz", hash = "sha256:92ad876fb6a201a7e23a6b85ea96d9643a51e285667c253a8653643804f7cb68"}, 920 | ] 921 | svgpathtools = [] 922 | svgwrite = [ 923 | {file = "svgwrite-1.4-py3-none-any.whl", hash = "sha256:fa842fb3129a9399d19b5e9602a022fcc7f2f3f24713550e765c488ffafd743d"}, 924 | {file = "svgwrite-1.4.zip", hash = "sha256:b38ac03b67f81c728d81a33e4711aaf3ab136a57156d721bb17f88525d9909bb"}, 925 | ] 926 | toml = [ 927 | {file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"}, 928 | {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"}, 929 | ] 930 | tornado = [ 931 | {file = "tornado-6.0.4-cp35-cp35m-win32.whl", hash = "sha256:5217e601700f24e966ddab689f90b7ea4bd91ff3357c3600fa1045e26d68e55d"}, 932 | {file = "tornado-6.0.4-cp35-cp35m-win_amd64.whl", hash = "sha256:c98232a3ac391f5faea6821b53db8db461157baa788f5d6222a193e9456e1740"}, 933 | {file = "tornado-6.0.4-cp36-cp36m-win32.whl", hash = "sha256:5f6a07e62e799be5d2330e68d808c8ac41d4a259b9cea61da4101b83cb5dc673"}, 934 | {file = "tornado-6.0.4-cp36-cp36m-win_amd64.whl", hash = "sha256:c952975c8ba74f546ae6de2e226ab3cc3cc11ae47baf607459a6728585bb542a"}, 935 | {file = "tornado-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:2c027eb2a393d964b22b5c154d1a23a5f8727db6fda837118a776b29e2b8ebc6"}, 936 | {file = "tornado-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:5618f72e947533832cbc3dec54e1dffc1747a5cb17d1fd91577ed14fa0dc081b"}, 937 | {file = "tornado-6.0.4-cp38-cp38-win32.whl", hash = "sha256:22aed82c2ea340c3771e3babc5ef220272f6fd06b5108a53b4976d0d722bcd52"}, 938 | {file = "tornado-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:c58d56003daf1b616336781b26d184023ea4af13ae143d9dda65e31e534940b9"}, 939 | {file = "tornado-6.0.4.tar.gz", hash = "sha256:0fe2d45ba43b00a41cd73f8be321a44936dc1aba233dee979f17a042b83eb6dc"}, 940 | ] 941 | traitlets = [ 942 | {file = "traitlets-4.3.3-py2.py3-none-any.whl", hash = "sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44"}, 943 | {file = "traitlets-4.3.3.tar.gz", hash = "sha256:d023ee369ddd2763310e4c3eae1ff649689440d4ae59d7485eb4cfbbe3e359f7"}, 944 | ] 945 | typed-ast = [ 946 | {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, 947 | {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"}, 948 | {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"}, 949 | {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"}, 950 | {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, 951 | {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, 952 | {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, 953 | {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, 954 | {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, 955 | {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, 956 | {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, 957 | {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, 958 | {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, 959 | {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, 960 | {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, 961 | {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, 962 | {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, 963 | {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, 964 | {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, 965 | {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, 966 | {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, 967 | ] 968 | urllib3 = [ 969 | {file = "urllib3-1.25.9-py2.py3-none-any.whl", hash = "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"}, 970 | {file = "urllib3-1.25.9.tar.gz", hash = "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527"}, 971 | ] 972 | wcwidth = [ 973 | {file = "wcwidth-0.2.4-py2.py3-none-any.whl", hash = "sha256:79375666b9954d4a1a10739315816324c3e73110af9d0e102d906fdb0aec009f"}, 974 | {file = "wcwidth-0.2.4.tar.gz", hash = "sha256:8c6b5b6ee1360b842645f336d9e5d68c55817c26d3050f46b235ef2bc650e48f"}, 975 | ] 976 | zipp = [ 977 | {file = "zipp-3.1.0-py3-none-any.whl", hash = "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b"}, 978 | {file = "zipp-3.1.0.tar.gz", hash = "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"}, 979 | ] 980 | --------------------------------------------------------------------------------