├── .bumpversion.cfg ├── .flake8 ├── .gitignore ├── .pre-commit-config.yaml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── assets └── demo.gif ├── poetry.lock ├── pyproject.toml ├── src └── zotnote │ ├── __init__.py │ ├── __main__.py │ ├── __version__.py │ ├── app.py │ ├── cli.py │ ├── config │ ├── config.py │ └── config.toml.example │ ├── connectors │ └── bbt.py │ ├── notes │ ├── note.py │ └── templates │ │ ├── content │ │ ├── empirical.j2 │ │ └── simple.j2 │ │ └── master.j2 │ └── utils │ └── helpers.py └── tests ├── test_bbt.py ├── test_cli.py └── test_config.py /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.3.5 3 | commit = True 4 | tag = False 5 | 6 | [bumpversion:file:pyproject.toml] 7 | search = version = "{current_version}" 8 | replace = version = "{new_version}" 9 | 10 | [bumpversion:file:src/zotnote/__version__.py] 11 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 88 3 | extend-ignore = 4 | # See https://github.com/PyCQA/pycodestyle/issues/373 5 | E203, 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | notebooks 132 | 133 | .env 134 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/psf/black 3 | rev: stable 4 | hooks: 5 | - id: black 6 | 7 | - repo: https://gitlab.com/pycqa/flake8 8 | rev: 3.7.9 9 | hooks: 10 | - id: flake8 11 | 12 | - repo: https://github.com/timothycrosley/isort 13 | rev: 4.3.21 14 | hooks: 15 | - id: isort 16 | additional_dependencies: [toml] 17 | exclude: ^.*/?setup\.py$ 18 | 19 | - repo: https://github.com/pre-commit/pre-commit-hooks 20 | rev: v2.5.0 21 | hooks: 22 | - id: trailing-whitespace 23 | args: [--markdown-linebreak-ext=md] 24 | exclude: ^tests/.*/fixtures/.* 25 | - id: end-of-file-fixer 26 | exclude: ^tests/.*/fixtures/.* 27 | - id: debug-statements 28 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [0.3.0] - 31-03-2019 11 | 12 | - Major redesign of how configuration, notes, and templates are managed 13 | - XDG_DATA_HOME is no longer used to store templates. User templates will be loaded from notes folder 14 | - Implemented a Note class to manage notes/templates related methods 15 | - Changes to CLI: 16 | - Added `templates` function to list all available templates 17 | - Added `-t/--template` option to `zotnote add` to choose between various formats 18 | - Improved python project management 19 | - Added bump2version to project 20 | - Added pre-commit running various hooks 21 | 22 | ## [0.2.0] - 27-03-2019 23 | 24 | - Completely removed requirements to access local SQLite database. Now using the BBP search interface. 25 | - Will probably remove all sqlite3 dependencies in the future. 26 | - Modified "create" command to no longer overwrite files. 27 | - Added "edit" command to open notes in an editor 28 | - Added "remove" command to delete note 29 | 30 | ## [0.1.0] - 26-03-2019 31 | 32 | Initial version released. 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Asura Enkhbayar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ZotNote 2 | 3 | Automatize and manage your reading notes with Zotero & Better Bibtex Plugin (BBT). **Note: ZotNote is still in early development and not production ready** 4 | 5 | [![PyPI version](https://img.shields.io/pypi/v/zotnote.svg)](https://pypi.python.org/pypi/zotnote/) 6 | 7 | ![ZotNote demo](assets/demo.gif) 8 | 9 | --- 10 | 11 | *Current features* 12 | 13 | - Simple installation via pipx/pip 14 | - Full command-line interface to create, edit, and remove notes 15 | - Graphical interface to select a Zotero item 16 | - Support for various reading note templates 17 | 18 | *Planned features* 19 | 20 | - Annotation of reading notes and individual quotes using tags/keywords 21 | - Retrieval of relevant quotes based on these tags and keywords 22 | - Analytics based on these tags and keywords 23 | - Enrich reading notes with more metadata from Zotero 24 | - Simple reports about progress of literature review 25 | - (*dreaming*) Automatically export collection of notes as an annotated bibliography. 26 | 27 | *Long-term vision* 28 | 29 | A literature review suite that connects to Zotero & BBT. Management of reading notes, reading/writing analytics, and basic qualitative text analysis (export reports as HTML via Jupyter notebooks). Export of reading notes in different formats (e.g., annotated bibliography). 30 | 31 | You can find a roadmap for ZotNote [here](https://github.com/Bubblbu/zotnote/issues/7). 32 | 33 | ## Installation 34 | 35 | ### Requirements 36 | 37 | - [Python](https://www.python.org/downloads/) 3.6 or higher 38 | - [Zotero Standalone](https://www.zotero.org/) with [Better Bibtex plugin](https://github.com/retorquere/zotero-better-bibtex) 39 | 40 | ### Recommended: Install via pipx 41 | 42 | The recommended way to install ZotNote is using [pipx](https://pipxproject.github.io/pipx/). Pipx cleanly install the package in an isolated environment (clean uninstalls!) and automagically exposes the CLI-endpoints globally on your system. 43 | 44 | pipx install zotnote 45 | 46 | ### Option 2: Install via pip 47 | 48 | However, you can also simply use pip. Please be aware of the Python version and environments you are using. 49 | 50 | pip install zotnote 51 | 52 | ### Option 3: Download from GitHub 53 | 54 | Download the latest release from Github and unzip. Put the folder containing the scripts into your `PATH`. 55 | 56 | Alternatively, run 57 | 58 | 59 | [sudo] python3 setup.py install 60 | 61 | or 62 | 63 | python3 setup.py install --user 64 | 65 | ### Option 4: Git clone (for developers) 66 | 67 | git clone git@github.com:Bubblbu/zotnote.git 68 | 69 | The project is being developed with [Poetry](https://python-poetry.org/) as a dependency manager. 70 | 71 | More instructions will follow soon! 72 | 73 | ## Getting started 74 | 75 | ``` 76 | Usage: zotnote [OPTIONS] COMMAND [ARGS]... 77 | 78 | CLI for ZotNote. 79 | 80 | Options: 81 | --help Show this message and exit. 82 | 83 | Commands: 84 | add Create a new note. 85 | config Configure Zotnote from the command line. 86 | edit Open a note in your editor of choice. 87 | remove Remove a note 88 | templates List all available templates for notes. 89 | ``` 90 | 91 | ### Configuration 92 | 93 | After installation you should be able to simply run `zotnote` and be prompted to a quick command-line configuration. 94 | 95 | ZotNote currently asks you for: 96 | 97 | - A name which is used in all reading notes. 98 | - An email address 99 | - A folder to store your reading notes 100 | 101 | ### Usage 102 | 103 | Some basic use cases: 104 | 105 | Create a note with the graphical interface (Zotero picker) 106 | 107 | zotnote add 108 | 109 | Create for specific citekey 110 | 111 | zotnote add [citekey] 112 | 113 | Edit a note (with graphical picker) 114 | 115 | zotnote edit 116 | 117 | or 118 | 119 | zotnote edit [citekey] 120 | 121 | You can explore each command in detail by adding `--help`. 122 | 123 | ## Authors 124 | 125 | Written by [Asura Enkhbayar](https://twitter.com/bubblbu_) while he was quarantined. 126 | -------------------------------------------------------------------------------- /assets/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bubblbu/zotnote/5e27bc79a80d362f61f35c72c1c773551f1274f5/assets/demo.gif -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | category = "dev" 3 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 4 | name = "appdirs" 5 | optional = false 6 | python-versions = "*" 7 | version = "1.4.3" 8 | 9 | [[package]] 10 | category = "dev" 11 | description = "Disable App Nap on OS X 10.9" 12 | marker = "sys_platform == \"darwin\" or platform_system == \"Darwin\"" 13 | name = "appnope" 14 | optional = false 15 | python-versions = "*" 16 | version = "0.1.0" 17 | 18 | [[package]] 19 | category = "dev" 20 | description = "A few extensions to pyyaml." 21 | name = "aspy.yaml" 22 | optional = false 23 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 24 | version = "1.3.0" 25 | 26 | [package.dependencies] 27 | pyyaml = "*" 28 | 29 | [[package]] 30 | category = "dev" 31 | description = "Atomic file writes." 32 | marker = "sys_platform == \"win32\"" 33 | name = "atomicwrites" 34 | optional = false 35 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 36 | version = "1.3.0" 37 | 38 | [[package]] 39 | category = "dev" 40 | description = "Classes Without Boilerplate" 41 | name = "attrs" 42 | optional = false 43 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 44 | version = "19.3.0" 45 | 46 | [package.extras] 47 | azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] 48 | dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"] 49 | docs = ["sphinx", "zope.interface"] 50 | tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] 51 | 52 | [[package]] 53 | category = "dev" 54 | description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" 55 | name = "autopep8" 56 | optional = false 57 | python-versions = "*" 58 | version = "1.5" 59 | 60 | [package.dependencies] 61 | pycodestyle = ">=2.5.0" 62 | 63 | [[package]] 64 | category = "dev" 65 | description = "Specifications for callback functions passed in to an API" 66 | name = "backcall" 67 | optional = false 68 | python-versions = "*" 69 | version = "0.1.0" 70 | 71 | [[package]] 72 | category = "dev" 73 | description = "The uncompromising code formatter." 74 | name = "black" 75 | optional = false 76 | python-versions = ">=3.6" 77 | version = "19.10b0" 78 | 79 | [package.dependencies] 80 | appdirs = "*" 81 | attrs = ">=18.1.0" 82 | click = ">=6.5" 83 | pathspec = ">=0.6,<1" 84 | regex = "*" 85 | toml = ">=0.9.4" 86 | typed-ast = ">=1.4.0" 87 | 88 | [package.extras] 89 | d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] 90 | 91 | [[package]] 92 | category = "dev" 93 | description = "Version-bump your software with a single command!" 94 | name = "bump2version" 95 | optional = false 96 | python-versions = ">=3.5" 97 | version = "1.0.0" 98 | 99 | [[package]] 100 | category = "main" 101 | description = "Python package for providing Mozilla's CA Bundle." 102 | name = "certifi" 103 | optional = false 104 | python-versions = "*" 105 | version = "2019.11.28" 106 | 107 | [[package]] 108 | category = "dev" 109 | description = "Validate configuration and produce human readable error messages." 110 | name = "cfgv" 111 | optional = false 112 | python-versions = ">=3.6" 113 | version = "3.0.0" 114 | 115 | [[package]] 116 | category = "main" 117 | description = "Universal encoding detector for Python 2 and 3" 118 | name = "chardet" 119 | optional = false 120 | python-versions = "*" 121 | version = "3.0.4" 122 | 123 | [[package]] 124 | category = "main" 125 | description = "Composable command line interface toolkit" 126 | name = "click" 127 | optional = false 128 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 129 | version = "7.1.1" 130 | 131 | [[package]] 132 | category = "main" 133 | description = "Option groups missing in Click" 134 | name = "click-option-group" 135 | optional = false 136 | python-versions = ">=3.6,<4" 137 | version = "0.3.0" 138 | 139 | [package.dependencies] 140 | Click = ">=7.0,<8" 141 | 142 | [package.extras] 143 | docs = ["sphinx (>=2.3)", "pallets-sphinx-themes", "m2r"] 144 | tests = ["pytest"] 145 | 146 | [[package]] 147 | category = "dev" 148 | description = "Cross-platform colored terminal text." 149 | marker = "sys_platform == \"win32\"" 150 | name = "colorama" 151 | optional = false 152 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 153 | version = "0.4.3" 154 | 155 | [[package]] 156 | category = "dev" 157 | description = "Code coverage measurement for Python" 158 | name = "coverage" 159 | optional = false 160 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 161 | version = "5.0.4" 162 | 163 | [package.extras] 164 | toml = ["toml"] 165 | 166 | [[package]] 167 | category = "dev" 168 | description = "Decorators for Humans" 169 | name = "decorator" 170 | optional = false 171 | python-versions = ">=2.6, !=3.0.*, !=3.1.*" 172 | version = "4.4.2" 173 | 174 | [[package]] 175 | category = "dev" 176 | description = "Distribution utilities" 177 | name = "distlib" 178 | optional = false 179 | python-versions = "*" 180 | version = "0.3.0" 181 | 182 | [[package]] 183 | category = "dev" 184 | description = "Discover and load entry points from installed packages." 185 | name = "entrypoints" 186 | optional = false 187 | python-versions = ">=2.7" 188 | version = "0.3" 189 | 190 | [[package]] 191 | category = "dev" 192 | description = "A platform independent file lock." 193 | name = "filelock" 194 | optional = false 195 | python-versions = "*" 196 | version = "3.0.12" 197 | 198 | [[package]] 199 | category = "dev" 200 | description = "the modular source code checker: pep8, pyflakes and co" 201 | name = "flake8" 202 | optional = false 203 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 204 | version = "3.7.9" 205 | 206 | [package.dependencies] 207 | entrypoints = ">=0.3.0,<0.4.0" 208 | mccabe = ">=0.6.0,<0.7.0" 209 | pycodestyle = ">=2.5.0,<2.6.0" 210 | pyflakes = ">=2.1.0,<2.2.0" 211 | 212 | [[package]] 213 | category = "dev" 214 | description = "Extension for flake8 which uses pydocstyle to check docstrings" 215 | name = "flake8-docstrings" 216 | optional = false 217 | python-versions = "*" 218 | version = "1.5.0" 219 | 220 | [package.dependencies] 221 | flake8 = ">=3" 222 | pydocstyle = ">=2.1" 223 | 224 | [[package]] 225 | category = "dev" 226 | description = "File identification library for Python" 227 | name = "identify" 228 | optional = false 229 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" 230 | version = "1.4.13" 231 | 232 | [package.extras] 233 | license = ["editdistance"] 234 | 235 | [[package]] 236 | category = "main" 237 | description = "Internationalized Domain Names in Applications (IDNA)" 238 | name = "idna" 239 | optional = false 240 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 241 | version = "2.9" 242 | 243 | [[package]] 244 | category = "dev" 245 | description = "Read metadata from Python packages" 246 | marker = "python_version < \"3.8\"" 247 | name = "importlib-metadata" 248 | optional = false 249 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 250 | version = "1.6.0" 251 | 252 | [package.dependencies] 253 | zipp = ">=0.5" 254 | 255 | [package.extras] 256 | docs = ["sphinx", "rst.linker"] 257 | testing = ["packaging", "importlib-resources"] 258 | 259 | [[package]] 260 | category = "dev" 261 | description = "Read resources from Python packages" 262 | marker = "python_version < \"3.7\"" 263 | name = "importlib-resources" 264 | optional = false 265 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 266 | version = "1.4.0" 267 | 268 | [package.dependencies] 269 | [package.dependencies.importlib-metadata] 270 | python = "<3.8" 271 | version = "*" 272 | 273 | [package.dependencies.zipp] 274 | python = "<3.8" 275 | version = ">=0.4" 276 | 277 | [package.extras] 278 | docs = ["sphinx", "rst.linker", "jaraco.packaging"] 279 | 280 | [[package]] 281 | category = "dev" 282 | description = "IPython Kernel for Jupyter" 283 | name = "ipykernel" 284 | optional = false 285 | python-versions = ">=3.5" 286 | version = "5.2.0" 287 | 288 | [package.dependencies] 289 | appnope = "*" 290 | ipython = ">=5.0.0" 291 | jupyter-client = "*" 292 | tornado = ">=4.2" 293 | traitlets = ">=4.1.0" 294 | 295 | [package.extras] 296 | test = ["pytest (!=5.3.4)", "pytest-cov", "flaky", "nose"] 297 | 298 | [[package]] 299 | category = "dev" 300 | description = "IPython: Productive Interactive Computing" 301 | name = "ipython" 302 | optional = false 303 | python-versions = ">=3.6" 304 | version = "7.13.0" 305 | 306 | [package.dependencies] 307 | appnope = "*" 308 | backcall = "*" 309 | colorama = "*" 310 | decorator = "*" 311 | jedi = ">=0.10" 312 | pexpect = "*" 313 | pickleshare = "*" 314 | prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" 315 | pygments = "*" 316 | setuptools = ">=18.5" 317 | traitlets = ">=4.2" 318 | 319 | [package.extras] 320 | all = ["numpy (>=1.14)", "testpath", "notebook", "nose (>=0.10.1)", "nbconvert", "requests", "ipywidgets", "qtconsole", "ipyparallel", "Sphinx (>=1.3)", "pygments", "nbformat", "ipykernel"] 321 | doc = ["Sphinx (>=1.3)"] 322 | kernel = ["ipykernel"] 323 | nbconvert = ["nbconvert"] 324 | nbformat = ["nbformat"] 325 | notebook = ["notebook", "ipywidgets"] 326 | parallel = ["ipyparallel"] 327 | qtconsole = ["qtconsole"] 328 | test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.14)"] 329 | 330 | [[package]] 331 | category = "dev" 332 | description = "Vestigial utilities from IPython" 333 | name = "ipython-genutils" 334 | optional = false 335 | python-versions = "*" 336 | version = "0.2.0" 337 | 338 | [[package]] 339 | category = "dev" 340 | description = "An autocompletion tool for Python that can be used for text editors." 341 | name = "jedi" 342 | optional = false 343 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 344 | version = "0.16.0" 345 | 346 | [package.dependencies] 347 | parso = ">=0.5.2" 348 | 349 | [package.extras] 350 | qa = ["flake8 (3.7.9)"] 351 | testing = ["colorama (0.4.1)", "docopt", "pytest (>=3.9.0,<5.0.0)"] 352 | 353 | [[package]] 354 | category = "main" 355 | description = "A very fast and expressive template engine." 356 | name = "jinja2" 357 | optional = false 358 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 359 | version = "2.11.1" 360 | 361 | [package.dependencies] 362 | MarkupSafe = ">=0.23" 363 | 364 | [package.extras] 365 | i18n = ["Babel (>=0.8)"] 366 | 367 | [[package]] 368 | category = "dev" 369 | description = "Jupyter protocol implementation and client libraries" 370 | name = "jupyter-client" 371 | optional = false 372 | python-versions = ">=3.5" 373 | version = "6.1.2" 374 | 375 | [package.dependencies] 376 | jupyter-core = ">=4.6.0" 377 | python-dateutil = ">=2.1" 378 | pyzmq = ">=13" 379 | tornado = ">=4.1" 380 | traitlets = "*" 381 | 382 | [package.extras] 383 | test = ["ipykernel", "ipython", "mock", "pytest"] 384 | 385 | [[package]] 386 | category = "dev" 387 | description = "Jupyter core package. A base package on which Jupyter projects rely." 388 | name = "jupyter-core" 389 | optional = false 390 | python-versions = "!=3.0,!=3.1,!=3.2,!=3.3,!=3.4,>=2.7" 391 | version = "4.6.3" 392 | 393 | [package.dependencies] 394 | pywin32 = ">=1.0" 395 | traitlets = "*" 396 | 397 | [[package]] 398 | category = "main" 399 | description = "Safely add untrusted strings to HTML/XML markup." 400 | name = "markupsafe" 401 | optional = false 402 | python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" 403 | version = "1.1.1" 404 | 405 | [[package]] 406 | category = "dev" 407 | description = "McCabe checker, plugin for flake8" 408 | name = "mccabe" 409 | optional = false 410 | python-versions = "*" 411 | version = "0.6.1" 412 | 413 | [[package]] 414 | category = "dev" 415 | description = "More routines for operating on iterables, beyond itertools" 416 | name = "more-itertools" 417 | optional = false 418 | python-versions = ">=3.5" 419 | version = "8.2.0" 420 | 421 | [[package]] 422 | category = "dev" 423 | description = "Node.js virtual environment builder" 424 | name = "nodeenv" 425 | optional = false 426 | python-versions = "*" 427 | version = "1.3.5" 428 | 429 | [[package]] 430 | category = "dev" 431 | description = "Core utilities for Python packages" 432 | name = "packaging" 433 | optional = false 434 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 435 | version = "20.3" 436 | 437 | [package.dependencies] 438 | pyparsing = ">=2.0.2" 439 | six = "*" 440 | 441 | [[package]] 442 | category = "dev" 443 | description = "A Python Parser" 444 | name = "parso" 445 | optional = false 446 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 447 | version = "0.6.2" 448 | 449 | [package.extras] 450 | testing = ["docopt", "pytest (>=3.0.7)"] 451 | 452 | [[package]] 453 | category = "dev" 454 | description = "Utility library for gitignore style pattern matching of file paths." 455 | name = "pathspec" 456 | optional = false 457 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 458 | version = "0.7.0" 459 | 460 | [[package]] 461 | category = "dev" 462 | description = "Pexpect allows easy control of interactive console applications." 463 | marker = "sys_platform != \"win32\"" 464 | name = "pexpect" 465 | optional = false 466 | python-versions = "*" 467 | version = "4.8.0" 468 | 469 | [package.dependencies] 470 | ptyprocess = ">=0.5" 471 | 472 | [[package]] 473 | category = "dev" 474 | description = "Tiny 'shelve'-like database with concurrency support" 475 | name = "pickleshare" 476 | optional = false 477 | python-versions = "*" 478 | version = "0.7.5" 479 | 480 | [[package]] 481 | category = "dev" 482 | description = "plugin and hook calling mechanisms for python" 483 | name = "pluggy" 484 | optional = false 485 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 486 | version = "0.13.1" 487 | 488 | [package.dependencies] 489 | [package.dependencies.importlib-metadata] 490 | python = "<3.8" 491 | version = ">=0.12" 492 | 493 | [package.extras] 494 | dev = ["pre-commit", "tox"] 495 | 496 | [[package]] 497 | category = "dev" 498 | description = "A framework for managing and maintaining multi-language pre-commit hooks." 499 | name = "pre-commit" 500 | optional = false 501 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 502 | version = "1.21.0" 503 | 504 | [package.dependencies] 505 | "aspy.yaml" = "*" 506 | cfgv = ">=2.0.0" 507 | identify = ">=1.0.0" 508 | nodeenv = ">=0.11.1" 509 | pyyaml = "*" 510 | six = "*" 511 | toml = "*" 512 | virtualenv = ">=15.2" 513 | 514 | [package.dependencies.importlib-metadata] 515 | python = "<3.8" 516 | version = "*" 517 | 518 | [package.dependencies.importlib-resources] 519 | python = "<3.7" 520 | version = "*" 521 | 522 | [[package]] 523 | category = "dev" 524 | description = "Library for building powerful interactive command lines in Python" 525 | name = "prompt-toolkit" 526 | optional = false 527 | python-versions = ">=3.6" 528 | version = "3.0.3" 529 | 530 | [package.dependencies] 531 | wcwidth = "*" 532 | 533 | [[package]] 534 | category = "dev" 535 | description = "Run a subprocess in a pseudo terminal" 536 | marker = "sys_platform != \"win32\"" 537 | name = "ptyprocess" 538 | optional = false 539 | python-versions = "*" 540 | version = "0.6.0" 541 | 542 | [[package]] 543 | category = "dev" 544 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 545 | name = "py" 546 | optional = false 547 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 548 | version = "1.8.1" 549 | 550 | [[package]] 551 | category = "dev" 552 | description = "Python style guide checker" 553 | name = "pycodestyle" 554 | optional = false 555 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 556 | version = "2.5.0" 557 | 558 | [[package]] 559 | category = "dev" 560 | description = "Python docstring style checker" 561 | name = "pydocstyle" 562 | optional = false 563 | python-versions = ">=3.5" 564 | version = "5.0.2" 565 | 566 | [package.dependencies] 567 | snowballstemmer = "*" 568 | 569 | [[package]] 570 | category = "dev" 571 | description = "passive checker of Python programs" 572 | name = "pyflakes" 573 | optional = false 574 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 575 | version = "2.1.1" 576 | 577 | [[package]] 578 | category = "dev" 579 | description = "Pygments is a syntax highlighting package written in Python." 580 | name = "pygments" 581 | optional = false 582 | python-versions = ">=3.5" 583 | version = "2.6.1" 584 | 585 | [[package]] 586 | category = "dev" 587 | description = "Python parsing module" 588 | name = "pyparsing" 589 | optional = false 590 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 591 | version = "2.4.6" 592 | 593 | [[package]] 594 | category = "dev" 595 | description = "pytest: simple powerful testing with Python" 596 | name = "pytest" 597 | optional = false 598 | python-versions = ">=3.5" 599 | version = "5.3.5" 600 | 601 | [package.dependencies] 602 | atomicwrites = ">=1.0" 603 | attrs = ">=17.4.0" 604 | colorama = "*" 605 | more-itertools = ">=4.0.0" 606 | packaging = "*" 607 | pluggy = ">=0.12,<1.0" 608 | py = ">=1.5.0" 609 | wcwidth = "*" 610 | 611 | [package.dependencies.importlib-metadata] 612 | python = "<3.8" 613 | version = ">=0.12" 614 | 615 | [package.extras] 616 | checkqa-mypy = ["mypy (v0.761)"] 617 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 618 | 619 | [[package]] 620 | category = "dev" 621 | description = "Pytest plugin for measuring coverage." 622 | name = "pytest-cov" 623 | optional = false 624 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 625 | version = "2.8.1" 626 | 627 | [package.dependencies] 628 | coverage = ">=4.4" 629 | pytest = ">=3.6" 630 | 631 | [package.extras] 632 | testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "virtualenv"] 633 | 634 | [[package]] 635 | category = "dev" 636 | description = "pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly)." 637 | name = "pytest-sugar" 638 | optional = false 639 | python-versions = "*" 640 | version = "0.9.2" 641 | 642 | [package.dependencies] 643 | packaging = ">=14.1" 644 | pytest = ">=2.9" 645 | termcolor = ">=1.1.0" 646 | 647 | [[package]] 648 | category = "dev" 649 | description = "Extensions to the standard Python datetime module" 650 | name = "python-dateutil" 651 | optional = false 652 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 653 | version = "2.8.1" 654 | 655 | [package.dependencies] 656 | six = ">=1.5" 657 | 658 | [[package]] 659 | category = "dev" 660 | description = "Python for Window Extensions" 661 | marker = "sys_platform == \"win32\"" 662 | name = "pywin32" 663 | optional = false 664 | python-versions = "*" 665 | version = "227" 666 | 667 | [[package]] 668 | category = "dev" 669 | description = "YAML parser and emitter for Python" 670 | name = "pyyaml" 671 | optional = false 672 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 673 | version = "5.3.1" 674 | 675 | [[package]] 676 | category = "dev" 677 | description = "Python bindings for 0MQ" 678 | name = "pyzmq" 679 | optional = false 680 | python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*" 681 | version = "19.0.0" 682 | 683 | [[package]] 684 | category = "dev" 685 | description = "Alternative regular expression module, to replace re." 686 | name = "regex" 687 | optional = false 688 | python-versions = "*" 689 | version = "2020.2.20" 690 | 691 | [[package]] 692 | category = "main" 693 | description = "Python HTTP for Humans." 694 | name = "requests" 695 | optional = false 696 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 697 | version = "2.23.0" 698 | 699 | [package.dependencies] 700 | certifi = ">=2017.4.17" 701 | chardet = ">=3.0.2,<4" 702 | idna = ">=2.5,<3" 703 | urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" 704 | 705 | [package.extras] 706 | security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] 707 | socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] 708 | 709 | [[package]] 710 | category = "dev" 711 | description = "Python 2 and 3 compatibility utilities" 712 | name = "six" 713 | optional = false 714 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 715 | version = "1.14.0" 716 | 717 | [[package]] 718 | category = "dev" 719 | description = "This package provides 26 stemmers for 25 languages generated from Snowball algorithms." 720 | name = "snowballstemmer" 721 | optional = false 722 | python-versions = "*" 723 | version = "2.0.0" 724 | 725 | [[package]] 726 | category = "dev" 727 | description = "ANSII Color formatting for output in terminal." 728 | name = "termcolor" 729 | optional = false 730 | python-versions = "*" 731 | version = "1.1.0" 732 | 733 | [[package]] 734 | category = "dev" 735 | description = "Python Library for Tom's Obvious, Minimal Language" 736 | name = "toml" 737 | optional = false 738 | python-versions = "*" 739 | version = "0.10.0" 740 | 741 | [[package]] 742 | category = "main" 743 | description = "Style preserving TOML library" 744 | name = "tomlkit" 745 | optional = false 746 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 747 | version = "0.5.11" 748 | 749 | [[package]] 750 | category = "dev" 751 | description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." 752 | name = "tornado" 753 | optional = false 754 | python-versions = ">= 3.5" 755 | version = "6.0.4" 756 | 757 | [[package]] 758 | category = "dev" 759 | description = "Traitlets Python config system" 760 | name = "traitlets" 761 | optional = false 762 | python-versions = "*" 763 | version = "4.3.3" 764 | 765 | [package.dependencies] 766 | decorator = "*" 767 | ipython-genutils = "*" 768 | six = "*" 769 | 770 | [package.extras] 771 | test = ["pytest", "mock"] 772 | 773 | [[package]] 774 | category = "dev" 775 | description = "a fork of Python 2 and 3 ast modules with type comment support" 776 | name = "typed-ast" 777 | optional = false 778 | python-versions = "*" 779 | version = "1.4.1" 780 | 781 | [[package]] 782 | category = "main" 783 | description = "HTTP library with thread-safe connection pooling, file post, and more." 784 | name = "urllib3" 785 | optional = false 786 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 787 | version = "1.25.8" 788 | 789 | [package.extras] 790 | brotli = ["brotlipy (>=0.6.0)"] 791 | secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] 792 | socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] 793 | 794 | [[package]] 795 | category = "dev" 796 | description = "Virtual Python Environment builder" 797 | name = "virtualenv" 798 | optional = false 799 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" 800 | version = "20.0.15" 801 | 802 | [package.dependencies] 803 | appdirs = ">=1.4.3,<2" 804 | distlib = ">=0.3.0,<1" 805 | filelock = ">=3.0.0,<4" 806 | six = ">=1.9.0,<2" 807 | 808 | [package.dependencies.importlib-metadata] 809 | python = "<3.8" 810 | version = ">=0.12,<2" 811 | 812 | [package.dependencies.importlib-resources] 813 | python = "<3.7" 814 | version = ">=1.0,<2" 815 | 816 | [package.extras] 817 | docs = ["sphinx (>=2.0.0,<3)", "sphinx-argparse (>=0.2.5,<1)", "sphinx-rtd-theme (>=0.4.3,<1)", "towncrier (>=19.9.0rc1)", "proselint (>=0.10.2,<1)"] 818 | testing = ["pytest (>=4.0.0,<6)", "coverage (>=4.5.1,<6)", "pytest-mock (>=2.0.0,<3)", "pytest-env (>=0.6.2,<1)", "pytest-timeout (>=1.3.4,<2)", "packaging (>=20.0)", "xonsh (>=0.9.13,<1)"] 819 | 820 | [[package]] 821 | category = "dev" 822 | description = "Measures number of Terminal column cells of wide-character codes" 823 | name = "wcwidth" 824 | optional = false 825 | python-versions = "*" 826 | version = "0.1.9" 827 | 828 | [[package]] 829 | category = "main" 830 | description = "Variables defined by the XDG Base Directory Specification" 831 | name = "xdg" 832 | optional = false 833 | python-versions = ">=3.6,<4.0" 834 | version = "4.0.1" 835 | 836 | [[package]] 837 | category = "dev" 838 | description = "Backport of pathlib-compatible object wrapper for zip files" 839 | marker = "python_version < \"3.8\"" 840 | name = "zipp" 841 | optional = false 842 | python-versions = ">=3.6" 843 | version = "3.1.0" 844 | 845 | [package.extras] 846 | docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] 847 | testing = ["jaraco.itertools", "func-timeout"] 848 | 849 | [metadata] 850 | content-hash = "7718607c54d9cd1460192953b56e8bfac7e95a68436c9e3a338331f42390a1fa" 851 | python-versions = "^3.6" 852 | 853 | [metadata.files] 854 | appdirs = [ 855 | {file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"}, 856 | {file = "appdirs-1.4.3.tar.gz", hash = "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92"}, 857 | ] 858 | appnope = [ 859 | {file = "appnope-0.1.0-py2.py3-none-any.whl", hash = "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0"}, 860 | {file = "appnope-0.1.0.tar.gz", hash = "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"}, 861 | ] 862 | "aspy.yaml" = [ 863 | {file = "aspy.yaml-1.3.0-py2.py3-none-any.whl", hash = "sha256:463372c043f70160a9ec950c3f1e4c3a82db5fca01d334b6bc89c7164d744bdc"}, 864 | {file = "aspy.yaml-1.3.0.tar.gz", hash = "sha256:e7c742382eff2caed61f87a39d13f99109088e5e93f04d76eb8d4b28aa143f45"}, 865 | ] 866 | atomicwrites = [ 867 | {file = "atomicwrites-1.3.0-py2.py3-none-any.whl", hash = "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4"}, 868 | {file = "atomicwrites-1.3.0.tar.gz", hash = "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"}, 869 | ] 870 | attrs = [ 871 | {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, 872 | {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, 873 | ] 874 | autopep8 = [ 875 | {file = "autopep8-1.5.tar.gz", hash = "sha256:0f592a0447acea0c2b0a9602be1e4e3d86db52badd2e3c84f0193bfd89fd3a43"}, 876 | ] 877 | backcall = [ 878 | {file = "backcall-0.1.0.tar.gz", hash = "sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4"}, 879 | {file = "backcall-0.1.0.zip", hash = "sha256:bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2"}, 880 | ] 881 | black = [ 882 | {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"}, 883 | {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"}, 884 | ] 885 | bump2version = [ 886 | {file = "bump2version-1.0.0-py2.py3-none-any.whl", hash = "sha256:477f0e18a0d58e50bb3dbc9af7fcda464fd0ebfc7a6151d8888602d7153171a0"}, 887 | {file = "bump2version-1.0.0.tar.gz", hash = "sha256:cd4f3a231305e405ed8944d8ff35bd742d9bc740ad62f483bd0ca21ce7131984"}, 888 | ] 889 | certifi = [ 890 | {file = "certifi-2019.11.28-py2.py3-none-any.whl", hash = "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3"}, 891 | {file = "certifi-2019.11.28.tar.gz", hash = "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"}, 892 | ] 893 | cfgv = [ 894 | {file = "cfgv-3.0.0-py2.py3-none-any.whl", hash = "sha256:f22b426ed59cd2ab2b54ff96608d846c33dfb8766a67f0b4a6ce130ce244414f"}, 895 | {file = "cfgv-3.0.0.tar.gz", hash = "sha256:04b093b14ddf9fd4d17c53ebfd55582d27b76ed30050193c14e560770c5360eb"}, 896 | ] 897 | chardet = [ 898 | {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, 899 | {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, 900 | ] 901 | click = [ 902 | {file = "click-7.1.1-py2.py3-none-any.whl", hash = "sha256:e345d143d80bf5ee7534056164e5e112ea5e22716bbb1ce727941f4c8b471b9a"}, 903 | {file = "click-7.1.1.tar.gz", hash = "sha256:8a18b4ea89d8820c5d0c7da8a64b2c324b4dabb695804dbfea19b9be9d88c0cc"}, 904 | ] 905 | click-option-group = [ 906 | {file = "click-option-group-0.3.0.tar.gz", hash = "sha256:36c7f30ef1f3c0d3d378aa8805004d93dec6e97010e4dae4ec66bde4e906c2fa"}, 907 | {file = "click_option_group-0.3.0-py3-none-any.whl", hash = "sha256:e5dab79e3be37ede08962946cc87804501c442a6106579b6a743474269603add"}, 908 | ] 909 | colorama = [ 910 | {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, 911 | {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, 912 | ] 913 | coverage = [ 914 | {file = "coverage-5.0.4-cp27-cp27m-macosx_10_12_x86_64.whl", hash = "sha256:8a620767b8209f3446197c0e29ba895d75a1e272a36af0786ec70fe7834e4307"}, 915 | {file = "coverage-5.0.4-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:73aa6e86034dad9f00f4bbf5a666a889d17d79db73bc5af04abd6c20a014d9c8"}, 916 | {file = "coverage-5.0.4-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:408ce64078398b2ee2ec08199ea3fcf382828d2f8a19c5a5ba2946fe5ddc6c31"}, 917 | {file = "coverage-5.0.4-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:cda33311cb9fb9323958a69499a667bd728a39a7aa4718d7622597a44c4f1441"}, 918 | {file = "coverage-5.0.4-cp27-cp27m-win32.whl", hash = "sha256:5f587dfd83cb669933186661a351ad6fc7166273bc3e3a1531ec5c783d997aac"}, 919 | {file = "coverage-5.0.4-cp27-cp27m-win_amd64.whl", hash = "sha256:9fad78c13e71546a76c2f8789623eec8e499f8d2d799f4b4547162ce0a4df435"}, 920 | {file = "coverage-5.0.4-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:2e08c32cbede4a29e2a701822291ae2bc9b5220a971bba9d1e7615312efd3037"}, 921 | {file = "coverage-5.0.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:922fb9ef2c67c3ab20e22948dcfd783397e4c043a5c5fa5ff5e9df5529074b0a"}, 922 | {file = "coverage-5.0.4-cp35-cp35m-macosx_10_12_x86_64.whl", hash = "sha256:c3fc325ce4cbf902d05a80daa47b645d07e796a80682c1c5800d6ac5045193e5"}, 923 | {file = "coverage-5.0.4-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:046a1a742e66d065d16fb564a26c2a15867f17695e7f3d358d7b1ad8a61bca30"}, 924 | {file = "coverage-5.0.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6ad6ca45e9e92c05295f638e78cd42bfaaf8ee07878c9ed73e93190b26c125f7"}, 925 | {file = "coverage-5.0.4-cp35-cp35m-win32.whl", hash = "sha256:eda55e6e9ea258f5e4add23bcf33dc53b2c319e70806e180aecbff8d90ea24de"}, 926 | {file = "coverage-5.0.4-cp35-cp35m-win_amd64.whl", hash = "sha256:4a8a259bf990044351baf69d3b23e575699dd60b18460c71e81dc565f5819ac1"}, 927 | {file = "coverage-5.0.4-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:f372cdbb240e09ee855735b9d85e7f50730dcfb6296b74b95a3e5dea0615c4c1"}, 928 | {file = "coverage-5.0.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a37c6233b28e5bc340054cf6170e7090a4e85069513320275a4dc929144dccf0"}, 929 | {file = "coverage-5.0.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:443be7602c790960b9514567917af538cac7807a7c0c0727c4d2bbd4014920fd"}, 930 | {file = "coverage-5.0.4-cp36-cp36m-win32.whl", hash = "sha256:165a48268bfb5a77e2d9dbb80de7ea917332a79c7adb747bd005b3a07ff8caf0"}, 931 | {file = "coverage-5.0.4-cp36-cp36m-win_amd64.whl", hash = "sha256:0a907199566269e1cfa304325cc3b45c72ae341fbb3253ddde19fa820ded7a8b"}, 932 | {file = "coverage-5.0.4-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:513e6526e0082c59a984448f4104c9bf346c2da9961779ede1fc458e8e8a1f78"}, 933 | {file = "coverage-5.0.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:3844c3dab800ca8536f75ae89f3cf566848a3eb2af4d9f7b1103b4f4f7a5dad6"}, 934 | {file = "coverage-5.0.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:641e329e7f2c01531c45c687efcec8aeca2a78a4ff26d49184dce3d53fc35014"}, 935 | {file = "coverage-5.0.4-cp37-cp37m-win32.whl", hash = "sha256:db1d4e38c9b15be1521722e946ee24f6db95b189d1447fa9ff18dd16ba89f732"}, 936 | {file = "coverage-5.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:62061e87071497951155cbccee487980524d7abea647a1b2a6eb6b9647df9006"}, 937 | {file = "coverage-5.0.4-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:65a7e00c00472cd0f59ae09d2fb8a8aaae7f4a0cf54b2b74f3138d9f9ceb9cb2"}, 938 | {file = "coverage-5.0.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1f66cf263ec77af5b8fe14ef14c5e46e2eb4a795ac495ad7c03adc72ae43fafe"}, 939 | {file = "coverage-5.0.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:85596aa5d9aac1bf39fe39d9fa1051b0f00823982a1de5766e35d495b4a36ca9"}, 940 | {file = "coverage-5.0.4-cp38-cp38-win32.whl", hash = "sha256:86a0ea78fd851b313b2e712266f663e13b6bc78c2fb260b079e8b67d970474b1"}, 941 | {file = "coverage-5.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:03f630aba2b9b0d69871c2e8d23a69b7fe94a1e2f5f10df5049c0df99db639a0"}, 942 | {file = "coverage-5.0.4-cp39-cp39-win32.whl", hash = "sha256:7c9762f80a25d8d0e4ab3cb1af5d9dffbddb3ee5d21c43e3474c84bf5ff941f7"}, 943 | {file = "coverage-5.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:4482f69e0701139d0f2c44f3c395d1d1d37abd81bfafbf9b6efbe2542679d892"}, 944 | {file = "coverage-5.0.4.tar.gz", hash = "sha256:1b60a95fc995649464e0cd48cecc8288bac5f4198f21d04b8229dc4097d76823"}, 945 | ] 946 | decorator = [ 947 | {file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"}, 948 | {file = "decorator-4.4.2.tar.gz", hash = "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"}, 949 | ] 950 | distlib = [ 951 | {file = "distlib-0.3.0.zip", hash = "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21"}, 952 | ] 953 | entrypoints = [ 954 | {file = "entrypoints-0.3-py2.py3-none-any.whl", hash = "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19"}, 955 | {file = "entrypoints-0.3.tar.gz", hash = "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"}, 956 | ] 957 | filelock = [ 958 | {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, 959 | {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, 960 | ] 961 | flake8 = [ 962 | {file = "flake8-3.7.9-py2.py3-none-any.whl", hash = "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca"}, 963 | {file = "flake8-3.7.9.tar.gz", hash = "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb"}, 964 | ] 965 | flake8-docstrings = [ 966 | {file = "flake8-docstrings-1.5.0.tar.gz", hash = "sha256:3d5a31c7ec6b7367ea6506a87ec293b94a0a46c0bce2bb4975b7f1d09b6f3717"}, 967 | {file = "flake8_docstrings-1.5.0-py2.py3-none-any.whl", hash = "sha256:a256ba91bc52307bef1de59e2a009c3cf61c3d0952dbe035d6ff7208940c2edc"}, 968 | ] 969 | identify = [ 970 | {file = "identify-1.4.13-py2.py3-none-any.whl", hash = "sha256:a7577a1f55cee1d21953a5cf11a3c839ab87f5ef909a4cba6cf52ed72b4c6059"}, 971 | {file = "identify-1.4.13.tar.gz", hash = "sha256:ab246293e6585a1c6361a505b68d5b501a0409310932b7de2c2ead667b564d89"}, 972 | ] 973 | idna = [ 974 | {file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"}, 975 | {file = "idna-2.9.tar.gz", hash = "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb"}, 976 | ] 977 | importlib-metadata = [ 978 | {file = "importlib_metadata-1.6.0-py2.py3-none-any.whl", hash = "sha256:2a688cbaa90e0cc587f1df48bdc97a6eadccdcd9c35fb3f976a09e3b5016d90f"}, 979 | {file = "importlib_metadata-1.6.0.tar.gz", hash = "sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e"}, 980 | ] 981 | importlib-resources = [ 982 | {file = "importlib_resources-1.4.0-py2.py3-none-any.whl", hash = "sha256:dd98ceeef3f5ad2ef4cc287b8586da4ebad15877f351e9688987ad663a0a29b8"}, 983 | {file = "importlib_resources-1.4.0.tar.gz", hash = "sha256:4019b6a9082d8ada9def02bece4a76b131518866790d58fdda0b5f8c603b36c2"}, 984 | ] 985 | ipykernel = [ 986 | {file = "ipykernel-5.2.0-py3-none-any.whl", hash = "sha256:39746b5f7d847a23fae4eac893e63e3d9cc5f8c3a4797fcd3bfa8d1a296ec6ed"}, 987 | {file = "ipykernel-5.2.0.tar.gz", hash = "sha256:37c65d2e2da3326e5cf114405df6d47d997b8a3eba99e2cc4b75833bf71a5e18"}, 988 | ] 989 | ipython = [ 990 | {file = "ipython-7.13.0-py3-none-any.whl", hash = "sha256:eb8d075de37f678424527b5ef6ea23f7b80240ca031c2dd6de5879d687a65333"}, 991 | {file = "ipython-7.13.0.tar.gz", hash = "sha256:ca478e52ae1f88da0102360e57e528b92f3ae4316aabac80a2cd7f7ab2efb48a"}, 992 | ] 993 | ipython-genutils = [ 994 | {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, 995 | {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, 996 | ] 997 | jedi = [ 998 | {file = "jedi-0.16.0-py2.py3-none-any.whl", hash = "sha256:b4f4052551025c6b0b0b193b29a6ff7bdb74c52450631206c262aef9f7159ad2"}, 999 | {file = "jedi-0.16.0.tar.gz", hash = "sha256:d5c871cb9360b414f981e7072c52c33258d598305280fef91c6cae34739d65d5"}, 1000 | ] 1001 | jinja2 = [ 1002 | {file = "Jinja2-2.11.1-py2.py3-none-any.whl", hash = "sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49"}, 1003 | {file = "Jinja2-2.11.1.tar.gz", hash = "sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250"}, 1004 | ] 1005 | jupyter-client = [ 1006 | {file = "jupyter_client-6.1.2-py3-none-any.whl", hash = "sha256:81c1c712de383bf6bf3dab6b407392b0d84d814c7bd0ce2c7035ead8b2ffea97"}, 1007 | {file = "jupyter_client-6.1.2.tar.gz", hash = "sha256:5724827aedb1948ed6ed15131372bc304a8d3ad9ac67ac19da7c95120d6b17e0"}, 1008 | ] 1009 | jupyter-core = [ 1010 | {file = "jupyter_core-4.6.3-py2.py3-none-any.whl", hash = "sha256:a4ee613c060fe5697d913416fc9d553599c05e4492d58fac1192c9a6844abb21"}, 1011 | {file = "jupyter_core-4.6.3.tar.gz", hash = "sha256:394fd5dd787e7c8861741880bdf8a00ce39f95de5d18e579c74b882522219e7e"}, 1012 | ] 1013 | markupsafe = [ 1014 | {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, 1015 | {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, 1016 | {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"}, 1017 | {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"}, 1018 | {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"}, 1019 | {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"}, 1020 | {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"}, 1021 | {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"}, 1022 | {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"}, 1023 | {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"}, 1024 | {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"}, 1025 | {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"}, 1026 | {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"}, 1027 | {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"}, 1028 | {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"}, 1029 | {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, 1030 | {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, 1031 | {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, 1032 | {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, 1033 | {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, 1034 | {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, 1035 | {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, 1036 | {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, 1037 | {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, 1038 | {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, 1039 | {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, 1040 | {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, 1041 | {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, 1042 | {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, 1043 | {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, 1044 | {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, 1045 | {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, 1046 | {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, 1047 | ] 1048 | mccabe = [ 1049 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 1050 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 1051 | ] 1052 | more-itertools = [ 1053 | {file = "more-itertools-8.2.0.tar.gz", hash = "sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507"}, 1054 | {file = "more_itertools-8.2.0-py3-none-any.whl", hash = "sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c"}, 1055 | ] 1056 | nodeenv = [ 1057 | {file = "nodeenv-1.3.5-py2.py3-none-any.whl", hash = "sha256:5b2438f2e42af54ca968dd1b374d14a1194848955187b0e5e4be1f73813a5212"}, 1058 | ] 1059 | packaging = [ 1060 | {file = "packaging-20.3-py2.py3-none-any.whl", hash = "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752"}, 1061 | {file = "packaging-20.3.tar.gz", hash = "sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3"}, 1062 | ] 1063 | parso = [ 1064 | {file = "parso-0.6.2-py2.py3-none-any.whl", hash = "sha256:8515fc12cfca6ee3aa59138741fc5624d62340c97e401c74875769948d4f2995"}, 1065 | {file = "parso-0.6.2.tar.gz", hash = "sha256:0c5659e0c6eba20636f99a04f469798dca8da279645ce5c387315b2c23912157"}, 1066 | ] 1067 | pathspec = [ 1068 | {file = "pathspec-0.7.0-py2.py3-none-any.whl", hash = "sha256:163b0632d4e31cef212976cf57b43d9fd6b0bac6e67c26015d611a647d5e7424"}, 1069 | {file = "pathspec-0.7.0.tar.gz", hash = "sha256:562aa70af2e0d434367d9790ad37aed893de47f1693e4201fd1d3dca15d19b96"}, 1070 | ] 1071 | pexpect = [ 1072 | {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, 1073 | {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, 1074 | ] 1075 | pickleshare = [ 1076 | {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, 1077 | {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, 1078 | ] 1079 | pluggy = [ 1080 | {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, 1081 | {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, 1082 | ] 1083 | pre-commit = [ 1084 | {file = "pre_commit-1.21.0-py2.py3-none-any.whl", hash = "sha256:f92a359477f3252452ae2e8d3029de77aec59415c16ae4189bcfba40b757e029"}, 1085 | {file = "pre_commit-1.21.0.tar.gz", hash = "sha256:8f48d8637bdae6fa70cc97db9c1dd5aa7c5c8bf71968932a380628c25978b850"}, 1086 | ] 1087 | prompt-toolkit = [ 1088 | {file = "prompt_toolkit-3.0.3-py3-none-any.whl", hash = "sha256:c93e53af97f630f12f5f62a3274e79527936ed466f038953dfa379d4941f651a"}, 1089 | {file = "prompt_toolkit-3.0.3.tar.gz", hash = "sha256:a402e9bf468b63314e37460b68ba68243d55b2f8c4d0192f85a019af3945050e"}, 1090 | ] 1091 | ptyprocess = [ 1092 | {file = "ptyprocess-0.6.0-py2.py3-none-any.whl", hash = "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"}, 1093 | {file = "ptyprocess-0.6.0.tar.gz", hash = "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0"}, 1094 | ] 1095 | py = [ 1096 | {file = "py-1.8.1-py2.py3-none-any.whl", hash = "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"}, 1097 | {file = "py-1.8.1.tar.gz", hash = "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa"}, 1098 | ] 1099 | pycodestyle = [ 1100 | {file = "pycodestyle-2.5.0-py2.py3-none-any.whl", hash = "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56"}, 1101 | {file = "pycodestyle-2.5.0.tar.gz", hash = "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"}, 1102 | ] 1103 | pydocstyle = [ 1104 | {file = "pydocstyle-5.0.2-py3-none-any.whl", hash = "sha256:da7831660b7355307b32778c4a0dbfb137d89254ef31a2b2978f50fc0b4d7586"}, 1105 | {file = "pydocstyle-5.0.2.tar.gz", hash = "sha256:f4f5d210610c2d153fae39093d44224c17429e2ad7da12a8b419aba5c2f614b5"}, 1106 | ] 1107 | pyflakes = [ 1108 | {file = "pyflakes-2.1.1-py2.py3-none-any.whl", hash = "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0"}, 1109 | {file = "pyflakes-2.1.1.tar.gz", hash = "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"}, 1110 | ] 1111 | pygments = [ 1112 | {file = "Pygments-2.6.1-py3-none-any.whl", hash = "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"}, 1113 | {file = "Pygments-2.6.1.tar.gz", hash = "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44"}, 1114 | ] 1115 | pyparsing = [ 1116 | {file = "pyparsing-2.4.6-py2.py3-none-any.whl", hash = "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"}, 1117 | {file = "pyparsing-2.4.6.tar.gz", hash = "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f"}, 1118 | ] 1119 | pytest = [ 1120 | {file = "pytest-5.3.5-py3-none-any.whl", hash = "sha256:ff615c761e25eb25df19edddc0b970302d2a9091fbce0e7213298d85fb61fef6"}, 1121 | {file = "pytest-5.3.5.tar.gz", hash = "sha256:0d5fe9189a148acc3c3eb2ac8e1ac0742cb7618c084f3d228baaec0c254b318d"}, 1122 | ] 1123 | pytest-cov = [ 1124 | {file = "pytest-cov-2.8.1.tar.gz", hash = "sha256:cc6742d8bac45070217169f5f72ceee1e0e55b0221f54bcf24845972d3a47f2b"}, 1125 | {file = "pytest_cov-2.8.1-py2.py3-none-any.whl", hash = "sha256:cdbdef4f870408ebdbfeb44e63e07eb18bb4619fae852f6e760645fa36172626"}, 1126 | ] 1127 | pytest-sugar = [ 1128 | {file = "pytest-sugar-0.9.2.tar.gz", hash = "sha256:fcd87a74b2bce5386d244b49ad60549bfbc4602527797fac167da147983f58ab"}, 1129 | {file = "pytest_sugar-0.9.2-py2.py3-none-any.whl", hash = "sha256:26cf8289fe10880cbbc130bd77398c4e6a8b936d8393b116a5c16121d95ab283"}, 1130 | ] 1131 | python-dateutil = [ 1132 | {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, 1133 | {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, 1134 | ] 1135 | pywin32 = [ 1136 | {file = "pywin32-227-cp27-cp27m-win32.whl", hash = "sha256:371fcc39416d736401f0274dd64c2302728c9e034808e37381b5e1b22be4a6b0"}, 1137 | {file = "pywin32-227-cp27-cp27m-win_amd64.whl", hash = "sha256:4cdad3e84191194ea6d0dd1b1b9bdda574ff563177d2adf2b4efec2a244fa116"}, 1138 | {file = "pywin32-227-cp35-cp35m-win32.whl", hash = "sha256:f4c5be1a293bae0076d93c88f37ee8da68136744588bc5e2be2f299a34ceb7aa"}, 1139 | {file = "pywin32-227-cp35-cp35m-win_amd64.whl", hash = "sha256:a929a4af626e530383a579431b70e512e736e9588106715215bf685a3ea508d4"}, 1140 | {file = "pywin32-227-cp36-cp36m-win32.whl", hash = "sha256:300a2db938e98c3e7e2093e4491439e62287d0d493fe07cce110db070b54c0be"}, 1141 | {file = "pywin32-227-cp36-cp36m-win_amd64.whl", hash = "sha256:9b31e009564fb95db160f154e2aa195ed66bcc4c058ed72850d047141b36f3a2"}, 1142 | {file = "pywin32-227-cp37-cp37m-win32.whl", hash = "sha256:47a3c7551376a865dd8d095a98deba954a98f326c6fe3c72d8726ca6e6b15507"}, 1143 | {file = "pywin32-227-cp37-cp37m-win_amd64.whl", hash = "sha256:31f88a89139cb2adc40f8f0e65ee56a8c585f629974f9e07622ba80199057511"}, 1144 | {file = "pywin32-227-cp38-cp38-win32.whl", hash = "sha256:7f18199fbf29ca99dff10e1f09451582ae9e372a892ff03a28528a24d55875bc"}, 1145 | {file = "pywin32-227-cp38-cp38-win_amd64.whl", hash = "sha256:7c1ae32c489dc012930787f06244426f8356e129184a02c25aef163917ce158e"}, 1146 | {file = "pywin32-227-cp39-cp39-win32.whl", hash = "sha256:c054c52ba46e7eb6b7d7dfae4dbd987a1bb48ee86debe3f245a2884ece46e295"}, 1147 | {file = "pywin32-227-cp39-cp39-win_amd64.whl", hash = "sha256:f27cec5e7f588c3d1051651830ecc00294f90728d19c3bf6916e6dba93ea357c"}, 1148 | ] 1149 | pyyaml = [ 1150 | {file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"}, 1151 | {file = "PyYAML-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76"}, 1152 | {file = "PyYAML-5.3.1-cp35-cp35m-win32.whl", hash = "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2"}, 1153 | {file = "PyYAML-5.3.1-cp35-cp35m-win_amd64.whl", hash = "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c"}, 1154 | {file = "PyYAML-5.3.1-cp36-cp36m-win32.whl", hash = "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2"}, 1155 | {file = "PyYAML-5.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648"}, 1156 | {file = "PyYAML-5.3.1-cp37-cp37m-win32.whl", hash = "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"}, 1157 | {file = "PyYAML-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf"}, 1158 | {file = "PyYAML-5.3.1-cp38-cp38-win32.whl", hash = "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97"}, 1159 | {file = "PyYAML-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee"}, 1160 | {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"}, 1161 | ] 1162 | pyzmq = [ 1163 | {file = "pyzmq-19.0.0-cp27-cp27m-macosx_10_9_intel.whl", hash = "sha256:3f12ce1e9cc9c31497bd82b207e8e86ccda9eebd8c9f95053aae46d15ccd2196"}, 1164 | {file = "pyzmq-19.0.0-cp27-cp27m-win32.whl", hash = "sha256:e8e4efb52ec2df8d046395ca4c84ae0056cf507b2f713ec803c65a8102d010de"}, 1165 | {file = "pyzmq-19.0.0-cp27-cp27m-win_amd64.whl", hash = "sha256:f5b6d015587a1d6f582ba03b226a9ddb1dfb09878b3be04ef48b01b7d4eb6b2a"}, 1166 | {file = "pyzmq-19.0.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:bb10361293d96aa92be6261fa4d15476bca56203b3a11c62c61bd14df0ef89ba"}, 1167 | {file = "pyzmq-19.0.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:4557d5e036e6d85715b4b9fdb482081398da1d43dc580d03db642b91605b409f"}, 1168 | {file = "pyzmq-19.0.0-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:84b91153102c4bcf5d0f57d1a66a0f03c31e9e6525a5f656f52fc615a675c748"}, 1169 | {file = "pyzmq-19.0.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:6aaaf90b420dc40d9a0e1996b82c6a0ff91d9680bebe2135e67c9e6d197c0a53"}, 1170 | {file = "pyzmq-19.0.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:ad48865a29efa8a0cecf266432ea7bc34e319954e55cf104be0319c177e6c8f5"}, 1171 | {file = "pyzmq-19.0.0-cp35-cp35m-win32.whl", hash = "sha256:32234c21c5e0a767c754181c8112092b3ddd2e2a36c3f76fc231ced817aeee47"}, 1172 | {file = "pyzmq-19.0.0-cp35-cp35m-win_amd64.whl", hash = "sha256:f37c29da2a5b0c5e31e6f8aab885625ea76c807082f70b2d334d3fd573c3100a"}, 1173 | {file = "pyzmq-19.0.0-cp36-cp36m-macosx_10_9_intel.whl", hash = "sha256:1e076ad5bd3638a18c376544d32e0af986ca10d43d4ce5a5d889a8649f0d0a3d"}, 1174 | {file = "pyzmq-19.0.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:f4d558bc5668d2345773a9ff8c39e2462dafcb1f6772a2e582fbced389ce527f"}, 1175 | {file = "pyzmq-19.0.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4f562dab21c03c7aa061f63b147a595dbe1006bf4f03213272fc9f7d5baec791"}, 1176 | {file = "pyzmq-19.0.0-cp36-cp36m-win32.whl", hash = "sha256:7f7e7b24b1d392bb5947ba91c981e7d1a43293113642e0d8870706c8e70cdc71"}, 1177 | {file = "pyzmq-19.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:75238d3c16cab96947705d5709187a49ebb844f54354cdf0814d195dd4c045de"}, 1178 | {file = "pyzmq-19.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cb3b7156ef6b1a119e68fbe3a54e0a0c40ecacc6b7838d57dd708c90b62a06dc"}, 1179 | {file = "pyzmq-19.0.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:a99ae601b4f6917985e9bb071549e30b6f93c72f5060853e197bdc4b7d357e5f"}, 1180 | {file = "pyzmq-19.0.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:242d949eb6b10197cda1d1cec377deab1d5324983d77e0d0bf9dc5eb6d71a6b4"}, 1181 | {file = "pyzmq-19.0.0-cp37-cp37m-win32.whl", hash = "sha256:a49fd42a29c1cc1aa9f461c5f2f5e0303adba7c945138b35ee7f4ab675b9f754"}, 1182 | {file = "pyzmq-19.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:5f10a31f288bf055be76c57710807a8f0efdb2b82be6c2a2b8f9a61f33a40cea"}, 1183 | {file = "pyzmq-19.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:26f4ae420977d2a8792d7c2d7bda43128b037b5eeb21c81951a94054ad8b8843"}, 1184 | {file = "pyzmq-19.0.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:944f6bb5c63140d76494467444fd92bebd8674236837480a3c75b01fe17df1ab"}, 1185 | {file = "pyzmq-19.0.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:b08e425cf93b4e018ab21dc8fdbc25d7d0502a23cc4fea2380010cf8cf11e462"}, 1186 | {file = "pyzmq-19.0.0-cp38-cp38-win32.whl", hash = "sha256:a1f957c20c9f51d43903881399b078cddcf710d34a2950e88bce4e494dcaa4d1"}, 1187 | {file = "pyzmq-19.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:bd1a769d65257a7a12e2613070ca8155ee348aa9183f2aadf1c8b8552a5510f5"}, 1188 | {file = "pyzmq-19.0.0-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:0bbc1728fe4314b4ca46249c33873a390559edac7c217ec7001b5e0c34a8fb7f"}, 1189 | {file = "pyzmq-19.0.0-pp36-pypy36_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5e071b834051e9ecb224915398f474bfad802c2fff883f118ff5363ca4ae3edf"}, 1190 | {file = "pyzmq-19.0.0.tar.gz", hash = "sha256:5e1f65e576ab07aed83f444e201d86deb01cd27dcf3f37c727bc8729246a60a8"}, 1191 | ] 1192 | regex = [ 1193 | {file = "regex-2020.2.20-cp27-cp27m-win32.whl", hash = "sha256:99272d6b6a68c7ae4391908fc15f6b8c9a6c345a46b632d7fdb7ef6c883a2bbb"}, 1194 | {file = "regex-2020.2.20-cp27-cp27m-win_amd64.whl", hash = "sha256:974535648f31c2b712a6b2595969f8ab370834080e00ab24e5dbb9d19b8bfb74"}, 1195 | {file = "regex-2020.2.20-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5de40649d4f88a15c9489ed37f88f053c15400257eeb18425ac7ed0a4e119400"}, 1196 | {file = "regex-2020.2.20-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:82469a0c1330a4beb3d42568f82dffa32226ced006e0b063719468dcd40ffdf0"}, 1197 | {file = "regex-2020.2.20-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d58a4fa7910102500722defbde6e2816b0372a4fcc85c7e239323767c74f5cbc"}, 1198 | {file = "regex-2020.2.20-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:f1ac2dc65105a53c1c2d72b1d3e98c2464a133b4067a51a3d2477b28449709a0"}, 1199 | {file = "regex-2020.2.20-cp36-cp36m-win32.whl", hash = "sha256:8c2b7fa4d72781577ac45ab658da44c7518e6d96e2a50d04ecb0fd8f28b21d69"}, 1200 | {file = "regex-2020.2.20-cp36-cp36m-win_amd64.whl", hash = "sha256:269f0c5ff23639316b29f31df199f401e4cb87529eafff0c76828071635d417b"}, 1201 | {file = "regex-2020.2.20-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:bed7986547ce54d230fd8721aba6fd19459cdc6d315497b98686d0416efaff4e"}, 1202 | {file = "regex-2020.2.20-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:046e83a8b160aff37e7034139a336b660b01dbfe58706f9d73f5cdc6b3460242"}, 1203 | {file = "regex-2020.2.20-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:b33ebcd0222c1d77e61dbcd04a9fd139359bded86803063d3d2d197b796c63ce"}, 1204 | {file = "regex-2020.2.20-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bba52d72e16a554d1894a0cc74041da50eea99a8483e591a9edf1025a66843ab"}, 1205 | {file = "regex-2020.2.20-cp37-cp37m-win32.whl", hash = "sha256:01b2d70cbaed11f72e57c1cfbaca71b02e3b98f739ce33f5f26f71859ad90431"}, 1206 | {file = "regex-2020.2.20-cp37-cp37m-win_amd64.whl", hash = "sha256:113309e819634f499d0006f6200700c8209a2a8bf6bd1bdc863a4d9d6776a5d1"}, 1207 | {file = "regex-2020.2.20-cp38-cp38-manylinux1_i686.whl", hash = "sha256:25f4ce26b68425b80a233ce7b6218743c71cf7297dbe02feab1d711a2bf90045"}, 1208 | {file = "regex-2020.2.20-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9b64a4cc825ec4df262050c17e18f60252cdd94742b4ba1286bcfe481f1c0f26"}, 1209 | {file = "regex-2020.2.20-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:9ff16d994309b26a1cdf666a6309c1ef51ad4f72f99d3392bcd7b7139577a1f2"}, 1210 | {file = "regex-2020.2.20-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:c7f58a0e0e13fb44623b65b01052dae8e820ed9b8b654bb6296bc9c41f571b70"}, 1211 | {file = "regex-2020.2.20-cp38-cp38-win32.whl", hash = "sha256:200539b5124bc4721247a823a47d116a7a23e62cc6695744e3eb5454a8888e6d"}, 1212 | {file = "regex-2020.2.20-cp38-cp38-win_amd64.whl", hash = "sha256:7f78f963e62a61e294adb6ff5db901b629ef78cb2a1cfce3cf4eeba80c1c67aa"}, 1213 | {file = "regex-2020.2.20.tar.gz", hash = "sha256:9e9624440d754733eddbcd4614378c18713d2d9d0dc647cf9c72f64e39671be5"}, 1214 | ] 1215 | requests = [ 1216 | {file = "requests-2.23.0-py2.py3-none-any.whl", hash = "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee"}, 1217 | {file = "requests-2.23.0.tar.gz", hash = "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"}, 1218 | ] 1219 | six = [ 1220 | {file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"}, 1221 | {file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"}, 1222 | ] 1223 | snowballstemmer = [ 1224 | {file = "snowballstemmer-2.0.0-py2.py3-none-any.whl", hash = "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0"}, 1225 | {file = "snowballstemmer-2.0.0.tar.gz", hash = "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"}, 1226 | ] 1227 | termcolor = [ 1228 | {file = "termcolor-1.1.0.tar.gz", hash = "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"}, 1229 | ] 1230 | toml = [ 1231 | {file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"}, 1232 | {file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"}, 1233 | {file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"}, 1234 | ] 1235 | tomlkit = [ 1236 | {file = "tomlkit-0.5.11-py2.py3-none-any.whl", hash = "sha256:4e1bd6c9197d984528f9ff0cc9db667c317d8881288db50db20eeeb0f6b0380b"}, 1237 | {file = "tomlkit-0.5.11.tar.gz", hash = "sha256:f044eda25647882e5ef22b43a1688fb6ab12af2fc50e8456cdfc751c873101cf"}, 1238 | ] 1239 | tornado = [ 1240 | {file = "tornado-6.0.4-cp35-cp35m-win32.whl", hash = "sha256:5217e601700f24e966ddab689f90b7ea4bd91ff3357c3600fa1045e26d68e55d"}, 1241 | {file = "tornado-6.0.4-cp35-cp35m-win_amd64.whl", hash = "sha256:c98232a3ac391f5faea6821b53db8db461157baa788f5d6222a193e9456e1740"}, 1242 | {file = "tornado-6.0.4-cp36-cp36m-win32.whl", hash = "sha256:5f6a07e62e799be5d2330e68d808c8ac41d4a259b9cea61da4101b83cb5dc673"}, 1243 | {file = "tornado-6.0.4-cp36-cp36m-win_amd64.whl", hash = "sha256:c952975c8ba74f546ae6de2e226ab3cc3cc11ae47baf607459a6728585bb542a"}, 1244 | {file = "tornado-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:2c027eb2a393d964b22b5c154d1a23a5f8727db6fda837118a776b29e2b8ebc6"}, 1245 | {file = "tornado-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:5618f72e947533832cbc3dec54e1dffc1747a5cb17d1fd91577ed14fa0dc081b"}, 1246 | {file = "tornado-6.0.4-cp38-cp38-win32.whl", hash = "sha256:22aed82c2ea340c3771e3babc5ef220272f6fd06b5108a53b4976d0d722bcd52"}, 1247 | {file = "tornado-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:c58d56003daf1b616336781b26d184023ea4af13ae143d9dda65e31e534940b9"}, 1248 | {file = "tornado-6.0.4.tar.gz", hash = "sha256:0fe2d45ba43b00a41cd73f8be321a44936dc1aba233dee979f17a042b83eb6dc"}, 1249 | ] 1250 | traitlets = [ 1251 | {file = "traitlets-4.3.3-py2.py3-none-any.whl", hash = "sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44"}, 1252 | {file = "traitlets-4.3.3.tar.gz", hash = "sha256:d023ee369ddd2763310e4c3eae1ff649689440d4ae59d7485eb4cfbbe3e359f7"}, 1253 | ] 1254 | typed-ast = [ 1255 | {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, 1256 | {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"}, 1257 | {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"}, 1258 | {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"}, 1259 | {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, 1260 | {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, 1261 | {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, 1262 | {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, 1263 | {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, 1264 | {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, 1265 | {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, 1266 | {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, 1267 | {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, 1268 | {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, 1269 | {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, 1270 | {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, 1271 | {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, 1272 | {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, 1273 | {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, 1274 | {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, 1275 | {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, 1276 | ] 1277 | urllib3 = [ 1278 | {file = "urllib3-1.25.8-py2.py3-none-any.whl", hash = "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc"}, 1279 | {file = "urllib3-1.25.8.tar.gz", hash = "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"}, 1280 | ] 1281 | virtualenv = [ 1282 | {file = "virtualenv-20.0.15-py2.py3-none-any.whl", hash = "sha256:4e399f48c6b71228bf79f5febd27e3bbb753d9d5905776a86667bc61ab628a25"}, 1283 | {file = "virtualenv-20.0.15.tar.gz", hash = "sha256:9e81279f4a9d16d1c0654a127c2c86e5bca2073585341691882c1e66e31ef8a5"}, 1284 | ] 1285 | wcwidth = [ 1286 | {file = "wcwidth-0.1.9-py2.py3-none-any.whl", hash = "sha256:cafe2186b3c009a04067022ce1dcd79cb38d8d65ee4f4791b8888d6599d1bbe1"}, 1287 | {file = "wcwidth-0.1.9.tar.gz", hash = "sha256:ee73862862a156bf77ff92b09034fc4825dd3af9cf81bc5b360668d425f3c5f1"}, 1288 | ] 1289 | xdg = [ 1290 | {file = "xdg-4.0.1-py3-none-any.whl", hash = "sha256:bf9032b027e3061d38c362a21b14dcf057a5b5a4906956f8e8278cefdf73f38b"}, 1291 | {file = "xdg-4.0.1.tar.gz", hash = "sha256:c939c99def394cbaf765a3ee55efd6ea7e4c5eaed8d9ebc2d03af84ba35dec57"}, 1292 | ] 1293 | zipp = [ 1294 | {file = "zipp-3.1.0-py3-none-any.whl", hash = "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b"}, 1295 | {file = "zipp-3.1.0.tar.gz", hash = "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"}, 1296 | ] 1297 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "zotnote" 3 | version = "0.3.5" 4 | description = "Streamlining reading notes with Zotero" 5 | authors = [ 6 | "Asura Enkhbayar " 7 | ] 8 | license = "MIT" 9 | packages = [ 10 | { include = "zotnote", from="src" } 11 | ] 12 | 13 | keywords = ['zotero', 'literature-review', 'reading-notes'] 14 | readme = 'README.md' 15 | repository = "https://github.com/bubblbu/zotnote" 16 | homepage = "https://github.com/bubblbu/zotnote" 17 | 18 | [tool.poetry.scripts] 19 | zotnote = "zotnote.app:main" 20 | 21 | [tool.poetry.dependencies] 22 | python = "^3.6" 23 | click = "^7.1.1" 24 | jinja2 = "^2.11.1" 25 | xdg = "^4.0.1" 26 | tomlkit = "^0.5.11" 27 | requests = "^2.23.0" 28 | click-option-group = "^0.3.0" 29 | 30 | [tool.poetry.dev-dependencies] 31 | flake8 = "^3.7.9" 32 | autopep8 = "^1.5" 33 | pytest = "5.3.*" 34 | pytest-sugar = "^0.9.2" 35 | pytest-cov = "^2.8.1" 36 | black = "^19.10b0" 37 | flake8-docstrings = "^1.5.0" 38 | bump2version = "^1.0.0" 39 | pre-commit = "^1.10" 40 | ipykernel = "^5.2.0" 41 | 42 | [tool.isort] 43 | line_length = 88 44 | force_single_line = true 45 | 46 | [build-system] 47 | requires = ["poetry>=0.12"] 48 | build-backend = "poetry.masonry.api" 49 | -------------------------------------------------------------------------------- /src/zotnote/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Some definitions for the whole package.""" 3 | from pathlib import Path 4 | 5 | from xdg import XDG_CONFIG_HOME 6 | 7 | ROOT = Path(__file__).resolve().parent 8 | config_dir = Path(XDG_CONFIG_HOME) / "zotnote" 9 | -------------------------------------------------------------------------------- /src/zotnote/__main__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Main module.""" 3 | from .app import main 4 | 5 | if __name__ == "__main__": 6 | main() 7 | -------------------------------------------------------------------------------- /src/zotnote/__version__.py: -------------------------------------------------------------------------------- 1 | """Current version.""" 2 | __version__ = "0.3.5" 3 | -------------------------------------------------------------------------------- /src/zotnote/app.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """This module is the main entry point for the app.""" 3 | from .cli import add 4 | from .cli import cli 5 | from .cli import config 6 | from .cli import edit 7 | from .cli import remove 8 | from .cli import templates 9 | from .config.config import Configuration 10 | 11 | 12 | def main(): 13 | """Load config and launch CLI.""" 14 | # Check if all configuration files are in place 15 | Configuration.validate() 16 | 17 | # Add commands to CLI 18 | cli.add_command(add) 19 | cli.add_command(config) 20 | cli.add_command(edit) 21 | cli.add_command(remove) 22 | cli.add_command(templates) 23 | # cli.add_command(report) 24 | 25 | # Launch CLI 26 | cli(prog_name="zotnote") 27 | -------------------------------------------------------------------------------- /src/zotnote/cli.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """The main CLI module.""" 3 | import os 4 | import sys 5 | from pathlib import Path 6 | 7 | import click 8 | from click_option_group import RequiredMutuallyExclusiveOptionGroup 9 | from click_option_group import optgroup 10 | 11 | from .config.config import Configuration 12 | from .connectors.bbt import BetterBibtex 13 | from .connectors.bbt import BetterBibtexNotRunning 14 | from .notes.note import BadTemplateName 15 | from .notes.note import Note 16 | from .utils.helpers import citekey_regex 17 | 18 | 19 | def create_note(citekey, config, bbt, force, template): 20 | """Create reading note for CITEKEY in your Zotero library.""" 21 | candidates = bbt.search_citekey_in_bbp(citekey) 22 | if not candidates: 23 | click.echo("No results found for " + citekey) 24 | sys.exit() 25 | elif len(candidates) != 1: 26 | click.echo("Something wrong happened here. We have too many candidates...") 27 | sys.exit() 28 | else: 29 | candidate = candidates[0] 30 | fieldValues = bbt.extract_fields(candidate) 31 | 32 | # Fill template 33 | try: 34 | note = Note(citekey, fieldValues, config, template) 35 | except BadTemplateName as e: 36 | click.echo(e) 37 | sys.exit() 38 | 39 | # Write output file 40 | notes_dir = Path(config["notes"]) 41 | outfile = notes_dir / f"{citekey}.md" 42 | 43 | if outfile.exists(): 44 | if force: 45 | click.echo(f"Overwriting {str(outfile)}") 46 | else: 47 | choice = click.confirm( 48 | "This file already exists. Edit instead?" 49 | "Use --force to overwrite files." 50 | ) 51 | if choice: 52 | os.system(f"{config['editor']} {str(outfile)}") 53 | else: 54 | click.echo(f"Writing {str(outfile)}") 55 | 56 | # Write note 57 | outfile.write_text(note.render()) 58 | 59 | 60 | @click.command() 61 | @click.argument("citekey", required=False) 62 | @click.option( 63 | "-t", 64 | "--template", 65 | default="simple", 66 | help="Template for note layout", 67 | metavar="TEMPLATE", 68 | ) 69 | @click.option("-f", "--force", is_flag=True, help="Overwrite existing notes") 70 | def add(citekey, force, template): 71 | """ 72 | Create a new note. If no citekey is provided the Zotero picker is launched. 73 | 74 | CITEKEY is the cite key created by Better Bibtex. 75 | TEMPLATE is the type of the note created. 76 | 77 | See `templates` command for more details. 78 | """ 79 | config = Configuration.load_config() 80 | 81 | try: 82 | bbt = BetterBibtex(config) 83 | except BetterBibtexNotRunning as e: 84 | click.echo(e) 85 | sys.exit() 86 | 87 | if citekey: 88 | match = citekey_regex.match(citekey) 89 | if match is None: 90 | click.echo("The citekey provided is not valid") 91 | sys.exit() 92 | else: 93 | citekey = bbt.citation_picker() 94 | if citekey is None: 95 | click.echo("No citation key provided.") 96 | sys.exit() 97 | 98 | create_note(citekey, config, bbt, force, template) 99 | 100 | 101 | @click.command() 102 | @click.argument("citekey", required=False) 103 | def edit(citekey): 104 | """ 105 | Open a note in your editor of choice. 106 | 107 | CITEKEY is the cite key created by BBT. 108 | """ 109 | config = Configuration.load_config() 110 | 111 | try: 112 | bbt = BetterBibtex(config) 113 | except BetterBibtexNotRunning as e: 114 | click.echo(e) 115 | sys.exit() 116 | 117 | if citekey: 118 | match = citekey_regex.match(citekey) 119 | if match is None: 120 | click.echo("The citekey provided is not valid") 121 | sys.exit() 122 | else: 123 | citekey = bbt.citation_picker() 124 | if citekey is None: 125 | sys.exit() 126 | 127 | # Write output file 128 | notes_dir = Path(config["notes"]) 129 | outfile = notes_dir / f"{citekey}.md" 130 | 131 | if outfile.exists(): 132 | os.system(f"{config['editor']} {str(outfile)}") 133 | else: 134 | choice = click.confirm("File does not exist yet. Create now?") 135 | if choice: 136 | create_note(citekey, config) 137 | else: 138 | sys.exit() 139 | 140 | 141 | @click.command(help="Remove a note") 142 | @click.argument("citekey", required=False) 143 | def remove(citekey): 144 | """Remove a note. 145 | 146 | CITEKEY is the cite key created by BBT. 147 | """ 148 | config = Configuration.load_config() 149 | 150 | try: 151 | bbt = BetterBibtex(config) 152 | except BetterBibtexNotRunning as e: 153 | click.echo(e) 154 | sys.exit() 155 | 156 | if citekey: 157 | match = citekey_regex.match(citekey) 158 | if match is None: 159 | click.echo("The citekey provided is not valid") 160 | sys.exit() 161 | else: 162 | citekey = bbt.citation_picker() 163 | if citekey is None: 164 | sys.exit() 165 | 166 | # Write output file 167 | notes_dir = Path(config["notes"]) 168 | outfile = notes_dir / f"{citekey}.md" 169 | 170 | if outfile.exists(): 171 | choice = click.confirm("Are you sure you want to delete this note?") 172 | if choice: 173 | outfile.unlink() 174 | else: 175 | sys.exit() 176 | else: 177 | click.echo("This note does not exist.") 178 | 179 | 180 | @click.command() 181 | def templates(): 182 | """List all available templates for notes.""" 183 | config = Configuration.load_config() 184 | 185 | templates = Note.list_all_templates(config) 186 | 187 | for t in templates: 188 | if t == "simple": 189 | click.echo(f"{t} (default)") 190 | else: 191 | click.echo(t) 192 | 193 | 194 | @click.command() 195 | @optgroup( 196 | "Edit configuration", 197 | cls=RequiredMutuallyExclusiveOptionGroup, 198 | help="Interact with the configuration", 199 | ) 200 | @optgroup.option( 201 | "-l", "--list", is_flag=True, help="List all config key/value pairs", 202 | ) 203 | @optgroup.option("-r", "--reset", is_flag=True, help="Reset config.") 204 | @optgroup.option( 205 | "-u", 206 | "--update-entry", 207 | metavar="ENTRY", 208 | help="Update an ENTRY in the config file.", 209 | type=str, 210 | ) 211 | def config(list, reset, update_entry): 212 | """Configure Zotnote from the command line.""" 213 | config = Configuration.load_config() 214 | 215 | if list: 216 | for k, v in config.items(): 217 | click.echo(f"{k}: {v}") 218 | elif reset: 219 | Configuration.create_config() 220 | elif update_entry is not None: 221 | if update_entry in config: 222 | click.echo(f"Old value: {config[update_entry]}") 223 | value = click.prompt("New value") 224 | Configuration.update_config(update_entry, value) 225 | else: 226 | click.echo(f"{update_entry} is not a valid entry in the config.") 227 | 228 | 229 | @click.command() 230 | def report(): 231 | """Create a small, basic report based on the notes.""" 232 | NotImplemented 233 | 234 | 235 | @click.group() 236 | def cli(): 237 | """CLI for ZotNote.""" 238 | pass 239 | -------------------------------------------------------------------------------- /src/zotnote/config/config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """A module that manages all things config.""" 3 | 4 | import click 5 | from tomlkit import dumps 6 | from tomlkit import loads 7 | from zotnote import ROOT 8 | from zotnote import config_dir 9 | 10 | 11 | class Configuration: 12 | """A helper class to manage configuration.""" 13 | 14 | exmpl_cfg_file = ROOT / "config/config.toml.example" 15 | config_file = config_dir / "config.toml" 16 | 17 | @classmethod 18 | def validate(cls): 19 | """Validate local configuration & create missing folders and files.""" 20 | # Make sure the config file is placed in XDG_CONFIG_HOME 21 | if not cls.config_file.exists(): 22 | click.echo("Missing configuration file. Proceeding to create a new one.") 23 | cls.__create_dir(config_dir) 24 | cls.create_config() 25 | 26 | @classmethod 27 | def load_config(cls): 28 | """Load configuration file.""" 29 | return loads(cls.config_file.read_text()) 30 | 31 | @classmethod 32 | def create_config(cls): 33 | """Create new configuration.""" 34 | if cls.config_file.exists(): 35 | ow = click.confirm( 36 | "Do you really want to create a new config? " 37 | "This will overwrite your existing config." 38 | ) 39 | if ow: 40 | config = cls.__new_config() 41 | else: 42 | config = cls.__new_config() 43 | cls.config_file.write_text(dumps(config)) 44 | 45 | @classmethod 46 | def update_config(cls, key, value): 47 | """Update a single value in the config.""" 48 | config = loads(cls.config_file.read_text()) 49 | config[key] = value 50 | cls.config_file.write_text(dumps(config)) 51 | 52 | @classmethod 53 | def __new_config(cls): 54 | """Load example configuration and populate interactively.""" 55 | exmpl_config = loads(cls.exmpl_cfg_file.read_text()) 56 | config = exmpl_config 57 | 58 | config["name"] = click.prompt("Enter your name") 59 | config["email"] = click.prompt("Enter your email") 60 | 61 | config["editor"] = click.prompt("The command to execute your editor of choice") 62 | 63 | config["notes"] = click.prompt("Enter location for your notes") 64 | return config 65 | 66 | @staticmethod 67 | def __create_dir(dir): 68 | """Create directory if missing.""" 69 | if not dir.exists(): 70 | dir.mkdir(parents=True) 71 | -------------------------------------------------------------------------------- /src/zotnote/config/config.toml.example: -------------------------------------------------------------------------------- 1 | # Profile 2 | name = "your name" 3 | email = "your_email" 4 | 5 | # Settings 6 | editor = "your_editor_of_choice" 7 | 8 | # Directories 9 | notes = "/path/to/your/notes/" 10 | -------------------------------------------------------------------------------- /src/zotnote/connectors/bbt.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """This module contains code to interface with Better Bibtex.""" 3 | import json 4 | 5 | import requests 6 | from zotnote.utils.helpers import prune_author_str 7 | 8 | 9 | class BetterBibtexNotRunning(Exception): 10 | """This error is thrown when BBT is not running.""" 11 | 12 | pass 13 | 14 | 15 | class BetterBibtex: 16 | """Wrapper class to access and manage BetterBibtex.""" 17 | 18 | BASE_URL = "http://localhost:23119/better-bibtex/" 19 | 20 | SEARCH_URL = BASE_URL + "json-rpc" 21 | CAYW_URL = BASE_URL + "cayw" 22 | 23 | def __init__(self, config): 24 | """Initialise BBT. 25 | 26 | Sets up the requests for the JSON-RPC endpoints and check is BBT is running 27 | """ 28 | self.headers = { 29 | "Content-Type": "application/json", 30 | "Accept": "application/json", 31 | } 32 | self.payload = [{"jsonrpc": "2.0", "method": "item.search", "params": None}] 33 | 34 | self.selected_fields = ["title", "DOI", "type", "issued", "author"] 35 | 36 | self.author_str_len = 60 37 | 38 | if not self.probe_bbt(): 39 | raise BetterBibtexNotRunning( 40 | "Better Bibtex is not running. Please make sure to launch Zotero BBT" 41 | ) 42 | 43 | def probe_bbt(self): 44 | """Check if Zotero & BBT are running.""" 45 | r = requests.get(BetterBibtex.CAYW_URL + "?probe=probe") 46 | if r.text == "ready": 47 | return True 48 | else: 49 | return False 50 | 51 | def citation_picker(self): 52 | """Launch the Zotero citation picker and return result.""" 53 | r = requests.get(BetterBibtex.CAYW_URL) 54 | return r.text 55 | 56 | def search_citekey_in_bbp(self, citekey): 57 | """Search the endpoint with a citekey. Returns all candidates.""" 58 | payload = self.payload 59 | payload[0]["params"] = [f"{citekey}"] 60 | payload = json.dumps(payload) 61 | 62 | r = requests.post(BetterBibtex.SEARCH_URL, data=payload, headers=self.headers) 63 | if r.status_code == 200: 64 | candidates = r.json()[0]["result"] 65 | return candidates 66 | else: 67 | return None 68 | 69 | def extract_fields(self, candidate): 70 | """ 71 | Pretty simple function that retrieves the article information. 72 | 73 | Returns a dict defined by selected fields. 74 | """ 75 | article = {f: None for f in self.selected_fields} 76 | 77 | for f in self.selected_fields: 78 | if f in candidate: 79 | if f == "author": 80 | author_str = [] 81 | for name in candidate[f]: 82 | name_str = f"{name['family']}, {name['given']}" 83 | author_str.append(name_str) 84 | author_str = "; ".join(author_str) 85 | if len(author_str) >= self.author_str_len: 86 | author_str = prune_author_str(author_str, self.author_str_len) 87 | article[f] = author_str 88 | elif f == "issued": 89 | year = candidate[f]["date-parts"][0][0] 90 | article[f] = year 91 | else: 92 | article[f] = candidate[f] 93 | return article 94 | -------------------------------------------------------------------------------- /src/zotnote/notes/note.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | This module manages notes. 4 | 5 | - Read and write notes 6 | - Manage templates 7 | - Export notes to different formats 8 | """ 9 | 10 | import datetime 11 | from pathlib import Path 12 | 13 | from jinja2 import Template 14 | from zotnote import ROOT 15 | 16 | 17 | class BadTemplateName(Exception): 18 | """This exception is thrown when a BadTemplate is loaded.""" 19 | 20 | pass 21 | 22 | 23 | class Note: 24 | """The Note class.""" 25 | 26 | templates_dir = ROOT / "notes/templates" 27 | content_dir = templates_dir / "content" 28 | 29 | # Load the master template 30 | master_file = templates_dir / "master.j2" 31 | 32 | def __init__(self, citekey, fieldValues, config, note_type): 33 | """Construct note with citekey and retrieved values.""" 34 | self.citekey = citekey 35 | self.fieldValues = fieldValues 36 | 37 | self.ts = datetime.datetime.now() 38 | self.ts_iso = self.ts.isoformat(timespec="seconds") 39 | self.ts_day = self.ts.strftime("%m.%d.%y") 40 | 41 | self.author = config["name"] 42 | self.email = config["email"] 43 | 44 | notes_dir = Path(config["notes"]) 45 | self.user_templates = notes_dir / "templates" 46 | 47 | # Ensure that content type is set and valid 48 | self.content_template = None 49 | 50 | # Check if user template is present in notes folder 51 | files = self.user_templates.glob("*.j2") 52 | for f in files: 53 | if note_type + ".j2" == f.name: 54 | self.content_template = f 55 | 56 | # If not found, check in system templates 57 | if self.content_template is None: 58 | files = self.content_dir.glob("*.j2") 59 | for f in files: 60 | if note_type + ".j2" == f.name: 61 | self.content_template = f 62 | 63 | # If content_template is still not found: 64 | if self.content_template is None: 65 | raise BadTemplateName("Invalid template name.") 66 | 67 | def render(self): 68 | """Render note with template and save to disk.""" 69 | master = Template(self.master_file.read_text()) 70 | content = Template(self.content_template.read_text()) 71 | 72 | # Render content 73 | d = { 74 | "citekey": self.citekey, 75 | "author": self.author, 76 | "ts": self.ts_iso, 77 | "ts_day": self.ts_day, 78 | "title": self.fieldValues["title"], 79 | "creator": self.fieldValues["author"], 80 | "date": self.fieldValues["issued"], 81 | "doi": self.fieldValues["DOI"], 82 | "type": self.fieldValues["type"], 83 | } 84 | rendered = master.render(d) + "\n\n" + content.render() 85 | 86 | return rendered 87 | 88 | @classmethod 89 | def list_all_templates(cls, config): 90 | notes_dir = Path(config["notes"]) 91 | user_templates = notes_dir / "templates" 92 | 93 | templates = [] 94 | # Check if user template is present in notes folder 95 | files = user_templates.glob("*.j2") 96 | templates.extend([f.name.split(".")[0] for f in files]) 97 | 98 | files = cls.content_dir.glob("*.j2") 99 | templates.extend([f.name.split(".")[0] for f in files]) 100 | 101 | return set(templates) 102 | -------------------------------------------------------------------------------- /src/zotnote/notes/templates/content/empirical.j2: -------------------------------------------------------------------------------- 1 | ## Detailed summary 2 | 3 | ### Context 4 | 5 | ### Questions-Hypothesis 6 | 7 | ### Methods 8 | 9 | ### Theory 10 | 11 | ### Conclusions 12 | 13 | ## Thoughts 14 | 15 | ### Problems 16 | -------------------------------------------------------------------------------- /src/zotnote/notes/templates/content/simple.j2: -------------------------------------------------------------------------------- 1 | ## Notes 2 | -------------------------------------------------------------------------------- /src/zotnote/notes/templates/master.j2: -------------------------------------------------------------------------------- 1 | --- 2 | key: {{ citekey }} 3 | date: {{ ts }} 4 | author: {{ author }} 5 | --- 6 | 7 | # {{ title }} 8 | 9 | *Reading Notes by {{ author }} | {{ ts_day }}* 10 | 11 | | Zotero field | Value | 12 | | --- | --- | 13 | | Creators | {{ creator }} | 14 | | Citekey | {{ citekey }} | 15 | | Type | {{ type }} | 16 | | DOI | {% if doi %} [{{ doi }}](https://doi.org/{{ doi }}) {% else %} {% endif %} | 17 | | Date | {{ date }} | 18 | 19 | *Keywords*: 20 | 21 | --- 22 | 23 | ## Summary 24 | 25 | --- 26 | -------------------------------------------------------------------------------- /src/zotnote/utils/helpers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Various utility functions.""" 3 | 4 | import re 5 | 6 | citekey_regex = re.compile(r"^[a-zA-Z0-9_-]+$") 7 | 8 | 9 | def prune_author_str(author_str, maxlen): 10 | """Shorten author string. 11 | 12 | If len(AUTHOR_STR) > MAXLEN the authors are shortened 13 | using an ellipsis while retaining the last author. 14 | """ 15 | strings = author_str.split("; ") 16 | string = "; ".join(strings[0:-1])[0:maxlen] 17 | string = string + "...; " + strings[-1] 18 | return string 19 | -------------------------------------------------------------------------------- /tests/test_bbt.py: -------------------------------------------------------------------------------- 1 | # from zotnote.connectors.bbt import BetterBibtex 2 | 3 | 4 | def test_bbt(): 5 | pass 6 | # bbt = BetterBibtex() 7 | -------------------------------------------------------------------------------- /tests/test_cli.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from zotnote.cli import remove 3 | 4 | 5 | @pytest.fixture 6 | def config(): 7 | pass 8 | 9 | 10 | def test_remove_note_bad_citekey(): 11 | citekey = "ahahaha" 12 | 13 | with pytest.raises(SystemExit): 14 | remove(citekey) 15 | -------------------------------------------------------------------------------- /tests/test_config.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bubblbu/zotnote/5e27bc79a80d362f61f35c72c1c773551f1274f5/tests/test_config.py --------------------------------------------------------------------------------