Initial commit! 🌠
This commit is contained in:
commit
4f8acc86df
|
@ -0,0 +1,129 @@
|
|||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
pip-wheel-metadata/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License
|
||||
|
||||
Copyright (c) 2021 Holllo <helllo@holllo.cc>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,35 @@
|
|||
# opyml
|
||||
|
||||
An OPML library for Python.
|
||||
|
||||
This is a largely identical "port" of [the Rust crate](https://github.com/Holllo/opml).
|
||||
|
||||
## Example
|
||||
|
||||
```python
|
||||
from opyml import OPML, Outline
|
||||
|
||||
# Create OPML documents from scratch.
|
||||
document = OPML()
|
||||
document.body.outlines.append(Outline(text="Example"))
|
||||
|
||||
# Convert documents to XML.
|
||||
xml = document.to_xml()
|
||||
|
||||
# Parse OPML documents from XML.
|
||||
document = OPML.from_xml(xml)
|
||||
```
|
||||
|
||||
For complete examples check out the `tests/` directory.
|
||||
|
||||
## Development
|
||||
|
||||
* Install dependencies with [Poetry](https://python-poetry.org) (`poetry shell` + `poetry install`).
|
||||
* Format code with `black opyml tests`.
|
||||
* Check types with `mypy opyml`.
|
||||
* Run tests and collect coverage with `pytest --cov=opyml --cov-report html`.
|
||||
* Generate documentation with `pdoc opyml`.
|
||||
|
||||
## License
|
||||
|
||||
Dual-licensed with the [Apache License, Version 2.0](https://github.com/Holllo/opyml/blob/main/LICENSE-Apache) and [MIT license](https://github.com/Holllo/opyml/blob/main/LICENSE-MIT).
|
|
@ -0,0 +1,21 @@
|
|||
"""
|
||||
.. include:: ../README.md
|
||||
"""
|
||||
|
||||
from .body import Body
|
||||
from .head import Head
|
||||
from .opml import OPML
|
||||
from .outline import Outline
|
||||
|
||||
__version__ = "0.1.0"
|
||||
|
||||
# Export everything at the top-level.
|
||||
__all__ = [
|
||||
"Body",
|
||||
"Head",
|
||||
"OPML",
|
||||
"Outline",
|
||||
]
|
||||
|
||||
# Set docformat for pdoc.
|
||||
__docformat__ = "restructuredtext"
|
|
@ -0,0 +1,41 @@
|
|||
import json
|
||||
|
||||
from defusedxml import ElementTree
|
||||
from typing import List, Optional
|
||||
from xml.etree import ElementTree as XmlBuilder
|
||||
|
||||
from .outline import Outline
|
||||
from .util import _dict_exclude_none
|
||||
|
||||
|
||||
class Body:
|
||||
"""The OPML Body element."""
|
||||
|
||||
def __init__(self, outlines: Optional[List[Outline]] = None) -> None:
|
||||
"""Create a new Body."""
|
||||
|
||||
self.outlines: List[Outline] = [] if outlines is None else outlines
|
||||
"""All the top-level Outline elements."""
|
||||
|
||||
@staticmethod
|
||||
def from_element_tree(element: ElementTree) -> "Body":
|
||||
"""Parse a Body object from an XML ElementTree."""
|
||||
|
||||
return Body(Outline.parse_outlines(element))
|
||||
|
||||
def insert_xml_element(self, parent: XmlBuilder.Element) -> None:
|
||||
"""Insert the Body into an XML Element."""
|
||||
|
||||
body = XmlBuilder.SubElement(parent, "body")
|
||||
for outline in self.outlines:
|
||||
outline.insert_xml_element(body)
|
||||
|
||||
def to_json(self) -> str:
|
||||
"""Output the Body as a JSON string."""
|
||||
|
||||
return json.dumps(
|
||||
self,
|
||||
default=lambda o: _dict_exclude_none(o.__dict__),
|
||||
indent=2,
|
||||
sort_keys=True,
|
||||
)
|
|
@ -0,0 +1,162 @@
|
|||
import json
|
||||
|
||||
from defusedxml import ElementTree
|
||||
from typing import Optional
|
||||
from xml.etree import ElementTree as XmlBuilder
|
||||
|
||||
from .util import _dict_exclude_none
|
||||
|
||||
|
||||
class Head:
|
||||
"""The OPML Head element."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
title: Optional[str] = None,
|
||||
date_created: Optional[str] = None,
|
||||
date_modified: Optional[str] = None,
|
||||
owner_name: Optional[str] = None,
|
||||
owner_email: Optional[str] = None,
|
||||
owner_id: Optional[str] = None,
|
||||
docs: Optional[str] = None,
|
||||
expansion_state: Optional[str] = None,
|
||||
vert_scroll_state: Optional[int] = None,
|
||||
window_top: Optional[int] = None,
|
||||
window_left: Optional[int] = None,
|
||||
window_bottom: Optional[int] = None,
|
||||
window_right: Optional[int] = None,
|
||||
) -> None:
|
||||
"""Create a new Head."""
|
||||
|
||||
self.title: Optional[str] = title
|
||||
"""The title of the document."""
|
||||
|
||||
self.date_created: Optional[str] = date_created
|
||||
"""A date-time (RFC822) indicating when the document was created."""
|
||||
|
||||
self.date_modified: Optional[str] = date_modified
|
||||
"""A date-time (RFC822) indicating when the document was last modified."""
|
||||
|
||||
self.owner_name: Optional[str] = owner_name
|
||||
"""The name of the document owner."""
|
||||
|
||||
self.owner_email: Optional[str] = owner_email
|
||||
"""The email address of the document owner."""
|
||||
|
||||
self.owner_id: Optional[str] = owner_id
|
||||
"""A link to the website of the document owner."""
|
||||
|
||||
self.docs: Optional[str] = docs
|
||||
"""A link to the documentation of the OPML format used for this document."""
|
||||
|
||||
self.expansion_state: Optional[str] = expansion_state
|
||||
"""A comma-separated list of line numbers that are expanded. The line
|
||||
numbers in the list tell you which headlines to expand. The order is
|
||||
important. For each element in the list, X, starting at the first summit,
|
||||
navigate flatdown X times and expand. Repeat for each element in the
|
||||
list."""
|
||||
|
||||
self.vert_scroll_state: Optional[int] = vert_scroll_state
|
||||
"""A number indicating which line of the outline is displayed on the top
|
||||
line of the window. This number is calculated with the expansion state
|
||||
already applied."""
|
||||
|
||||
self.window_top: Optional[int] = window_top
|
||||
"""The pixel location of the top edge of the window."""
|
||||
|
||||
self.window_left: Optional[int] = window_left
|
||||
"""The pixel location of the left edge of the window."""
|
||||
|
||||
self.window_bottom: Optional[int] = window_bottom
|
||||
"""The pixel location of the bottom edge of the window."""
|
||||
|
||||
self.window_right: Optional[int] = window_right
|
||||
"""The pixel location of the right edge of the window."""
|
||||
|
||||
@staticmethod
|
||||
def from_element_tree(element: ElementTree) -> "Head":
|
||||
"""Parse a Head object from an XML ElementTree."""
|
||||
|
||||
head = Head()
|
||||
for child in element:
|
||||
if child.tag == "title":
|
||||
head.title = child.text
|
||||
elif child.tag == "dateCreated":
|
||||
head.date_created = child.text
|
||||
elif child.tag == "dateModified":
|
||||
head.date_modified = child.text
|
||||
elif child.tag == "ownerName":
|
||||
head.owner_name = child.text
|
||||
elif child.tag == "ownerEmail":
|
||||
head.owner_email = child.text
|
||||
elif child.tag == "ownerId":
|
||||
head.owner_id = child.text
|
||||
elif child.tag == "docs":
|
||||
head.docs = child.text
|
||||
elif child.tag == "expansionState":
|
||||
head.expansion_state = child.text
|
||||
elif child.tag == "vertScrollState":
|
||||
head.vert_scroll_state = int(child.text)
|
||||
elif child.tag == "windowTop":
|
||||
head.window_top = int(child.text)
|
||||
elif child.tag == "windowLeft":
|
||||
head.window_left = int(child.text)
|
||||
elif child.tag == "windowBottom":
|
||||
head.window_bottom = int(child.text)
|
||||
elif child.tag == "windowRight":
|
||||
head.window_right = int(child.text)
|
||||
return head
|
||||
|
||||
def insert_xml_element(self, parent: XmlBuilder.Element) -> None:
|
||||
"""Insert the Head into an XML Element."""
|
||||
|
||||
head = XmlBuilder.SubElement(parent, "head")
|
||||
if self.title is not None:
|
||||
element = XmlBuilder.SubElement(head, "title")
|
||||
element.text = self.title
|
||||
if self.date_created is not None:
|
||||
element = XmlBuilder.SubElement(head, "dateCreated")
|
||||
element.text = self.date_created
|
||||
if self.date_modified is not None:
|
||||
element = XmlBuilder.SubElement(head, "dateModified")
|
||||
element.text = self.date_modified
|
||||
if self.owner_name is not None:
|
||||
element = XmlBuilder.SubElement(head, "ownerName")
|
||||
element.text = self.owner_name
|
||||
if self.owner_email is not None:
|
||||
element = XmlBuilder.SubElement(head, "ownerEmail")
|
||||
element.text = self.owner_email
|
||||
if self.owner_id is not None:
|
||||
element = XmlBuilder.SubElement(head, "ownerId")
|
||||
element.text = self.owner_id
|
||||
if self.docs is not None:
|
||||
element = XmlBuilder.SubElement(head, "docs")
|
||||
element.text = self.docs
|
||||
if self.expansion_state is not None:
|
||||
element = XmlBuilder.SubElement(head, "expansionState")
|
||||
element.text = self.expansion_state
|
||||
if self.vert_scroll_state is not None:
|
||||
element = XmlBuilder.SubElement(head, "vertScrollState")
|
||||
element.text = str(self.vert_scroll_state)
|
||||
if self.window_top is not None:
|
||||
element = XmlBuilder.SubElement(head, "windowTop")
|
||||
element.text = str(self.window_top)
|
||||
if self.window_left is not None:
|
||||
element = XmlBuilder.SubElement(head, "windowLeft")
|
||||
element.text = str(self.window_left)
|
||||
if self.window_bottom is not None:
|
||||
element = XmlBuilder.SubElement(head, "windowBottom")
|
||||
element.text = str(self.window_bottom)
|
||||
if self.window_right is not None:
|
||||
element = XmlBuilder.SubElement(head, "windowRight")
|
||||
element.text = str(self.window_right)
|
||||
|
||||
def to_json(self) -> str:
|
||||
"""Output the Head as a JSON string."""
|
||||
|
||||
return json.dumps(
|
||||
self,
|
||||
default=lambda o: _dict_exclude_none(o.__dict__),
|
||||
indent=2,
|
||||
sort_keys=True,
|
||||
)
|
|
@ -0,0 +1,92 @@
|
|||
import json
|
||||
|
||||
from defusedxml import ElementTree
|
||||
from typing import Optional
|
||||
from xml.etree import ElementTree as XmlBuilder
|
||||
|
||||
from .body import Body
|
||||
from .head import Head
|
||||
from .util import _dict_exclude_none
|
||||
|
||||
|
||||
class OPML:
|
||||
"""The root OPML element."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
version: Optional[str] = None,
|
||||
head: Optional[Head] = None,
|
||||
body: Optional[Body] = None,
|
||||
) -> None:
|
||||
"""Create a new OPML document."""
|
||||
|
||||
self.version: str = version if version is not None else "2.0"
|
||||
"""The OPML spec version of this document.
|
||||
|
||||
Must be `1.0`, `1.1` or `2.0`."""
|
||||
if self.version not in ["1.0", "1.1", "2.0"]:
|
||||
raise ValueError(f"unsupported version: {version}")
|
||||
|
||||
self.head: Optional[Head] = head
|
||||
"""The Head child object. Contains the metadata of the OPML document."""
|
||||
|
||||
self.body: Body = Body() if body is None else body
|
||||
"""The Body child object. Contains all the Outline elements."""
|
||||
|
||||
@staticmethod
|
||||
def from_xml(xml: str) -> "OPML":
|
||||
"""Parse an OPML document from an XML string."""
|
||||
|
||||
root = ElementTree.fromstring(xml)
|
||||
|
||||
if root.tag != "opml":
|
||||
raise ValueError("root element is not <opml>")
|
||||
|
||||
# OPML elements must have a version attribute.
|
||||
if "version" not in root.attrib:
|
||||
raise ValueError("missing <opml> version attribute")
|
||||
|
||||
# And the version attribute can only be 1.0, 1.1 or 2.0.
|
||||
version = root.attrib.get("version")
|
||||
if version not in ["1.0", "1.1", "2.0"]:
|
||||
raise ValueError(f"unsupported version: {version}")
|
||||
|
||||
# OPML elements must have a <body> child element.
|
||||
if True not in map(lambda e: e.tag == "body", root):
|
||||
raise ValueError("root <opml> element is missing a child <body>")
|
||||
|
||||
opml = OPML(version)
|
||||
|
||||
for child in root:
|
||||
if child.tag == "head":
|
||||
opml.head = Head.from_element_tree(child)
|
||||
elif child.tag == "body":
|
||||
opml.body = Body.from_element_tree(child)
|
||||
|
||||
# OPML documents must contain at least 1 <outline> element.
|
||||
if len(opml.body.outlines) == 0:
|
||||
raise ValueError("<body> contains no <outline> elements")
|
||||
|
||||
return opml
|
||||
|
||||
def to_xml(self) -> str:
|
||||
"""Output the OPML document as an XML string."""
|
||||
|
||||
opml = XmlBuilder.Element("opml")
|
||||
opml.attrib["version"] = self.version
|
||||
|
||||
if self.head is not None:
|
||||
self.head.insert_xml_element(opml)
|
||||
|
||||
self.body.insert_xml_element(opml)
|
||||
return XmlBuilder.tostring(opml, encoding="unicode")
|
||||
|
||||
def to_json(self) -> str:
|
||||
"""Output the OPML document as a JSON string."""
|
||||
|
||||
return json.dumps(
|
||||
self,
|
||||
default=lambda o: _dict_exclude_none(o.__dict__),
|
||||
indent=2,
|
||||
sort_keys=True,
|
||||
)
|
|
@ -0,0 +1,182 @@
|
|||
import json
|
||||
|
||||
from defusedxml import ElementTree
|
||||
from typing import List, Optional
|
||||
from xml.etree import ElementTree as XmlBuilder
|
||||
|
||||
from .util import _dict_exclude_none
|
||||
|
||||
|
||||
class Outline:
|
||||
"""The OPML Outline element."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
text: str,
|
||||
type: Optional[str] = None,
|
||||
is_comment: Optional[bool] = None,
|
||||
is_breakpoint: Optional[bool] = None,
|
||||
created: Optional[str] = None,
|
||||
category: Optional[str] = None,
|
||||
outlines: Optional[List["Outline"]] = None,
|
||||
xml_url: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
html_url: Optional[str] = None,
|
||||
language: Optional[str] = None,
|
||||
title: Optional[str] = None,
|
||||
version: Optional[str] = None,
|
||||
url: Optional[str] = None,
|
||||
) -> None:
|
||||
"""Create a new Outline."""
|
||||
|
||||
self.text: str = text
|
||||
"""Every outline element must have at least a text attribute, which is what
|
||||
is displayed when an outliner opens the OPML document.
|
||||
|
||||
Version 1.0 OPML documents may omit this attribute, so for compatibility and
|
||||
strictness this attribute is “technically optional” as it will be replaced
|
||||
by an empty string if it is omitted.
|
||||
|
||||
Text attributes may contain encoded HTML markup."""
|
||||
|
||||
self.type: Optional[str] = type
|
||||
"""A string that indicates how the other attributes of the Outline should be
|
||||
interpreted."""
|
||||
|
||||
self.is_comment: Optional[bool] = is_comment
|
||||
"""Indicating whether the outline is commented or not. By convention if an
|
||||
outline is commented, all subordinate outlines are considered to also be
|
||||
commented."""
|
||||
|
||||
self.is_breakpoint: Optional[bool] = is_breakpoint
|
||||
"""Indicating whether a breakpoint is set on this outline. This attribute is
|
||||
mainly necessary for outlines used to edit scripts."""
|
||||
|
||||
self.created: Optional[str] = created
|
||||
"""The date-time (RFC822) that this Outline element was created."""
|
||||
|
||||
self.category: Optional[str] = category
|
||||
"""A string of comma-separated slash-delimited category strings, in the
|
||||
format defined by the [RSS 2.0 category][1] element. To represent a “tag”, the
|
||||
category string should contain no slashes.
|
||||
|
||||
[1]: https://cyber.law.harvard.edu/rss/rss.html#ltcategorygtSubelementOfLtitemgt
|
||||
"""
|
||||
|
||||
self.outlines: List["Outline"] = outlines if outlines is not None else []
|
||||
"""Child Outline elements of the current one."""
|
||||
|
||||
self.xml_url: Optional[str] = xml_url
|
||||
"""The HTTP address of the feed."""
|
||||
|
||||
self.description: Optional[str] = description
|
||||
"""The top-level description element from the feed."""
|
||||
|
||||
self.html_url: Optional[str] = html_url
|
||||
"""The top-level link element from the feed."""
|
||||
|
||||
self.language: Optional[str] = language
|
||||
"""The top-level language element from the feed."""
|
||||
|
||||
self.title: Optional[str] = title
|
||||
"""The top-level title element from the feed."""
|
||||
|
||||
self.version: Optional[str] = version
|
||||
"""The version of the feed’s format (such as RSS 0.91, 2.0, …)."""
|
||||
|
||||
self.url: Optional[str] = url
|
||||
"""A link that can point to another OPML document or to something that can
|
||||
be displayed in a web browser."""
|
||||
|
||||
@staticmethod
|
||||
def from_element_tree(element: ElementTree) -> "Outline":
|
||||
"""Parse an Outline object from an XML ElementTree."""
|
||||
|
||||
attr = element.attrib
|
||||
outline = Outline(
|
||||
text=attr.get("text", ""),
|
||||
outlines=Outline.parse_outlines(element),
|
||||
)
|
||||
|
||||
if "type" in attr:
|
||||
outline.type = attr.get("type")
|
||||
if "isComment" in attr and attr.get("isComment") in ["true", "false"]:
|
||||
outline.is_comment = bool(attr.get("isComment"))
|
||||
if "isBreakpoint" in attr and attr.get("isBreakpoint") in ["true", "false"]:
|
||||
outline.is_comment = bool(attr.get("isBreakpoint"))
|
||||
if "created" in attr:
|
||||
outline.created = attr.get("created")
|
||||
if "category" in attr:
|
||||
outline.category = attr.get("category")
|
||||
if "xmlUrl" in attr:
|
||||
outline.xml_url = attr.get("xmlUrl")
|
||||
if "description" in attr:
|
||||
outline.description = attr.get("description")
|
||||
if "htmlUrl" in attr:
|
||||
outline.html_url = attr.get("htmlUrl")
|
||||
if "language" in attr:
|
||||
outline.language = attr.get("language")
|
||||
if "title" in attr:
|
||||
outline.title = attr.get("title")
|
||||
if "version" in attr:
|
||||
outline.version = attr.get("version")
|
||||
if "url" in attr:
|
||||
outline.url = attr.get("url")
|
||||
return outline
|
||||
|
||||
@staticmethod
|
||||
def parse_outlines(element: ElementTree) -> List["Outline"]:
|
||||
"""Parse a list of Outline elements from an XML ElementTree."""
|
||||
|
||||
return list(
|
||||
map(
|
||||
lambda child: Outline.from_element_tree(child),
|
||||
filter(lambda child: child.tag == "outline", element),
|
||||
)
|
||||
)
|
||||
|
||||
def insert_xml_element(self, parent: XmlBuilder.Element) -> None:
|
||||
"""Insert the Outline into an XML Element."""
|
||||
|
||||
outline = XmlBuilder.SubElement(parent, "outline")
|
||||
outline.attrib["text"] = self.text
|
||||
|
||||
if self.text is not None:
|
||||
outline.attrib["text"] = self.text
|
||||
if self.type is not None:
|
||||
outline.attrib["type"] = self.type
|
||||
if self.is_comment is not None:
|
||||
outline.attrib["isComment"] = str(self.is_comment).lower()
|
||||
if self.is_breakpoint is not None:
|
||||
outline.attrib["isBreakpoint"] = str(self.is_breakpoint).lower()
|
||||
if self.created is not None:
|
||||
outline.attrib["created"] = self.created
|
||||
if self.category is not None:
|
||||
outline.attrib["category"] = self.category
|
||||
if self.xml_url is not None:
|
||||
outline.attrib["xmlUrl"] = self.xml_url
|
||||
if self.description is not None:
|
||||
outline.attrib["description"] = self.description
|
||||
if self.html_url is not None:
|
||||
outline.attrib["htmlUrl"] = self.html_url
|
||||
if self.language is not None:
|
||||
outline.attrib["language"] = self.language
|
||||
if self.title is not None:
|
||||
outline.attrib["title"] = self.title
|
||||
if self.version is not None:
|
||||
outline.attrib["version"] = self.version
|
||||
if self.url is not None:
|
||||
outline.attrib["url"] = self.url
|
||||
|
||||
for child_outline in self.outlines:
|
||||
child_outline.insert_xml_element(outline)
|
||||
|
||||
def to_json(self) -> str:
|
||||
"""Output the Outline as a JSON string."""
|
||||
|
||||
return json.dumps(
|
||||
self,
|
||||
default=lambda o: _dict_exclude_none(o.__dict__),
|
||||
indent=2,
|
||||
sort_keys=True,
|
||||
)
|
|
@ -0,0 +1,10 @@
|
|||
from typing import Any, Dict
|
||||
|
||||
|
||||
def _dict_exclude_none(input: Dict[Any, Any]) -> Dict[Any, Any]:
|
||||
"""Exclude items from a Dict where the value is None.
|
||||
|
||||
This is used for OPML elements' `to_json` function so the output
|
||||
doesn't contain a bunch of null values."""
|
||||
|
||||
return dict((k, v) for k, v in input.items() if v is not None)
|
|
@ -0,0 +1,625 @@
|
|||
[[package]]
|
||||
name = "astunparse"
|
||||
version = "1.6.3"
|
||||
description = "An AST unparser for Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
six = ">=1.6.1,<2.0"
|
||||
|
||||
[[package]]
|
||||
name = "atomicwrites"
|
||||
version = "1.4.0"
|
||||
description = "Atomic file writes."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[[package]]
|
||||
name = "attrs"
|
||||
version = "21.2.0"
|
||||
description = "Classes Without Boilerplate"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[package.extras]
|
||||
dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"]
|
||||
docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
|
||||
tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"]
|
||||
tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"]
|
||||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "21.9b0"
|
||||
description = "The uncompromising code formatter."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6.2"
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=7.1.2"
|
||||
mypy-extensions = ">=0.4.3"
|
||||
pathspec = ">=0.9.0,<1"
|
||||
platformdirs = ">=2"
|
||||
regex = ">=2020.1.8"
|
||||
tomli = ">=0.2.6,<2.0.0"
|
||||
typing-extensions = [
|
||||
{version = ">=3.10.0.0", markers = "python_version < \"3.10\""},
|
||||
{version = "!=3.10.0.1", markers = "python_version >= \"3.10\""},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
colorama = ["colorama (>=0.4.3)"]
|
||||
d = ["aiohttp (>=3.6.0)", "aiohttp-cors (>=0.4.0)"]
|
||||
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
|
||||
python2 = ["typed-ast (>=1.4.2)"]
|
||||
uvloop = ["uvloop (>=0.15.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.0.3"
|
||||
description = "Composable command line interface toolkit"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.4"
|
||||
description = "Cross-platform colored terminal text."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "6.0.2"
|
||||
description = "Code coverage measurement for Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
tomli = {version = "*", optional = true, markers = "extra == \"toml\""}
|
||||
|
||||
[package.extras]
|
||||
toml = ["tomli"]
|
||||
|
||||
[[package]]
|
||||
name = "defusedxml"
|
||||
version = "0.7.1"
|
||||
description = "XML bomb protection for Python stdlib modules"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.0.2"
|
||||
description = "A very fast and expressive template engine."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
MarkupSafe = ">=2.0"
|
||||
|
||||
[package.extras]
|
||||
i18n = ["Babel (>=2.7)"]
|
||||
|
||||
[[package]]
|
||||
name = "markupsafe"
|
||||
version = "2.0.1"
|
||||
description = "Safely add untrusted strings to HTML/XML markup."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "more-itertools"
|
||||
version = "8.10.0"
|
||||
description = "More routines for operating on iterables, beyond itertools"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "0.910"
|
||||
description = "Optional static typing for Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[package.dependencies]
|
||||
mypy-extensions = ">=0.4.3,<0.5.0"
|
||||
toml = "*"
|
||||
typing-extensions = ">=3.7.4"
|
||||
|
||||
[package.extras]
|
||||
dmypy = ["psutil (>=4.0)"]
|
||||
python2 = ["typed-ast (>=1.4.0,<1.5.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "mypy-extensions"
|
||||
version = "0.4.3"
|
||||
description = "Experimental type system extensions for programs checked with the mypy typechecker."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "21.0"
|
||||
description = "Core utilities for Python packages"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
pyparsing = ">=2.0.2"
|
||||
|
||||
[[package]]
|
||||
name = "pathspec"
|
||||
version = "0.9.0"
|
||||
description = "Utility library for gitignore style pattern matching of file paths."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
||||
|
||||
[[package]]
|
||||
name = "pdoc"
|
||||
version = "8.0.0"
|
||||
description = "API Documentation for Python Projects"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
astunparse = {version = "*", markers = "python_version < \"3.9\""}
|
||||
Jinja2 = ">=2.11.0"
|
||||
MarkupSafe = "*"
|
||||
pygments = "*"
|
||||
|
||||
[package.extras]
|
||||
dev = ["flake8", "hypothesis", "mypy", "pytest", "pytest-cov", "pytest-timeout", "tox"]
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "2.4.0"
|
||||
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.extras]
|
||||
docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"]
|
||||
test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "0.13.1"
|
||||
description = "plugin and hook calling mechanisms for python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[package.extras]
|
||||
dev = ["pre-commit", "tox"]
|
||||
|
||||
[[package]]
|
||||
name = "py"
|
||||
version = "1.10.0"
|
||||
description = "library with cross-python path, ini-parsing, io, code, log facilities"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.10.0"
|
||||
description = "Pygments is a syntax highlighting package written in Python."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[[package]]
|
||||
name = "pyparsing"
|
||||
version = "2.4.7"
|
||||
description = "Python parsing module"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "5.4.3"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[package.dependencies]
|
||||
atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
|
||||
attrs = ">=17.4.0"
|
||||
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
more-itertools = ">=4.0.0"
|
||||
packaging = "*"
|
||||
pluggy = ">=0.12,<1.0"
|
||||
py = ">=1.5.0"
|
||||
wcwidth = "*"
|
||||
|
||||
[package.extras]
|
||||
checkqa-mypy = ["mypy (==v0.761)"]
|
||||
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-cov"
|
||||
version = "3.0.0"
|
||||
description = "Pytest plugin for measuring coverage."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
coverage = {version = ">=5.2.1", extras = ["toml"]}
|
||||
pytest = ">=4.6"
|
||||
|
||||
[package.extras]
|
||||
testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-snapshot"
|
||||
version = "0.7.0"
|
||||
description = "A plugin for snapshot testing with pytest."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[package.dependencies]
|
||||
pytest = ">=3.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "2021.10.8"
|
||||
description = "Alternative regular expression module, to replace re."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.16.0"
|
||||
description = "Python 2 and 3 compatibility utilities"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.10.2"
|
||||
description = "Python Library for Tom's Obvious, Minimal Language"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
|
||||
[[package]]
|
||||
name = "tomli"
|
||||
version = "1.2.1"
|
||||
description = "A lil' TOML parser"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "3.10.0.2"
|
||||
description = "Backported and Experimental Type Hints for Python 3.5+"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "wcwidth"
|
||||
version = "0.2.5"
|
||||
description = "Measures the displayed width of unicode strings in a terminal"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.8"
|
||||
content-hash = "ecf7c4dfd02d35c892554873f8287a92748fac5d9a45db7e4a43a11b0630e13a"
|
||||
|
||||
[metadata.files]
|
||||
astunparse = [
|
||||
{file = "astunparse-1.6.3-py2.py3-none-any.whl", hash = "sha256:c2652417f2c8b5bb325c885ae329bdf3f86424075c4fd1a128674bc6fba4b8e8"},
|
||||
{file = "astunparse-1.6.3.tar.gz", hash = "sha256:5ad93a8456f0d084c3456d059fd9a92cce667963232cbf763eac3bc5b7940872"},
|
||||
]
|
||||
atomicwrites = [
|
||||
{file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
|
||||
{file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
|
||||
]
|
||||
attrs = [
|
||||
{file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"},
|
||||
{file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"},
|
||||
]
|
||||
black = [
|
||||
{file = "black-21.9b0-py3-none-any.whl", hash = "sha256:380f1b5da05e5a1429225676655dddb96f5ae8c75bdf91e53d798871b902a115"},
|
||||
{file = "black-21.9b0.tar.gz", hash = "sha256:7de4cfc7eb6b710de325712d40125689101d21d25283eed7e9998722cf10eb91"},
|
||||
]
|
||||
click = [
|
||||
{file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"},
|
||||
{file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"},
|
||||
]
|
||||
colorama = [
|
||||
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
|
||||
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
|
||||
]
|
||||
coverage = [
|
||||
{file = "coverage-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1549e1d08ce38259de2bc3e9a0d5f3642ff4a8f500ffc1b2df73fd621a6cdfc0"},
|
||||
{file = "coverage-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcae10fccb27ca2a5f456bf64d84110a5a74144be3136a5e598f9d9fb48c0caa"},
|
||||
{file = "coverage-6.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:53a294dc53cfb39c74758edaa6305193fb4258a30b1f6af24b360a6c8bd0ffa7"},
|
||||
{file = "coverage-6.0.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8251b37be1f2cd9c0e5ccd9ae0380909c24d2a5ed2162a41fcdbafaf59a85ebd"},
|
||||
{file = "coverage-6.0.2-cp310-cp310-win32.whl", hash = "sha256:db42baa892cba723326284490283a68d4de516bfb5aaba369b4e3b2787a778b7"},
|
||||
{file = "coverage-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:bbffde2a68398682623d9dd8c0ca3f46fda074709b26fcf08ae7a4c431a6ab2d"},
|
||||
{file = "coverage-6.0.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:60e51a3dd55540bec686d7fff61b05048ca31e804c1f32cbb44533e6372d9cc3"},
|
||||
{file = "coverage-6.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a6a9409223a27d5ef3cca57dd7cd4dfcb64aadf2fad5c3b787830ac9223e01a"},
|
||||
{file = "coverage-6.0.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4b34ae4f51bbfa5f96b758b55a163d502be3dcb24f505d0227858c2b3f94f5b9"},
|
||||
{file = "coverage-6.0.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3bbda1b550e70fa6ac40533d3f23acd4f4e9cb4e6e77251ce77fdf41b3309fb2"},
|
||||
{file = "coverage-6.0.2-cp36-cp36m-win32.whl", hash = "sha256:4e28d2a195c533b58fc94a12826f4431726d8eb029ac21d874345f943530c122"},
|
||||
{file = "coverage-6.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:a82d79586a0a4f5fd1cf153e647464ced402938fbccb3ffc358c7babd4da1dd9"},
|
||||
{file = "coverage-6.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3be1206dc09fb6298de3fce70593e27436862331a85daee36270b6d0e1c251c4"},
|
||||
{file = "coverage-6.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9cd3828bbe1a40070c11fe16a51df733fd2f0cb0d745fb83b7b5c1f05967df7"},
|
||||
{file = "coverage-6.0.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d036dc1ed8e1388e995833c62325df3f996675779541f682677efc6af71e96cc"},
|
||||
{file = "coverage-6.0.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:04560539c19ec26995ecfb3d9307ff154fbb9a172cb57e3b3cfc4ced673103d1"},
|
||||
{file = "coverage-6.0.2-cp37-cp37m-win32.whl", hash = "sha256:e4fb7ced4d9dec77d6cf533acfbf8e1415fe799430366affb18d69ee8a3c6330"},
|
||||
{file = "coverage-6.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:77b1da5767ed2f44611bc9bc019bc93c03fa495728ec389759b6e9e5039ac6b1"},
|
||||
{file = "coverage-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:61b598cbdbaae22d9e34e3f675997194342f866bb1d781da5d0be54783dce1ff"},
|
||||
{file = "coverage-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36e9040a43d2017f2787b28d365a4bb33fcd792c7ff46a047a04094dc0e2a30d"},
|
||||
{file = "coverage-6.0.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9f1627e162e3864a596486774876415a7410021f4b67fd2d9efdf93ade681afc"},
|
||||
{file = "coverage-6.0.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e7a0b42db2a47ecb488cde14e0f6c7679a2c5a9f44814393b162ff6397fcdfbb"},
|
||||
{file = "coverage-6.0.2-cp38-cp38-win32.whl", hash = "sha256:a1b73c7c4d2a42b9d37dd43199c5711d91424ff3c6c22681bc132db4a4afec6f"},
|
||||
{file = "coverage-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:1db67c497688fd4ba85b373b37cc52c50d437fd7267520ecd77bddbd89ea22c9"},
|
||||
{file = "coverage-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f2f184bf38e74f152eed7f87e345b51f3ab0b703842f447c22efe35e59942c24"},
|
||||
{file = "coverage-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd1cf1deb3d5544bd942356364a2fdc8959bad2b6cf6eb17f47d301ea34ae822"},
|
||||
{file = "coverage-6.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ad9b8c1206ae41d46ec7380b78ba735ebb77758a650643e841dd3894966c31d0"},
|
||||
{file = "coverage-6.0.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:381d773d896cc7f8ba4ff3b92dee4ed740fb88dfe33b6e42efc5e8ab6dfa1cfe"},
|
||||
{file = "coverage-6.0.2-cp39-cp39-win32.whl", hash = "sha256:424c44f65e8be58b54e2b0bd1515e434b940679624b1b72726147cfc6a9fc7ce"},
|
||||
{file = "coverage-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:abbff240f77347d17306d3201e14431519bf64495648ca5a49571f988f88dee9"},
|
||||
{file = "coverage-6.0.2-pp36-none-any.whl", hash = "sha256:7092eab374346121805fb637572483270324407bf150c30a3b161fc0c4ca5164"},
|
||||
{file = "coverage-6.0.2-pp37-none-any.whl", hash = "sha256:30922626ce6f7a5a30bdba984ad21021529d3d05a68b4f71ea3b16bda35b8895"},
|
||||
{file = "coverage-6.0.2.tar.gz", hash = "sha256:6807947a09510dc31fa86f43595bf3a14017cd60bf633cc746d52141bfa6b149"},
|
||||
]
|
||||
defusedxml = [
|
||||
{file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"},
|
||||
{file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"},
|
||||
]
|
||||
jinja2 = [
|
||||
{file = "Jinja2-3.0.2-py3-none-any.whl", hash = "sha256:8569982d3f0889eed11dd620c706d39b60c36d6d25843961f33f77fb6bc6b20c"},
|
||||
{file = "Jinja2-3.0.2.tar.gz", hash = "sha256:827a0e32839ab1600d4eb1c4c33ec5a8edfbc5cb42dafa13b81f182f97784b45"},
|
||||
]
|
||||
markupsafe = [
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"},
|
||||
{file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"},
|
||||
]
|
||||
more-itertools = [
|
||||
{file = "more-itertools-8.10.0.tar.gz", hash = "sha256:1debcabeb1df793814859d64a81ad7cb10504c24349368ccf214c664c474f41f"},
|
||||
{file = "more_itertools-8.10.0-py3-none-any.whl", hash = "sha256:56ddac45541718ba332db05f464bebfb0768110111affd27f66e0051f276fa43"},
|
||||
]
|
||||
mypy = [
|
||||
{file = "mypy-0.910-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457"},
|
||||
{file = "mypy-0.910-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb"},
|
||||
{file = "mypy-0.910-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:088cd9c7904b4ad80bec811053272986611b84221835e079be5bcad029e79dd9"},
|
||||
{file = "mypy-0.910-cp35-cp35m-win_amd64.whl", hash = "sha256:adaeee09bfde366d2c13fe6093a7df5df83c9a2ba98638c7d76b010694db760e"},
|
||||
{file = "mypy-0.910-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ecd2c3fe726758037234c93df7e98deb257fd15c24c9180dacf1ef829da5f921"},
|
||||
{file = "mypy-0.910-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d9dd839eb0dc1bbe866a288ba3c1afc33a202015d2ad83b31e875b5905a079b6"},
|
||||
{file = "mypy-0.910-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:3e382b29f8e0ccf19a2df2b29a167591245df90c0b5a2542249873b5c1d78212"},
|
||||
{file = "mypy-0.910-cp36-cp36m-win_amd64.whl", hash = "sha256:53fd2eb27a8ee2892614370896956af2ff61254c275aaee4c230ae771cadd885"},
|
||||
{file = "mypy-0.910-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b6fb13123aeef4a3abbcfd7e71773ff3ff1526a7d3dc538f3929a49b42be03f0"},
|
||||
{file = "mypy-0.910-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e4dab234478e3bd3ce83bac4193b2ecd9cf94e720ddd95ce69840273bf44f6de"},
|
||||
{file = "mypy-0.910-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:7df1ead20c81371ccd6091fa3e2878559b5c4d4caadaf1a484cf88d93ca06703"},
|
||||
{file = "mypy-0.910-cp37-cp37m-win_amd64.whl", hash = "sha256:0aadfb2d3935988ec3815952e44058a3100499f5be5b28c34ac9d79f002a4a9a"},
|
||||
{file = "mypy-0.910-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ec4e0cd079db280b6bdabdc807047ff3e199f334050db5cbb91ba3e959a67504"},
|
||||
{file = "mypy-0.910-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:119bed3832d961f3a880787bf621634ba042cb8dc850a7429f643508eeac97b9"},
|
||||
{file = "mypy-0.910-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:866c41f28cee548475f146aa4d39a51cf3b6a84246969f3759cb3e9c742fc072"},
|
||||
{file = "mypy-0.910-cp38-cp38-win_amd64.whl", hash = "sha256:ceb6e0a6e27fb364fb3853389607cf7eb3a126ad335790fa1e14ed02fba50811"},
|
||||
{file = "mypy-0.910-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a85e280d4d217150ce8cb1a6dddffd14e753a4e0c3cf90baabb32cefa41b59e"},
|
||||
{file = "mypy-0.910-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:42c266ced41b65ed40a282c575705325fa7991af370036d3f134518336636f5b"},
|
||||
{file = "mypy-0.910-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:3c4b8ca36877fc75339253721f69603a9c7fdb5d4d5a95a1a1b899d8b86a4de2"},
|
||||
{file = "mypy-0.910-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:c0df2d30ed496a08de5daed2a9ea807d07c21ae0ab23acf541ab88c24b26ab97"},
|
||||
{file = "mypy-0.910-cp39-cp39-win_amd64.whl", hash = "sha256:c6c2602dffb74867498f86e6129fd52a2770c48b7cd3ece77ada4fa38f94eba8"},
|
||||
{file = "mypy-0.910-py3-none-any.whl", hash = "sha256:ef565033fa5a958e62796867b1df10c40263ea9ded87164d67572834e57a174d"},
|
||||
{file = "mypy-0.910.tar.gz", hash = "sha256:704098302473cb31a218f1775a873b376b30b4c18229421e9e9dc8916fd16150"},
|
||||
]
|
||||
mypy-extensions = [
|
||||
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
|
||||
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
|
||||
]
|
||||
packaging = [
|
||||
{file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"},
|
||||
{file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"},
|
||||
]
|
||||
pathspec = [
|
||||
{file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
|
||||
{file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
|
||||
]
|
||||
pdoc = [
|
||||
{file = "pdoc-8.0.0-py3-none-any.whl", hash = "sha256:acd96792b03f1930db7af73bb144cc0f12d8cd10d0a9cc9c2d3083a096ea5831"},
|
||||
]
|
||||
platformdirs = [
|
||||
{file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"},
|
||||
{file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"},
|
||||
]
|
||||
pluggy = [
|
||||
{file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
|
||||
{file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
|
||||
]
|
||||
py = [
|
||||
{file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"},
|
||||
{file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"},
|
||||
]
|
||||
pygments = [
|
||||
{file = "Pygments-2.10.0-py3-none-any.whl", hash = "sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380"},
|
||||
{file = "Pygments-2.10.0.tar.gz", hash = "sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6"},
|
||||
]
|
||||
pyparsing = [
|
||||
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
|
||||
{file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
|
||||
]
|
||||
pytest = [
|
||||
{file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"},
|
||||
{file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"},
|
||||
]
|
||||
pytest-cov = [
|
||||
{file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"},
|
||||
{file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"},
|
||||
]
|
||||
pytest-snapshot = [
|
||||
{file = "pytest-snapshot-0.7.0.tar.gz", hash = "sha256:427b5ab088b25a1c8b63ce99725040664c840ff1f5a3891252723cce972897f9"},
|
||||
{file = "pytest_snapshot-0.7.0-py3-none-any.whl", hash = "sha256:8d6e77136eb25990b5d713876e4ed5ca45b3916ac719e0f8399ba42f65d280af"},
|
||||
]
|
||||
regex = [
|
||||
{file = "regex-2021.10.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:094a905e87a4171508c2a0e10217795f83c636ccc05ddf86e7272c26e14056ae"},
|
||||
{file = "regex-2021.10.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:981c786293a3115bc14c103086ae54e5ee50ca57f4c02ce7cf1b60318d1e8072"},
|
||||
{file = "regex-2021.10.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b0f2f874c6a157c91708ac352470cb3bef8e8814f5325e3c5c7a0533064c6a24"},
|
||||
{file = "regex-2021.10.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51feefd58ac38eb91a21921b047da8644155e5678e9066af7bcb30ee0dca7361"},
|
||||
{file = "regex-2021.10.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea8de658d7db5987b11097445f2b1f134400e2232cb40e614e5f7b6f5428710e"},
|
||||
{file = "regex-2021.10.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1ce02f420a7ec3b2480fe6746d756530f69769292eca363218c2291d0b116a01"},
|
||||
{file = "regex-2021.10.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:39079ebf54156be6e6902f5c70c078f453350616cfe7bfd2dd15bdb3eac20ccc"},
|
||||
{file = "regex-2021.10.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ff24897f6b2001c38a805d53b6ae72267025878d35ea225aa24675fbff2dba7f"},
|
||||
{file = "regex-2021.10.8-cp310-cp310-win32.whl", hash = "sha256:c6569ba7b948c3d61d27f04e2b08ebee24fec9ff8e9ea154d8d1e975b175bfa7"},
|
||||
{file = "regex-2021.10.8-cp310-cp310-win_amd64.whl", hash = "sha256:45cb0f7ff782ef51bc79e227a87e4e8f24bc68192f8de4f18aae60b1d60bc152"},
|
||||
{file = "regex-2021.10.8-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fab3ab8aedfb443abb36729410403f0fe7f60ad860c19a979d47fb3eb98ef820"},
|
||||
{file = "regex-2021.10.8-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74e55f8d66f1b41d44bc44c891bcf2c7fad252f8f323ee86fba99d71fd1ad5e3"},
|
||||
{file = "regex-2021.10.8-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d52c5e089edbdb6083391faffbe70329b804652a53c2fdca3533e99ab0580d9"},
|
||||
{file = "regex-2021.10.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1abbd95cbe9e2467cac65c77b6abd9223df717c7ae91a628502de67c73bf6838"},
|
||||
{file = "regex-2021.10.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9b5c215f3870aa9b011c00daeb7be7e1ae4ecd628e9beb6d7e6107e07d81287"},
|
||||
{file = "regex-2021.10.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f540f153c4f5617bc4ba6433534f8916d96366a08797cbbe4132c37b70403e92"},
|
||||
{file = "regex-2021.10.8-cp36-cp36m-win32.whl", hash = "sha256:1f51926db492440e66c89cd2be042f2396cf91e5b05383acd7372b8cb7da373f"},
|
||||
{file = "regex-2021.10.8-cp36-cp36m-win_amd64.whl", hash = "sha256:5f55c4804797ef7381518e683249310f7f9646da271b71cb6b3552416c7894ee"},
|
||||
{file = "regex-2021.10.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb2baff66b7d2267e07ef71e17d01283b55b3cc51a81b54cc385e721ae172ba4"},
|
||||
{file = "regex-2021.10.8-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e527ab1c4c7cf2643d93406c04e1d289a9d12966529381ce8163c4d2abe4faf"},
|
||||
{file = "regex-2021.10.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c98b013273e9da5790ff6002ab326e3f81072b4616fd95f06c8fa733d2745f"},
|
||||
{file = "regex-2021.10.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:55ef044899706c10bc0aa052f2fc2e58551e2510694d6aae13f37c50f3f6ff61"},
|
||||
{file = "regex-2021.10.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa0ab3530a279a3b7f50f852f1bab41bc304f098350b03e30a3876b7dd89840e"},
|
||||
{file = "regex-2021.10.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a37305eb3199d8f0d8125ec2fb143ba94ff6d6d92554c4b8d4a8435795a6eccd"},
|
||||
{file = "regex-2021.10.8-cp37-cp37m-win32.whl", hash = "sha256:2efd47704bbb016136fe34dfb74c805b1ef5c7313aef3ce6dcb5ff844299f432"},
|
||||
{file = "regex-2021.10.8-cp37-cp37m-win_amd64.whl", hash = "sha256:924079d5590979c0e961681507eb1773a142553564ccae18d36f1de7324e71ca"},
|
||||
{file = "regex-2021.10.8-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:19b8f6d23b2dc93e8e1e7e288d3010e58fafed323474cf7f27ab9451635136d9"},
|
||||
{file = "regex-2021.10.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b09d3904bf312d11308d9a2867427479d277365b1617e48ad09696fa7dfcdf59"},
|
||||
{file = "regex-2021.10.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:951be934dc25d8779d92b530e922de44dda3c82a509cdb5d619f3a0b1491fafa"},
|
||||
{file = "regex-2021.10.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f125fce0a0ae4fd5c3388d369d7a7d78f185f904c90dd235f7ecf8fe13fa741"},
|
||||
{file = "regex-2021.10.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f199419a81c1016e0560c39773c12f0bd924c37715bffc64b97140d2c314354"},
|
||||
{file = "regex-2021.10.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:09e1031e2059abd91177c302da392a7b6859ceda038be9e015b522a182c89e4f"},
|
||||
{file = "regex-2021.10.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c070d5895ac6aeb665bd3cd79f673775caf8d33a0b569e98ac434617ecea57d"},
|
||||
{file = "regex-2021.10.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:176796cb7f82a7098b0c436d6daac82f57b9101bb17b8e8119c36eecf06a60a3"},
|
||||
{file = "regex-2021.10.8-cp38-cp38-win32.whl", hash = "sha256:5e5796d2f36d3c48875514c5cd9e4325a1ca172fc6c78b469faa8ddd3d770593"},
|
||||
{file = "regex-2021.10.8-cp38-cp38-win_amd64.whl", hash = "sha256:e4204708fa116dd03436a337e8e84261bc8051d058221ec63535c9403a1582a1"},
|
||||
{file = "regex-2021.10.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6dcf53d35850ce938b4f044a43b33015ebde292840cef3af2c8eb4c860730fff"},
|
||||
{file = "regex-2021.10.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b8b6ee6555b6fbae578f1468b3f685cdfe7940a65675611365a7ea1f8d724991"},
|
||||
{file = "regex-2021.10.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e2ec1c106d3f754444abf63b31e5c4f9b5d272272a491fa4320475aba9e8157c"},
|
||||
{file = "regex-2021.10.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:973499dac63625a5ef9dfa4c791aa33a502ddb7615d992bdc89cf2cc2285daa3"},
|
||||
{file = "regex-2021.10.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88dc3c1acd3f0ecfde5f95c32fcb9beda709dbdf5012acdcf66acbc4794468eb"},
|
||||
{file = "regex-2021.10.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4786dae85c1f0624ac77cb3813ed99267c9adb72e59fdc7297e1cf4d6036d493"},
|
||||
{file = "regex-2021.10.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe6ce4f3d3c48f9f402da1ceb571548133d3322003ce01b20d960a82251695d2"},
|
||||
{file = "regex-2021.10.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9e3e2cea8f1993f476a6833ef157f5d9e8c75a59a8d8b0395a9a6887a097243b"},
|
||||
{file = "regex-2021.10.8-cp39-cp39-win32.whl", hash = "sha256:82cfb97a36b1a53de32b642482c6c46b6ce80803854445e19bc49993655ebf3b"},
|
||||
{file = "regex-2021.10.8-cp39-cp39-win_amd64.whl", hash = "sha256:b04e512eb628ea82ed86eb31c0f7fc6842b46bf2601b66b1356a7008327f7700"},
|
||||
{file = "regex-2021.10.8.tar.gz", hash = "sha256:26895d7c9bbda5c52b3635ce5991caa90fbb1ddfac9c9ff1c7ce505e2282fb2a"},
|
||||
]
|
||||
six = [
|
||||
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
||||
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
||||
]
|
||||
toml = [
|
||||
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
|
||||
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
|
||||
]
|
||||
tomli = [
|
||||
{file = "tomli-1.2.1-py3-none-any.whl", hash = "sha256:8dd0e9524d6f386271a36b41dbf6c57d8e32fd96fd22b6584679dc569d20899f"},
|
||||
{file = "tomli-1.2.1.tar.gz", hash = "sha256:a5b75cb6f3968abb47af1b40c1819dc519ea82bcc065776a866e8d74c5ca9442"},
|
||||
]
|
||||
typing-extensions = [
|
||||
{file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"},
|
||||
{file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"},
|
||||
{file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"},
|
||||
]
|
||||
wcwidth = [
|
||||
{file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
|
||||
{file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
|
||||
]
|
|
@ -0,0 +1,22 @@
|
|||
[tool.poetry]
|
||||
name = "opyml"
|
||||
license = "MIT OR Apache-2.0"
|
||||
version = "0.1.0"
|
||||
description = "An OPML library for Python."
|
||||
authors = ["Holllo <helllo@holllo.cc>"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
defusedxml = "^0.7.1"
|
||||
python = "^3.8"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
black = "^21.9b0"
|
||||
mypy = "^0.910"
|
||||
pdoc = "^8.0.0"
|
||||
pytest = "^5.2"
|
||||
pytest-cov = "^3.0.0"
|
||||
pytest-snapshot = "^0.7.0"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
|
@ -0,0 +1 @@
|
|||
<opml version="2.0"><head><title>Rust Feeds</title></head><body><outline text="Rust Blog" xmlUrl="https://blog.rust-lang.org/feed.xml" /><outline text="Inside Rust" xmlUrl="https://blog.rust-lang.org/inside-rust/feed.xml" /></body></opml>
|
|
@ -0,0 +1 @@
|
|||
<opml version="2.0"><head><title>Rust & Mozilla Feeds</title></head><body><outline text="Rust Feeds"><outline text="Rust Blog" xmlUrl="https://blog.rust-lang.org/feed.xml" /><outline text="Inside Rust" xmlUrl="https://blog.rust-lang.org/inside-rust/feed.xml" /></outline><outline text="Mozilla Feeds"><outline text="Mozilla Blog" xmlUrl="https://blog.mozilla.org/feed" /><outline text="Mozilla Hacks" xmlUrl="https://hacks.mozilla.org/feed" /></outline></body></opml>
|
|
@ -0,0 +1 @@
|
|||
<opml version="2.0"><head><title>Title</title><dateCreated>Date Created</dateCreated><dateModified>Date Modified</dateModified><ownerName>Owner Name</ownerName><ownerEmail>Owner Email</ownerEmail><ownerId>Owner ID</ownerId><docs>http://dev.opml.org/spec2.html</docs><expansionState>0,1</expansionState><vertScrollState>0</vertScrollState><windowTop>1</windowTop><windowLeft>2</windowLeft><windowBottom>3</windowBottom><windowRight>4</windowRight></head><body><outline text="Outline Text" type="Outline Type" isComment="true" isBreakpoint="true" created="Outline Date" category="Outline Category" xmlUrl="Outline XML URL" description="Outline Description" htmlUrl="Outline HTML URL" language="Outline Language" title="Outline Title" version="Outline Version" url="Outline URL" /></body></opml>
|
|
@ -0,0 +1,3 @@
|
|||
<opml version="2.0">
|
||||
<head/>
|
||||
</opml>
|
|
@ -0,0 +1,4 @@
|
|||
<opml version="2.0">
|
||||
<head/>
|
||||
<body/>
|
||||
</opml>
|
|
@ -0,0 +1,6 @@
|
|||
<opml>
|
||||
<head/>
|
||||
<body>
|
||||
<outline text="Outline Text"/>
|
||||
</body>
|
||||
</opml>
|
|
@ -0,0 +1,3 @@
|
|||
<root>
|
||||
<element/>
|
||||
</root>
|
|
@ -0,0 +1,6 @@
|
|||
<opml version="invalid">
|
||||
<head/>
|
||||
<body>
|
||||
<outline text="Outline Text"/>
|
||||
</body>
|
||||
</opml>
|
|
@ -0,0 +1 @@
|
|||
{not xml :)
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"body": {
|
||||
"outlines": [
|
||||
{
|
||||
"outlines": [],
|
||||
"text": "Outline Text"
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "2.0"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<opml version="2.0">
|
||||
<body>
|
||||
<outline text="Outline Text"/>
|
||||
</body>
|
||||
</opml>
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"body": {
|
||||
"outlines": [
|
||||
{
|
||||
"outlines": [],
|
||||
"text": "",
|
||||
"title": "Outline Title"
|
||||
}
|
||||
]
|
||||
},
|
||||
"head": {},
|
||||
"version": "1.0"
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
<opml version="1.0">
|
||||
<head/>
|
||||
<body>
|
||||
<outline title="Outline Title"/>
|
||||
</body>
|
||||
</opml>
|
|
@ -0,0 +1,53 @@
|
|||
{
|
||||
"body": {
|
||||
"outlines": [
|
||||
{
|
||||
"category": "Outline Category",
|
||||
"created": "Outline Date",
|
||||
"description": "Outline Description",
|
||||
"html_url": "Outline HTML URL",
|
||||
"is_comment": true,
|
||||
"language": "Outline Language",
|
||||
"outlines": [
|
||||
{
|
||||
"category": "Nested Outline Category",
|
||||
"created": "Nested Outline Date",
|
||||
"description": "Nested Outline Description",
|
||||
"html_url": "Nested Outline HTML URL",
|
||||
"is_comment": true,
|
||||
"language": "Nested Outline Language",
|
||||
"outlines": [],
|
||||
"text": "Nested Outline Text",
|
||||
"title": "Nested Outline Title",
|
||||
"type": "Nested Outline Type",
|
||||
"url": "Nested Outline URL",
|
||||
"version": "Nested Outline Version",
|
||||
"xml_url": "Nested Outline XML URL"
|
||||
}
|
||||
],
|
||||
"text": "Outline Text",
|
||||
"title": "Outline Title",
|
||||
"type": "Outline Type",
|
||||
"url": "Outline URL",
|
||||
"version": "Outline Version",
|
||||
"xml_url": "Outline XML URL"
|
||||
}
|
||||
]
|
||||
},
|
||||
"head": {
|
||||
"date_created": "Date Created",
|
||||
"date_modified": "Date Modified",
|
||||
"docs": "http://dev.opml.org/spec2.html",
|
||||
"expansion_state": "0,1",
|
||||
"owner_email": "Owner Email",
|
||||
"owner_id": "Owner ID",
|
||||
"owner_name": "Owner Name",
|
||||
"title": "Title",
|
||||
"vert_scroll_state": 0,
|
||||
"window_bottom": 3,
|
||||
"window_left": 2,
|
||||
"window_right": 4,
|
||||
"window_top": 1
|
||||
},
|
||||
"version": "2.0"
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
<opml version="2.0">
|
||||
<head>
|
||||
<title>Title</title>
|
||||
<dateCreated>Date Created</dateCreated>
|
||||
<dateModified>Date Modified</dateModified>
|
||||
<ownerName>Owner Name</ownerName>
|
||||
<ownerEmail>Owner Email</ownerEmail>
|
||||
<ownerId>Owner ID</ownerId>
|
||||
<docs>http://dev.opml.org/spec2.html</docs>
|
||||
<expansionState>0,1</expansionState>
|
||||
<vertScrollState>0</vertScrollState>
|
||||
<windowTop>1</windowTop>
|
||||
<windowLeft>2</windowLeft>
|
||||
<windowBottom>3</windowBottom>
|
||||
<windowRight>4</windowRight>
|
||||
</head>
|
||||
<body>
|
||||
<outline text="Outline Text"
|
||||
type="Outline Type"
|
||||
isBreakpoint="true"
|
||||
isComment="true"
|
||||
created="Outline Date"
|
||||
category="Outline Category"
|
||||
xmlUrl="Outline XML URL"
|
||||
description="Outline Description"
|
||||
htmlUrl="Outline HTML URL"
|
||||
language="Outline Language"
|
||||
title="Outline Title"
|
||||
version="Outline Version"
|
||||
url="Outline URL">
|
||||
<outline text="Nested Outline Text"
|
||||
type="Nested Outline Type"
|
||||
isBreakpoint="true"
|
||||
isComment="false"
|
||||
created="Nested Outline Date"
|
||||
category="Nested Outline Category"
|
||||
xmlUrl="Nested Outline XML URL"
|
||||
description="Nested Outline Description"
|
||||
htmlUrl="Nested Outline HTML URL"
|
||||
language="Nested Outline Language"
|
||||
title="Nested Outline Title"
|
||||
version="Nested Outline Version"
|
||||
url="Nested Outline URL"/>
|
||||
</outline>
|
||||
</body>
|
||||
</opml>
|
|
@ -0,0 +1,165 @@
|
|||
from opyml import __version__, Body, Head, OPML, Outline
|
||||
from defusedxml import ElementTree
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def __read_sample(file_name: str) -> str:
|
||||
with open(f"tests/samples/{file_name}.opml", mode="r") as file:
|
||||
return file.read()
|
||||
|
||||
|
||||
def test_version():
|
||||
assert len(__version__) > 0
|
||||
|
||||
|
||||
def test_to_json():
|
||||
opml = OPML("1.1")
|
||||
assert '"version": "1.1"' in opml.to_json()
|
||||
assert '"outlines": []' in opml.body.to_json()
|
||||
|
||||
opml.head = Head(title="Title")
|
||||
assert '"title": "Title"' in opml.head.to_json()
|
||||
|
||||
opml.body.outlines.append(Outline("Outline"))
|
||||
assert '"text": "Outline"' in opml.body.outlines[0].to_json()
|
||||
assert "null" not in opml.to_json()
|
||||
|
||||
|
||||
def test_valid_samples(snapshot):
|
||||
snapshot.snapshot_dir = "tests/samples"
|
||||
samples = [
|
||||
"minimum_valid_opml",
|
||||
"valid_opml_1_0",
|
||||
"valid_opml_with_everything",
|
||||
]
|
||||
|
||||
for file_name in samples:
|
||||
opml = OPML.from_xml(__read_sample(file_name))
|
||||
snapshot.assert_match(f"{opml.to_json()}\n", f"{file_name}.json")
|
||||
|
||||
|
||||
def test_invalid_samples():
|
||||
with pytest.raises(ElementTree.ParseError):
|
||||
OPML.from_xml(__read_sample("invalid_xml"))
|
||||
|
||||
samples = [
|
||||
"invalid_opml_no_body",
|
||||
"invalid_opml_no_outlines",
|
||||
"invalid_opml_no_version",
|
||||
"invalid_opml_not_opml",
|
||||
"invalid_opml_version",
|
||||
]
|
||||
|
||||
for file_name in samples:
|
||||
with pytest.raises(ValueError):
|
||||
OPML.from_xml(__read_sample(file_name))
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
OPML(version="unsupported")
|
||||
|
||||
|
||||
def test_construction_1(snapshot):
|
||||
opml = OPML(
|
||||
version="2.0",
|
||||
head=Head(title="Rust Feeds"),
|
||||
body=Body(
|
||||
outlines=[
|
||||
Outline(
|
||||
text="Rust Blog", xml_url="https://blog.rust-lang.org/feed.xml"
|
||||
),
|
||||
Outline(
|
||||
text="Inside Rust",
|
||||
xml_url="https://blog.rust-lang.org/inside-rust/feed.xml",
|
||||
),
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
xml = f"{opml.to_xml()}\n"
|
||||
snapshot.snapshot_dir = "tests/samples"
|
||||
snapshot.assert_match(xml, "construction_1.opml")
|
||||
|
||||
|
||||
def test_construction_2(snapshot):
|
||||
opml = OPML(
|
||||
head=Head(title="Rust & Mozilla Feeds"),
|
||||
body=Body(
|
||||
outlines=[
|
||||
Outline(
|
||||
text="Rust Feeds",
|
||||
outlines=[
|
||||
Outline(
|
||||
text="Rust Blog",
|
||||
xml_url="https://blog.rust-lang.org/feed.xml",
|
||||
),
|
||||
Outline(
|
||||
text="Inside Rust",
|
||||
xml_url="https://blog.rust-lang.org/inside-rust/feed.xml",
|
||||
),
|
||||
],
|
||||
),
|
||||
Outline(
|
||||
text="Mozilla Feeds",
|
||||
outlines=[
|
||||
Outline(
|
||||
text="Mozilla Blog",
|
||||
xml_url="https://blog.mozilla.org/feed",
|
||||
),
|
||||
Outline(
|
||||
text="Mozilla Hacks",
|
||||
xml_url="https://hacks.mozilla.org/feed",
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
xml = f"{opml.to_xml()}\n"
|
||||
snapshot.snapshot_dir = "tests/samples"
|
||||
snapshot.assert_match(xml, "construction_2.opml")
|
||||
|
||||
|
||||
def test_construction_3(snapshot):
|
||||
opml = OPML(
|
||||
version="2.0",
|
||||
head=Head(
|
||||
title="Title",
|
||||
date_created="Date Created",
|
||||
date_modified="Date Modified",
|
||||
owner_name="Owner Name",
|
||||
owner_email="Owner Email",
|
||||
owner_id="Owner ID",
|
||||
docs="http://dev.opml.org/spec2.html",
|
||||
expansion_state="0,1",
|
||||
vert_scroll_state=0,
|
||||
window_top=1,
|
||||
window_left=2,
|
||||
window_bottom=3,
|
||||
window_right=4,
|
||||
),
|
||||
body=Body(
|
||||
outlines=[
|
||||
Outline(
|
||||
text="Outline Text",
|
||||
type="Outline Type",
|
||||
is_breakpoint=True,
|
||||
is_comment=True,
|
||||
created="Outline Date",
|
||||
category="Outline Category",
|
||||
xml_url="Outline XML URL",
|
||||
description="Outline Description",
|
||||
html_url="Outline HTML URL",
|
||||
language="Outline Language",
|
||||
title="Outline Title",
|
||||
version="Outline Version",
|
||||
url="Outline URL",
|
||||
)
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
xml = f"{opml.to_xml()}\n"
|
||||
snapshot.snapshot_dir = "tests/samples"
|
||||
snapshot.assert_match(xml, "construction_3.opml")
|
Loading…
Reference in New Issue