pax_global_header00006660000000000000000000000064134142222430014507gustar00rootroot0000000000000052 comment=21ad5fddc35e736d2f249958b6405e223c690d4e multisplitby-0.0.1/000077500000000000000000000000001341422224300142465ustar00rootroot00000000000000multisplitby-0.0.1/.bumpversion.cfg000066400000000000000000000013521341422224300173570ustar00rootroot00000000000000[bumpversion] current_version = 0.0.1 commit = True tag = False parse = (?P\d+)\.(?P\d+)\.(?P\d+)(?:-(?P[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+(?P[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))? serialize = {major}.{minor}.{patch}-{release}+{build} {major}.{minor}.{patch}+{build} {major}.{minor}.{patch}-{release} {major}.{minor}.{patch} [bumpversion:part:release] optional_value = production first_value = dev values = dev production [bumpverion:part:build] values = [0-9A-Za-z-]+ [bumpversion:file:setup.cfg] search = version = {current_version} replace = version = {new_version} [bumpversion:file:src/multisplitby/__init__.py] search = VERSION = '{current_version}' replace = VERSION = '{new_version}' multisplitby-0.0.1/.flake8000066400000000000000000000010321341422224300154150ustar00rootroot00000000000000######################### # Flake8 Configuration # # (.flake8) # ######################### [flake8] exclude = *.egg-info, *.pyc, .cache, .eggs .git, .tox, __pycache__, build, dist, docs/source/conf.py, format = ${cyan}%(path)s${reset}:${yellow_bold}%(row)d${reset}:${green_bold}%(col)d${reset}: ${red_bold}%(code)s${reset} %(text)s ignore = max-line-length = 120 # flake8-import-order application-import-names = multigroupby import-order-style = pycharm # mccabe max-complexity = 10 multisplitby-0.0.1/.gitignore000066400000000000000000000022631341422224300162410ustar00rootroot00000000000000# 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/ *.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/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # 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/ multisplitby-0.0.1/.travis.yml000066400000000000000000000012271341422224300163610ustar00rootroot00000000000000sudo: false cache: pip language: python python: - 3.6 - 3.5 stages: - lint - docs - test env: - TOXENV=py - TOXENV=doctest jobs: include: # lint stage - stage: lint env: TOXENV=manifest - env: TOXENV=flake8 - env: TOXENV=pyroma - env: TOXENV=xenon - env: TOXENV=mypy # docs stage - stage: docs env: TOXENV=doc8 - env: TOXENV=readme # tests stage gets propagated automatically install: - sh -c 'if [ "$TOXENV" = "py" ]; then pip install tox codecov coverage; else pip install tox; fi' script: - tox after_success: - sh -c 'if [ "$TOXENV" = "py" ]; then tox -e coverage-report; codecov; fi' multisplitby-0.0.1/LICENSE000066400000000000000000000020641341422224300152550ustar00rootroot00000000000000MIT License Copyright (c) 2018 Charles Tapley Hoyt 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. multisplitby-0.0.1/MANIFEST.in000066400000000000000000000004521341422224300160050ustar00rootroot00000000000000graft src graft tests recursive-include docs/source *.py recursive-include docs/source *.rst include docs/Makefile global-exclude *.py[cod] __pycache__ *.so *.dylib .DS_Store *.gpickle exclude .bumpversion.cfg .dockerignore Dockerfile include *.rst *.txt *.yml LICENSE *.ini .coveragerc .flake8 multisplitby-0.0.1/README.rst000066400000000000000000000037751341422224300157510ustar00rootroot00000000000000multisplitby |build| |coverage| =============================== Split an iterable into multiple using arbitrary predicates. This package comes with a single function: ``multisplitby.multi_split_by``. For all lists ``values`` and ``predicates``, the following conditions are always true: 1. ``1 + len(predicates) = len(list(multi_split_by(values, predicates)))`` 2. ``values == itertools.chain.from_iterable(multi_split_by(values, predicates))`` Normal usage with one predicate: .. code-block:: python >>> values = range(4) >>> predicates = [lambda x: 2 < x] >>> list(map(list, multi_split_by(values, predicates))) [[0, 1, 2], [3]] Normal usage with several predicates: .. code-block:: python >>> values = range(9) >>> predicates = [lambda x: 2 < x, lambda x: 4 < x, lambda x: 7 < x] >>> list(map(list, multi_split_by(values, predicates))) [[0, 1, 2], [3, 4], [5, 6, 7], [8]] If no values are given, will result in ``|predicates| + 1`` generators, all yielding empty lists. .. code-block:: python >>> values = [] >>> predicates = [lambda x: 2 < x, lambda x: 4 < x, lambda x: 7 < x] >>> list(map(list, multi_split_by(values, predicates))) [[], [], [], []] If no predicates are given, will result in a single generator that yields the original list: .. code-block:: python >>> values = range(4) >>> predicates = [] >>> list(map(list, multi_split_by(values, predicates))) [[0, 1, 2, 3]] Installation ------------ Install from PyPI with: .. code-block:: bash $ pip install multisplitby or get the latest code from `GitHub `_ with: .. code-block:: bash $ git clone https://github.com/cthoyt/multisplitby.git $ cd multisplitby $ pip install -e . .. |build| image:: https://travis-ci.com/cthoyt/multisplitby.svg?branch=master :target: https://travis-ci.com/cthoyt/multisplitby .. |coverage| image:: https://codecov.io/gh/cthoyt/multisplitby/branch/master/graph/badge.svg :target: https://codecov.io/gh/cthoyt/multisplitby multisplitby-0.0.1/setup.cfg000066400000000000000000000031321341422224300160660ustar00rootroot00000000000000########################## # Setup.py Configuration # ########################## [metadata] name = multisplitby version = 0.0.1 url = https://github.com/cthoyt/multisplitby download_url = https://github.com/cthoyt/multisplitby/releases project_urls = Bug Tracker = https://github.com/cthoyt/multisplitby/issues Source Code = https://github.com/cthoyt/multisplitby author = Charles Tapley Hoyt author_email = cthoyt@gmail.com maintainer = Charles Tapley Hoyt maintainer_email = cthoyt@gmail.com classifiers = Development Status :: 1 - Planning Intended Audience :: Developers License :: OSI Approved :: MIT License Operating System :: OS Independent Programming Language :: Python Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3 :: Only license = MIT license_file = LICENSE description = Split an iterable into multiple using arbitrary predicates. long_description = file: README.rst keywords = iteration [options] python_requires = >=3.5 tests_require = tox packages = find: package_dir = = src zip_safe = true [options.packages.find] where = src ###################### # Doc8 Configuration # # (doc8.ini) # ###################### [doc8] max-line-length = 120 ########################## # Coverage Configuration # # (.coveragerc) # ########################## [coverage:run] branch = True source = multisplitby [coverage:paths] source = src/multisplitby .tox/*/lib/python*/site-packages/multisplitby [coverage:report] show_missing = True multisplitby-0.0.1/setup.py000066400000000000000000000002071341422224300157570ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Setup module for Bio2BEL miRBase.""" from setuptools import setup if __name__ == '__main__': setup() multisplitby-0.0.1/src/000077500000000000000000000000001341422224300150355ustar00rootroot00000000000000multisplitby-0.0.1/src/multisplitby/000077500000000000000000000000001341422224300175765ustar00rootroot00000000000000multisplitby-0.0.1/src/multisplitby/__init__.py000066400000000000000000000047141341422224300217150ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Split an iterable into multiple using arbitrary predicates.""" from typing import Callable, Iterable, TypeVar, Union __all__ = [ 'VERSION', 'multi_split_by', ] VERSION = '0.0.1' class _Sentinel: pass F = TypeVar('F') MaybeF = Union[F, _Sentinel] def multi_split_by(values: Iterable[F], predicates: Iterable[Callable[[F], bool]]) -> Iterable[Iterable[F]]: """Split the iterator after the predicate becomes true, then repeat for every remaining iterable. For all lists ``values`` and ``predicates``, the following conditions are always true: 1. ``1 + len(predicates) = len(list(multi_split_by(values, predicates)))`` 2. ``values == itertools.chain.from_iterable(multi_split_by(values, predicates))`` Normal usage with one predicate: >>> values = range(4) >>> predicates = [lambda x: 2 < x] >>> list(map(list, multi_split_by(values, predicates))) [[0, 1, 2], [3]] Normal usage with several predicates: >>> values = range(9) >>> predicates = [lambda x: 2 < x, lambda x: 4 < x, lambda x: 7 < x] >>> list(map(list, multi_split_by(values, predicates))) [[0, 1, 2], [3, 4], [5, 6, 7], [8]] If no values are given, will result in |predicates| + 1 generators, all yielding empty lists. >>> values = [] >>> predicates = [lambda x: 2 < x, lambda x: 4 < x, lambda x: 7 < x] >>> list(map(list, multi_split_by(values, predicates))) [[], [], [], []] If no predicates are given, will result in a single generator that yields the original list: >>> values = range(4) >>> predicates = [] >>> list(map(list, multi_split_by(values, predicates))) [[0, 1, 2, 3]] """ last_value = _Sentinel() # type: MaybeF values = iter(values) def generator(p: Callable[[F], bool]) -> Iterable[F]: """Yield values until the given predicate is met, keeping the last value saved each time.""" nonlocal last_value if not isinstance(last_value, _Sentinel): yield last_value for value in values: if p(value): last_value = value return yield value yield from map(generator, predicates) def generator_last() -> Iterable[F]: """Yield the remaining value on which the last predicate stopped, then the remainder of the iterable.""" if not isinstance(last_value, _Sentinel): yield last_value yield from values yield generator_last() multisplitby-0.0.1/tests/000077500000000000000000000000001341422224300154105ustar00rootroot00000000000000multisplitby-0.0.1/tests/__init__.py000066400000000000000000000000671341422224300175240ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Tests for multigroupby.""" multisplitby-0.0.1/tests/test_multisplitby.py000066400000000000000000000057551341422224300215760ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Tests for multigroupby.""" import unittest from typing import Iterable from multisplitby import multi_split_by def _consume(iterable: Iterable): def yield_all(): yield from iterable return list(yield_all()) def predicate_1(x: int) -> bool: """Return true if the integer is 3.""" return x == 3 def predicate_2(x: int) -> bool: """Return true if the integer is 6.""" return x == 6 def predicate_3(x: int) -> bool: """Return true if the integer is 8.""" return x == 8 class TestIter(unittest.TestCase): """Test :mod:`multisplitby`.""" def test_split_by_iterable_is_empty(self): """Test when an empty iterable is given.""" integers = [] predicates = [predicate_1, predicate_2] r = list(multi_split_by(integers, predicates)) self.assertEqual(1 + len(predicates), len(r)) a, b, c = r self.assertIsNotNone(a) self.assertIsNotNone(b) self.assertIsNotNone(c) a = _consume(a) b = _consume(b) c = _consume(c) self.assertEqual([], a) self.assertEqual([], b) self.assertEqual([], c) def test_split_by_predicates_is_empty(self): """Test when empty predicates are given.""" integers = [1, 2, 3, 4] predicates = [] r = tuple(multi_split_by(integers, predicates)) self.assertEqual(1 + len(predicates), len(r)) a, = r self.assertIsNotNone(a) a = _consume(a) self.assertEqual([1, 2, 3, 4], a) def test_split_by_two(self): """Test the :func:`multisplitby.multi_split_by` function with two predicates.""" integers = [1, 2, 3, 4, 5, 6, 7] predicates = [predicate_1, predicate_2] # expected = [[1, 2], [3, 4, 5], [6, 7]] r = tuple(multi_split_by(integers, predicates)) self.assertEqual(1 + len(predicates), len(r)) a, b, c = r self.assertIsNotNone(a) self.assertIsNotNone(b) self.assertIsNotNone(c) a = _consume(a) b = _consume(b) c = _consume(c) self.assertEqual([1, 2], a) self.assertEqual([3, 4, 5], b) self.assertEqual([6, 7], c) def test_split_by_three(self): """Test the :func:`multisplitby.multi_split_by` function with three predicates.""" integers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] predicates = [predicate_1, predicate_2, predicate_3] # expected = [[1, 2], [3, 4, 5], [6, 7], [8, 9, 10]] r = tuple(multi_split_by(integers, predicates)) self.assertEqual(1 + len(predicates), len(r)) a, b, c, d = r self.assertIsNotNone(a) self.assertIsNotNone(b) self.assertIsNotNone(c) self.assertIsNotNone(d) a = _consume(a) b = _consume(b) c = _consume(c) d = _consume(d) self.assertEqual([1, 2], a) self.assertEqual([3, 4, 5], b) self.assertEqual([6, 7], c) self.assertEqual([8, 9, 10], d) multisplitby-0.0.1/tox.ini000066400000000000000000000044211341422224300155620ustar00rootroot00000000000000[tox] envlist = coverage-clean manifest flake8 mypy xenon pyroma readme doc8 doctest py coverage-report [testenv] commands = coverage run -p -m pytest tests {posargs} passenv = TRAVIS CI deps = coverage pytest whitelist_externals = /bin/cat /bin/cp /bin/mkdir [testenv:coverage-clean] deps = coverage skip_install = true commands = coverage erase [testenv:manifest] deps = check-manifest skip_install = true commands = check-manifest [testenv:flake8] skip_install = true deps = flake8 flake8-docstrings>=0.2.7 flake8-import-order>=0.9 pep8-naming flake8-colors commands = flake8 src/multisplitby/ tests/ setup.py [testenv:xenon] deps = xenon skip_install = true commands = xenon --max-average A --max-modules A --max-absolute B . description = Run the xenon tool to monitor code complexity. [testenv:mypy] deps = mypy skip_install = true commands = mypy --ignore-missing-imports src/multisplitby/ description = Run the mypy tool to check static typing on the project. [testenv:pyroma] deps = pygments pyroma skip_install = true commands = pyroma --min=10 . description = Run the pyroma tool to check the project's package friendliness. [testenv:readme] commands = rst-lint README.rst skip_install = true deps = restructuredtext_lint pygments [testenv:doc8] skip_install = true deps = sphinx doc8 commands = doc8 README.rst [testenv:doctest] commands = python -m doctest src/multisplitby/__init__.py [testenv:coverage-report] deps = coverage skip_install = true commands = coverage combine coverage report #################### # Deployment tools # #################### [testenv:bumpversion] commands = bumpversion {posargs} passenv = HOME skip_install = true deps = bumpversion [testenv:build] skip_install = true deps = wheel setuptools commands = python setup.py -q sdist bdist_wheel [testenv:release] skip_install = true deps = {[testenv:build]deps} twine >= 1.5.0 commands = {[testenv:build]commands} twine upload --skip-existing dist/* [testenv:finish] skip_install = true passenv = HOME deps = {[testenv:release]deps} bumpversion commands = bumpversion release {[testenv:release]commands} git push bumpversion patch