pax_global_header00006660000000000000000000000064141406463570014524gustar00rootroot0000000000000052 comment=3d55591ac075690924ff73ac9c81b75967f2b9e2 bracex-2.2.1/000077500000000000000000000000001414064635700127725ustar00rootroot00000000000000bracex-2.2.1/.codecov.yml000066400000000000000000000000651414064635700152160ustar00rootroot00000000000000comment: false coverage: status: patch: false bracex-2.2.1/.coveragerc000066400000000000000000000001061414064635700151100ustar00rootroot00000000000000[run] omit= bracex/pep562.py [report] omit= bracex/pep562.py bracex-2.2.1/.github/000077500000000000000000000000001414064635700143325ustar00rootroot00000000000000bracex-2.2.1/.github/FUNDING.yml000066400000000000000000000001061414064635700161440ustar00rootroot00000000000000github: facelessuser custom: - "https://www.paypal.me/facelessuser" bracex-2.2.1/.github/labels.yml000066400000000000000000000006231414064635700163200ustar00rootroot00000000000000template: 'facelessuser:master-labels:labels.yml:master' # Wildcard labels brace_expansion: true extended_glob: true minus_negate: false rules: - labels: ['C: infrastructure'] patterns: ['*|{tools,requirements,.github}/**|!*.md'] - labels: ['C: source'] patterns: ['bracex/**'] - labels: ['C: tests'] patterns: ['tests/**'] - labels: ['C: docs'] patterns: ['docs/**|*.md'] bracex-2.2.1/.github/workflows/000077500000000000000000000000001414064635700163675ustar00rootroot00000000000000bracex-2.2.1/.github/workflows/build.yml000066400000000000000000000053351414064635700202170ustar00rootroot00000000000000name: pull request on: push: branches: - 'master' tags: - '**' pull_request: branches: - '**' jobs: tests: strategy: fail-fast: false max-parallel: 4 matrix: platform: [ubuntu-latest, windows-latest] python-version: - "3.6" - "3.7" - "3.8" - "3.9" - "3.10" include: - python-version: "3.6" tox-env: py36 - python-version: "3.7" tox-env: py37 - python-version: "3.8" tox-env: py38 - python-version: "3.9" tox-env: py39 - python-version: "3.10" tox-env: py310 env: TOXENV: ${{ matrix.tox-env }} runs-on: ${{ matrix.platform }} steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} if: "!endsWith(matrix.python-version, '-dev')" uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Set up development Python ${{ matrix.python-version }} if: endsWith(matrix.python-version, '-dev') uses: deadsnakes/action@v1.0.0 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip setuptools tox coverage codecov - name: Test run: | python -m tox - name: Upload Results if: success() uses: codecov/codecov-action@v1 with: file: ./coverage.xml flags: unittests name: ${{ matrix.platform }}-${{ matrix.tox-env }} fail_ci_if_error: false lint: strategy: max-parallel: 4 matrix: python-version: [3.9] env: TOXENV: lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip setuptools tox - name: Lint run: | python -m tox documents: strategy: max-parallel: 4 matrix: python-version: [3.9] env: TOXENV: documents runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip setuptools tox - name: Install Aspell run: | sudo apt-get install aspell aspell-en - name: Build documents run: | python -m tox bracex-2.2.1/.github/workflows/deploy.yml000066400000000000000000000027441414064635700204150ustar00rootroot00000000000000name: deploy on: push: tags: - '*' jobs: documents: strategy: max-parallel: 4 matrix: python-version: [3.9] runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip setuptools python -m pip install -r requirements/docs.txt - name: Deploy documents run: | git config user.name facelessuser git config user.email "${{ secrets.GH_EMAIL }}" git remote add gh-token "https://${{ secrets.GH_TOKEN }}@github.com/facelessuser/bracex.git" git fetch gh-token && git fetch gh-token gh-pages:gh-pages python -m mkdocs gh-deploy -v --clean --remote-name gh-token git push gh-token gh-pages pypi: strategy: matrix: distribution: [bdist_wheel, sdist] runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 with: python-version: 3.9 - name: Package run: | pip install --upgrade setuptools wheel python setup.py ${{ matrix.distribution }} - name: Publish uses: pypa/gh-action-pypi-publish@v1.4.1 with: user: __token__ password: ${{ secrets.PYPI_PWD }} bracex-2.2.1/.gitignore000066400000000000000000000022521414064635700147630ustar00rootroot00000000000000.DS_Store # 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 # 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/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py # 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/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ # pytest .pytest_cache/ *.patch bracex-2.2.1/.pyspelling.yml000066400000000000000000000043571414064635700157720ustar00rootroot00000000000000spellchecker: aspell matrix: - name: mkdocs sources: - site/**/*.html hunspell: d: en_US aspell: lang: en dictionary: wordlists: - docs/src/dictionary/en-custom.txt output: build/dictionary/mkdocs.dic pipeline: - pyspelling.filters.html: comments: false attributes: - title - alt ignores: - 'code, pre' - 'a:is(.magiclink-compare, .magiclink-commit, .magiclink-repository)' - 'span.keys' - '.MathJax_Preview, .md-nav__link, .md-footer-custom-text, .md-source__repository, .headerlink, .md-icon' - '.md-footer-social__link' - pyspelling.filters.url: - name: markdown sources: - README.md hunspell: d: en_US aspell: lang: en dictionary: wordlists: - docs/src/dictionary/en-custom.txt output: build/dictionary/mkdocs.dic pipeline: - pyspelling.filters.markdown: markdown_extensions: - pymdownx.superfences: - pymdownx.highlight: - pyspelling.filters.html: comments: false attributes: - title - alt ignores: - :is(code, pre) - pyspelling.filters.url: - name: python sources: - setup.py - '{bracex,tests,tools}/**/*.py' hunspell: d: en_US aspell: lang: en dictionary: wordlists: - docs/src/dictionary/en-custom.txt output: build/dictionary/python.dic pipeline: - pyspelling.filters.python: group_comments: true - pyspelling.flow_control.wildcard: allow: - py-comment - pyspelling.filters.context: context_visible_first: true delimiters: # Ignore lint (noqa) and coverage (pragma) as well as shebang (#!) - open: '^(?: *(?:noqa\b|pragma: no cover)|!|type:)' close: '$' # Ignore Python encoding string -*- encoding stuff -*- - open: '^ *-\*-' close: '-\*-$' - pyspelling.filters.context: context_visible_first: true escapes: '\\[\\`]' delimiters: # Ignore multiline content between fences (fences can have 3 or more back ticks) # ``` # content # ``` - open: '(?s)^(?P *`{3,})$' close: '^(?P=open)$' # Ignore text between inline back ticks - open: '(?P`+)' close: '(?P=open)' - pyspelling.filters.url: bracex-2.2.1/LICENSE.md000066400000000000000000000020641414064635700144000ustar00rootroot00000000000000MIT License Copyright (c) 2018 - 2021 facelessuser 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. bracex-2.2.1/MANIFEST.in000066400000000000000000000007151414064635700145330ustar00rootroot00000000000000recursive-include bracex *.py py.typed recursive-include tests *.txt *.py recursive-include docs/src/markdown *.md *.png *.gif *.html recursive-include docs/src/dictionary *.txt recursive-include docs/theme *.js *.css *.html recursive-include requirements *.txt recursive-exclude site * include .pyspelling.yml include mkdocs.yml include setup.py include setup.cfg include tox.ini include LICENSE.md include README.md include MANIFEST.in include pyproject.toml bracex-2.2.1/README.md000066400000000000000000000072331414064635700142560ustar00rootroot00000000000000[![Donate via PayPal][donate-image]][donate-link] [![Discord][discord-image]][discord-link] [![Build][github-ci-image]][github-ci-link] [![Coverage Status][codecov-image]][codecov-link] [![PyPI Version][pypi-image]][pypi-link] [![PyPI - Python Version][python-image]][pypi-link] ![License][license-image-mit] # Bracex Bracex is a brace expanding library (à la Bash) for Python. Brace expanding is used to generate arbitrary strings. ```console $ echo {{a,b},c}d ad bd cd ``` Bracex adds this ability to Python: ```python >>> bracex.expand(r'file-{{a,b},c}d.txt') ['file-ad.txt', 'file-bd.txt', 'file-cd.txt'] ``` and as a command: ```console $ python3 -m bracex -0 "base/{a,b}/{1..2}" | xargs -0 mkdir -p $ tree base/ base/ ├── a │ ├── 1 │ └── 2 └── b ├── 1 └── 2 ``` - **Why Bracex over other solutions?** Bracex actually follows pretty closely to how Bash processes braces. It is not a 1:1 implementation of how Bash handles braces, but generally, it follows very closely. Almost all of the test cases are run through Bash first, then our implementation is compared against the results Bash gives. There are a few cases where we have purposely deviated. For instance, we are not handling Bash's command line inputs, so we are not giving special meaning to back ticks and quotes at this time. On the command line Bracex can handle more expansions than Bash itself. ## Install ```console $ pip install bracex ``` ## Documentation Documentation is found here: https://facelessuser.github.io/bracex/. ## License MIT License Copyright (c) 2018 - 2021 Isaac Muse 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. [github-ci-image]: https://github.com/facelessuser/bracex/workflows/build/badge.svg?branch=master&event=push [github-ci-link]: https://github.com/facelessuser/bracex/actions?query=workflow%3Abuild+branch%3Amaster [discord-image]: https://img.shields.io/discord/678289859768745989?logo=discord&logoColor=aaaaaa&color=mediumpurple&labelColor=333333 [discord-link]:https://discord.gg/TWs8Tgr [codecov-image]: https://img.shields.io/codecov/c/github/facelessuser/bracex/master.svg?logo=codecov&logoColor=aaaaaa&labelColor=333333 [codecov-link]: https://codecov.io/github/facelessuser/bracex [pypi-image]: https://img.shields.io/pypi/v/bracex.svg?logo=pypi&logoColor=aaaaaa&labelColor=333333 [pypi-link]: https://pypi.python.org/pypi/bracex [python-image]: https://img.shields.io/pypi/pyversions/bracex?logo=python&logoColor=aaaaaa&labelColor=333333 [license-image-mit]: https://img.shields.io/badge/license-MIT-blue.svg?labelColor=333333 [donate-image]: https://img.shields.io/badge/Donate-PayPal-3fabd1?logo=paypal [donate-link]: https://www.paypal.me/facelessuser bracex-2.2.1/bracex/000077500000000000000000000000001414064635700142365ustar00rootroot00000000000000bracex-2.2.1/bracex/__init__.py000066400000000000000000000357171414064635700163640ustar00rootroot00000000000000""" A Bash like brace expander. Licensed under MIT Copyright (c) 2018 - 2020 Isaac Muse 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. """ import itertools import math import re from typing import List, Iterator, Pattern, Optional, Match, Iterable, AnyStr from . import __meta__ __all__ = ('expand', 'iexpand') __version__ = __meta__.__version__ __version_info__ = __meta__.__version_info__ _alpha = [chr(x) if x != 0x5c else '' for x in range(ord('A'), ord('z') + 1)] _nalpha = list(reversed(_alpha)) RE_INT_ITER = re.compile(r'(-?\d+)\.{2}(-?\d+)(?:\.{2}(-?\d+))?(?=\})') RE_CHR_ITER = re.compile(r'([A-Za-z])\.{2}([A-Za-z])(?:\.{2}(-?\d+))?(?=\})') DEFAULT_LIMIT = 1000 class ExpansionLimitException(Exception): """Brace expansion limit exception.""" def expand(string: AnyStr, keep_escapes: bool = False, limit: int = DEFAULT_LIMIT) -> List[AnyStr]: """Expand braces.""" return list(iexpand(string, keep_escapes, limit)) def iexpand(string: AnyStr, keep_escapes: bool = False, limit: int = DEFAULT_LIMIT) -> Iterator[AnyStr]: """Expand braces and return an iterator.""" if isinstance(string, bytes): for entry in ExpandBrace(keep_escapes, limit).expand(string.decode('latin-1')): yield entry.encode('latin-1') else: for entry in ExpandBrace(keep_escapes, limit).expand(string): yield entry class StringIter: """Preprocess replace tokens.""" def __init__(self, string: str) -> None: """Initialize.""" self._string = string self._index = 0 def __iter__(self) -> "StringIter": # pragma: no cover """Iterate.""" return self def __next__(self) -> str: """Python 3 iterator compatible next.""" return self.iternext() def match(self, pattern: Pattern[str]) -> Optional[Match[str]]: """Perform regex match at index.""" m = pattern.match(self._string, self._index) if m: self._index = m.end() return m @property def index(self) -> int: """Get current index.""" return self._index def previous(self) -> str: # pragma: no cover """Get previous char.""" return self._string[self._index - 1] def advance(self, count: int) -> None: """Advanced the index.""" self._index += count def rewind(self, count: int) -> None: """Rewind index.""" if count > self._index: # pragma: no cover raise ValueError("Can't rewind past beginning!") self._index -= count def iternext(self) -> str: """Iterate through characters of the string.""" try: char = self._string[self._index] self._index += 1 except IndexError: # pragma: no cover raise StopIteration return char class ExpandBrace: """Expand braces like in Bash.""" def __init__(self, keep_escapes: bool = False, limit: int = DEFAULT_LIMIT) -> None: """Initialize.""" self.max_limit = limit self.count = 0 self.expanding = False self.keep_escapes = keep_escapes def update_count_seq(self, count: List[int]) -> None: """Update the count from a list after evaluating a brace sequence and assert if count exceeds the max limit.""" self.count -= sum(count) prod = 1 for c in count: prod *= c self.update_count(prod) def update_count(self, count: int) -> None: """Update the count and assert if count exceeds the max limit.""" self.count += count if self.max_limit > 0 and self.count > self.max_limit: raise ExpansionLimitException( 'Brace expansion has exceeded the limit of {:d}'.format(self.max_limit) ) def set_expanding(self) -> bool: """Set that we are expanding a sequence, and return whether a release is required by the caller.""" status = not self.expanding if status: self.expanding = True return status def is_expanding(self) -> bool: """Get status of whether we are expanding.""" return self.expanding def release_expanding(self, release: bool) -> None: """Release the expand status.""" if release: self.expanding = False def get_escape(self, c: str, i: StringIter) -> str: """Get an escape.""" try: escaped = next(i) except StopIteration: escaped = '' return c + escaped if self.keep_escapes else escaped def squash(self, a: Iterable[str], b: Iterable[str]) -> Iterator[str]: """ Returns a generator that squashes two iterables into one. ``` ['this', 'that'], [[' and', ' or']] => ['this and', 'this or', 'that and', 'that or'] ``` """ for x in itertools.product(a, b): yield ''.join(x) if isinstance(x, tuple) else x def get_literals(self, c: str, i: StringIter, depth: int) -> Optional[Iterator[str]]: """ Get a string literal. Gather all the literal chars up to opening curly or closing brace. Also gather chars between braces and commas within a group (is_expanding). """ result = iter(['']) is_dollar = False count = True seq_count = [] try: while c: value = [c] # type: Iterable[str] ignore_brace = is_dollar is_dollar = False if c == '$': is_dollar = True elif c == '\\': value = [self.get_escape(c, i)] elif not ignore_brace and c == '{': # Try and get the group index = i.index try: current_count = self.count seq = self.get_sequence(next(i), i, depth + 1) if seq: if self.max_limit > 0: diff = self.count - current_count seq_count.append(diff) count = False value = seq except StopIteration: # Searched to end of string # and still didn't find it. i.rewind(i.index - index) elif self.is_expanding() and c in (',', '}'): # We are Expanding within a group and found a group delimiter # Return what we gathered before the group delimiters. i.rewind(1) if count: self.update_count(1) else: self.update_count_seq(seq_count) return result # Squash the current set of literals. result = self.squash(result, value) c = next(i) except StopIteration: if self.is_expanding(): return None if count: self.update_count(1) else: self.update_count_seq(seq_count) return result def get_sequence(self, c: str, i: StringIter, depth: int) -> Optional[Iterator[str]]: """ Get the sequence. Get sequence between `{}`, such as: `{a,b}`, `{1..2[..inc]}`, etc. It will basically crawl to the end or find a valid series. """ result = iter([]) # type: Iterator[str] release = self.set_expanding() has_comma = False # Used to indicate validity of group (`{1..2}` are an exception). is_empty = True # Tracks whether the current slot is empty `{slot,slot,slot}`. # Detect numerical and alphabetic series: `{1..2}` etc. i.rewind(1) item = self.get_range(i) i.advance(1) if item is not None: self.release_expanding(release) return item try: while True: # Bash has some special top level logic. if `}` follows `{` but hasn't matched # a group yet, keep going except when the first 2 bytes are `{}` which gets # completely ignored. keep_looking = depth == 1 and not has_comma # and i.index not in self.skip_index if (c == '}' and (not keep_looking or i.index == 2)): # If there is no comma, we know the sequence is bogus. if is_empty: result = itertools.chain(result, ['']) if not has_comma: result = (''.join(['{', literal, '}']) for literal in result) self.release_expanding(release) return result elif c == ',': # Must be the first element in the list. has_comma = True if is_empty: result = itertools.chain(result, ['']) else: is_empty = True else: if c == '}': # Top level: If we didn't find a comma, we haven't # completed the top level group. Request more and # append to what we already have for the first slot. if is_empty and not has_comma: result = itertools.chain(result, [c]) else: result = self.squash(result, [c]) value = self.get_literals(next(i), i, depth) if value is not None: result = self.squash(result, value) is_empty = False else: # Lower level: Try to find group, but give up if cannot acquire. value = self.get_literals(c, i, depth) if value is not None: result = itertools.chain(result, value) is_empty = False c = next(i) except StopIteration: self.release_expanding(release) raise def get_range(self, i: StringIter) -> Optional[Iterator[str]]: """ Check and retrieve range if value is a valid range. Here we are looking to see if the value is series or range. We look for `{1..2[..inc]}` or `{a..z[..inc]}` (negative numbers are fine). """ try: m = i.match(RE_INT_ITER) if m: return self.get_int_range(*m.groups()) m = i.match(RE_CHR_ITER) if m: return self.get_char_range(*m.groups()) except ExpansionLimitException: raise except Exception: # pragma: no cover # TODO: We really should never fail here, # but if we do, assume the sequence range # was invalid. This catch can probably # be removed in the future with more testing. pass return None def format_values(self, values: Iterable[int], padding: int) -> Iterator[str]: """Get padding adjusting for negative values.""" for value in values: yield "{:0{pad}d}".format(value, pad=padding) if padding else str(value) def get_int_range(self, start: str, end: str, increment: Optional[str] = None) -> Iterator[str]: """Get an integer range between start and end and increments of increment.""" first, last = int(start), int(end) inc = int(increment) if increment is not None else 1 max_length = max(len(start), len(end)) # Zero doesn't make sense as an incrementer # but like bash, just assume one if inc == 0: inc = 1 if start[0] == '-': start = start[1:] if end[0] == '-': end = end[1:] if (len(start) > 1 and start[0] == '0') or (len(end) > 1 and end[0] == '0'): padding = max_length else: padding = 0 if first < last: self.update_count(math.ceil(abs(((last + 1) - first) / inc))) r = range(first, last + 1, -inc if inc < 0 else inc) else: self.update_count(math.ceil(abs(((first + 1) - last) / inc))) r = range(first, last - 1, inc if inc < 0 else -inc) return self.format_values(r, padding) def get_char_range(self, start: str, end: str, increment: Optional[str] = None) -> Iterator[str]: """Get a range of alphabetic characters.""" inc = int(increment) if increment else 1 if inc < 0: inc = -inc # Zero doesn't make sense as an incrementer # but like bash, just assume one if inc == 0: inc = 1 inverse = start > end alpha = _nalpha if inverse else _alpha first = alpha.index(start) last = alpha.index(end) if first < last: self.update_count(math.ceil(((last + 1) - first) / inc)) return itertools.islice(alpha, first, last + 1, inc) else: self.update_count(math.ceil(((first + 1) - last) / inc)) return itertools.islice(alpha, last, first + 1, inc) def expand(self, string: str) -> Iterator[str]: """Expand.""" self.expanding = False empties = [] found_literal = False if string: i = StringIter(string) value = self.get_literals(next(i), i, 0) if value is not None: for x in value: # We don't want to return trailing empty strings. # Store empty strings and output only when followed by a literal. if not x: empties.append(x) continue found_literal = True while empties: yield empties.pop(0) yield x empties = [] # We found no literals so return an empty string if not found_literal: yield "" bracex-2.2.1/bracex/__main__.py000066400000000000000000000046601414064635700163360ustar00rootroot00000000000000""" Expands a bash-style brace expression, and outputs each expansion. Licensed under MIT Copyright (c) 2018 - 2020 Isaac Muse Copyright (c) 2021 Alex Willmer 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. """ import argparse import bracex from typing import Optional def main(argv: Optional[str] = None) -> None: """Accept command line arguments and output brace expansion to stdout.""" parser = argparse.ArgumentParser( prog='python -m bracex', description='Expands a bash-style brace expression, and outputs each expansion.', allow_abbrev=False, ) parser.add_argument( 'expression', help="Brace expression to expand", ) terminators = parser.add_mutually_exclusive_group() terminators.add_argument( '--terminator', '-t', default='\n', metavar='STR', help="Terminate each expansion with string STR (default: \\n)", ) terminators.add_argument( '-0', action='store_const', const='\0', dest='terminator', help="Terminate each expansion with a NUL character", ) parser.add_argument( '--version', action='version', version=bracex.__version__, ) args = parser.parse_args(argv) for expansion in bracex.iexpand(args.expression, limit=0): print(expansion, end=args.terminator) raise SystemExit(0) if __name__ == '__main__': main() # pragma: no cover bracex-2.2.1/bracex/__meta__.py000066400000000000000000000153011414064635700163320ustar00rootroot00000000000000"""Meta related things.""" from __future__ import unicode_literals from collections import namedtuple import re RE_VER = re.compile( r'''(?x) (?P\d+)(?:\.(?P\d+))?(?:\.(?P\d+))? (?:(?Pa|b|rc)(?P
\d+))?
    (?:\.post(?P\d+))?
    (?:\.dev(?P\d+))?
    '''
)

REL_MAP = {
    ".dev": "",
    ".dev-alpha": "a",
    ".dev-beta": "b",
    ".dev-candidate": "rc",
    "alpha": "a",
    "beta": "b",
    "candidate": "rc",
    "final": ""
}

DEV_STATUS = {
    ".dev": "2 - Pre-Alpha",
    ".dev-alpha": "2 - Pre-Alpha",
    ".dev-beta": "2 - Pre-Alpha",
    ".dev-candidate": "2 - Pre-Alpha",
    "alpha": "3 - Alpha",
    "beta": "4 - Beta",
    "candidate": "4 - Beta",
    "final": "5 - Production/Stable"
}

PRE_REL_MAP = {"a": 'alpha', "b": 'beta', "rc": 'candidate'}


class Version(namedtuple("Version", ["major", "minor", "micro", "release", "pre", "post", "dev"])):
    """
    Get the version (PEP 440).

    A biased approach to the PEP 440 semantic version.

    Provides a tuple structure which is sorted for comparisons `v1 > v2` etc.
      (major, minor, micro, release type, pre-release build, post-release build, development release build)
    Release types are named in is such a way they are comparable with ease.
    Accessors to check if a development, pre-release, or post-release build. Also provides accessor to get
    development status for setup files.

    How it works (currently):

    - You must specify a release type as either `final`, `alpha`, `beta`, or `candidate`.
    - To define a development release, you can use either `.dev`, `.dev-alpha`, `.dev-beta`, or `.dev-candidate`.
      The dot is used to ensure all development specifiers are sorted before `alpha`.
      You can specify a `dev` number for development builds, but do not have to as implicit development releases
      are allowed.
    - You must specify a `pre` value greater than zero if using a prerelease as this project (not PEP 440) does not
      allow implicit prereleases.
    - You can optionally set `post` to a value greater than zero to make the build a post release. While post releases
      are technically allowed in prereleases, it is strongly discouraged, so we are rejecting them. It should be
      noted that we do not allow `post0` even though PEP 440 does not restrict this. This project specifically
      does not allow implicit post releases.
    - It should be noted that we do not support epochs `1!` or local versions `+some-custom.version-1`.

    Acceptable version releases:

    ```
    Version(1, 0, 0, "final")                    1.0
    Version(1, 2, 0, "final")                    1.2
    Version(1, 2, 3, "final")                    1.2.3
    Version(1, 2, 0, ".dev-alpha", pre=4)        1.2a4
    Version(1, 2, 0, ".dev-beta", pre=4)         1.2b4
    Version(1, 2, 0, ".dev-candidate", pre=4)    1.2rc4
    Version(1, 2, 0, "final", post=1)            1.2.post1
    Version(1, 2, 3, ".dev")                     1.2.3.dev0
    Version(1, 2, 3, ".dev", dev=1)              1.2.3.dev1
    ```

    """

    def __new__(
        cls,
        major: int, minor: int, micro: int, release: str = "final",
        pre: int = 0, post: int = 0, dev: int = 0
    ) -> "Version":
        """Validate version info."""

        # Ensure all parts are positive integers.
        for value in (major, minor, micro, pre, post):
            if not (isinstance(value, int) and value >= 0):
                raise ValueError("All version parts except 'release' should be integers.")

        if release not in REL_MAP:
            raise ValueError("'{}' is not a valid release type.".format(release))

        # Ensure valid pre-release (we do not allow implicit pre-releases).
        if ".dev-candidate" < release < "final":
            if pre == 0:
                raise ValueError("Implicit pre-releases not allowed.")
            elif dev:
                raise ValueError("Version is not a development release.")
            elif post:
                raise ValueError("Post-releases are not allowed with pre-releases.")

        # Ensure valid development or development/pre release
        elif release < "alpha":
            if release > ".dev" and pre == 0:
                raise ValueError("Implicit pre-release not allowed.")
            elif post:
                raise ValueError("Post-releases are not allowed with pre-releases.")

        # Ensure a valid normal release
        else:
            if pre:
                raise ValueError("Version is not a pre-release.")
            elif dev:
                raise ValueError("Version is not a development release.")

        return super(Version, cls).__new__(cls, major, minor, micro, release, pre, post, dev)

    def _is_pre(self) -> bool:
        """Is prerelease."""

        return bool(self.pre > 0)

    def _is_dev(self) -> bool:
        """Is development."""

        return bool(self.release < "alpha")

    def _is_post(self) -> bool:
        """Is post."""

        return bool(self.post > 0)

    def _get_dev_status(self) -> str:  # pragma: no cover
        """Get development status string."""

        return DEV_STATUS[self.release]

    def _get_canonical(self) -> str:
        """Get the canonical output string."""

        # Assemble major, minor, micro version and append `pre`, `post`, or `dev` if needed..
        if self.micro == 0:
            ver = "{}.{}".format(self.major, self.minor)
        else:
            ver = "{}.{}.{}".format(self.major, self.minor, self.micro)
        if self._is_pre():
            ver += '{}{}'.format(REL_MAP[self.release], self.pre)
        if self._is_post():
            ver += ".post{}".format(self.post)
        if self._is_dev():
            ver += ".dev{}".format(self.dev)

        return ver


def parse_version(ver: str) -> Version:
    """Parse version into a comparable Version tuple."""

    m = RE_VER.match(ver)

    if m is None:
        raise ValueError("'{}' is not a valid version".format(ver))

    # Handle major, minor, micro
    major = int(m.group('major'))
    minor = int(m.group('minor')) if m.group('minor') else 0
    micro = int(m.group('micro')) if m.group('micro') else 0

    # Handle pre releases
    if m.group('type'):
        release = PRE_REL_MAP[m.group('type')]
        pre = int(m.group('pre'))
    else:
        release = "final"
        pre = 0

    # Handle development releases
    dev = m.group('dev') if m.group('dev') else 0
    if m.group('dev'):
        dev = int(m.group('dev'))
        release = '.dev-' + release if pre else '.dev'
    else:
        dev = 0

    # Handle post
    post = int(m.group('post')) if m.group('post') else 0

    return Version(major, minor, micro, release, pre, post, dev)


__version_info__ = Version(2, 2, 1, "final")
__version__ = __version_info__._get_canonical()
bracex-2.2.1/bracex/py.typed000066400000000000000000000000001414064635700157230ustar00rootroot00000000000000bracex-2.2.1/docs/000077500000000000000000000000001414064635700137225ustar00rootroot00000000000000bracex-2.2.1/docs/src/000077500000000000000000000000001414064635700145115ustar00rootroot00000000000000bracex-2.2.1/docs/src/dictionary/000077500000000000000000000000001414064635700166565ustar00rootroot00000000000000bracex-2.2.1/docs/src/dictionary/en-custom.txt000066400000000000000000000004431414064635700213320ustar00rootroot00000000000000API
Accessors
Backport
Bracex
Changelog
GitHub
Gitter
MERCHANTABILITY
MkDocs
NONINFRINGEMENT
NUL
Preprocess
PyPI
TODO
Tox
Twemoji
Virtualenv
Willmer
accessor
deprecations
expander
globbing
incrementer
iterables
linter
pre
prerelease
prereleases
pytest
regex
stdout
sublicense
tuple
wordlist
bracex-2.2.1/docs/src/markdown/000077500000000000000000000000001414064635700163335ustar00rootroot00000000000000bracex-2.2.1/docs/src/markdown/_snippets/000077500000000000000000000000001414064635700203375ustar00rootroot00000000000000bracex-2.2.1/docs/src/markdown/_snippets/abbr.txt000066400000000000000000000000001414064635700217740ustar00rootroot00000000000000bracex-2.2.1/docs/src/markdown/_snippets/links.txt000066400000000000000000000000561414064635700222210ustar00rootroot00000000000000[aspell]: https://github.com/GNUAspell/aspell
bracex-2.2.1/docs/src/markdown/_snippets/refs.txt000066400000000000000000000000411414064635700220320ustar00rootroot00000000000000--8<--
links.txt
abbr.txt
--8<--
bracex-2.2.1/docs/src/markdown/about/000077500000000000000000000000001414064635700174455ustar00rootroot00000000000000bracex-2.2.1/docs/src/markdown/about/changelog.md000066400000000000000000000026361414064635700217250ustar00rootroot00000000000000# Changes

## 2.2.1

- **FIX**: Remove excessive generator wrappers.
- **FIX**: Use `AnyStr` for string static types instead of custom alias.

## 2.2

- **NEW**: Support Python 3.10
- **NEW**: Command line interface using `python3 -m bracex`
- **NEW**: Add static types to API.

## 2.1.1

- **FIX**: Expansion limit evaluated much too late and hanging can still occur with large expansions. Calculate
  expansion count and assert limit while parsing strings to reduce chance of hanging.

## 2.1

- **NEW**: Drop support for Python 3.5.
- **FIX**: Fix potential corner case in looping logic.

## 2.0.1

- **FIX**: Officially support Python 3.9.

## 2.0

- **NEW**: An expansion limit of `1000` is enforced by default. This can be controlled, or even removed, via the `limit`
  option.

## 1.4

- **NEW**: Remove `version` and `version_info` and the associated deprecation code.

## 1.3

- **NEW**: Drop Python 3.4 support.

## 1.2

- **NEW**: Officially support Python 3.8.

## 1.1.1

- **FIX**: Vendor `pep562` in order to reduce dependencies.

## 1.1

- **NEW**: Deprecate `version` and `version_info` in favor of the more standard `__version__` and `__version_info__`.
- **FIX**: Proper iteration when using `iexpand`.

## 1.0.2

- **FIX**: Officially support Python 3.7.

## 1.0.1

- **FIX**: Allow zero increments in sequence ranges: `{1..10..0}`. Zero will be treated as one just like Bash does.

## 1.0

- **NEW**: Initial release.
bracex-2.2.1/docs/src/markdown/about/contributing.md000066400000000000000000000050441414064635700225010ustar00rootroot00000000000000# Contributing & Support

## Become a Sponsor :octicons-heart-fill-16:{: .heart-throb}

Open source projects take time and money. Help support the project by becoming a sponsor. You can add your support at
any tier you feel comfortable with. No amount is too little. We also accept one time contributions via PayPal.

[:octicons-mark-github-16: GitHub Sponsors](https://github.com/sponsors/facelessuser){: .md-button .md-button--primary }
[:fontawesome-brands-paypal: PayPal](https://www.paypal.me/facelessuser){ .md-button}

## Bug Reports

1. Please **read the documentation** and **search the issue tracker** to try and find the answer to your question
**before** posting an issue.

2. When creating an issue on the repository, please provide as much information as possible:

    - Version being used.
    - Operating system.
    - Version of Python.
    - Errors in console.
    - Detailed description of the problem.
    - Examples for reproducing the error.  You can post pictures, but if specific text or code is required to reproduce
    the issue, please provide the text in a plain text format for easy copy/paste.

    The more info provided the greater the chance someone will take the time to answer, implement, or fix the issue.

3. Be prepared to answer questions and provide additional information if required.  Issues in which the creator refuses
to respond to follow up questions will be marked as stale and closed.

## Reviewing Code

Take part in reviewing pull requests and/or reviewing direct commits.  Make suggestions to improve the code and discuss
solutions to overcome weakness in the algorithm.

## Answer Questions in Issues

Take time and answer questions and offer suggestions to people who've created issues in the issue tracker. Often people
will have questions that you might have an answer for.  Or maybe you know how to help them accomplish a specific task
they are asking about. Feel free to share your experience to help others.

## Pull Requests

Pull requests are welcome, and a great way to help fix bugs and add new features. If you you are interested in directly
contributing to the code, please check out [Development](./development.md) for more information on the environment and
processes.

## Documentation Improvements

A ton of time has been spent not only creating and supporting this tool and related extensions, but also spent making
this documentation.  If you feel it is still lacking, show your appreciation for the tool by helping to improve the
documentation. Check out [Development](./development.md) for more info on documentation.
bracex-2.2.1/docs/src/markdown/about/development.md000066400000000000000000000123531414064635700223150ustar00rootroot00000000000000# Development

## Project Layout

There are a number of files for build, test, and continuous integration in the root of the project, but in general, the
project is broken up like so.

```
├── docs
│   └── src
│       ├── dictionary
│       └── markdown
├── bracex
├── requirements
└── tests
```

Directory             | Description
--------------------- | -----------
`docs/src/dictionary` | Contains the spell check wordlist(s) for the project.
`docs/src/markdown`   | Contains the content for the documentation.
`soupsieve`           | Contains the source code for the project.
`requirements`        | Contains files with lists of dependencies that are required for the project, and required for continuous integration.
`tests`               | Contains unit test files.

## Coding Standards

When writing code, the code should roughly conform to PEP8 and PEP257 suggestions.  The project utilizes the Flake8
linter (with some additional plugins) to ensure code conforms (give or take some of the rules).  When in doubt, follow
the formatting hints of existing code when adding files or modifying existing files.  Listed below are the modules used:

- @gitlab:pycqa/flake8
- @gitlab:pycqa/flake8-docstrings
- @gitlab:pycqa/pep8-naming
- @ebeweber/flake8-mutable
- @gforcada/flake8-builtins

Usually this can be automated with Tox (assuming it is installed): `tox -e lint`.

## Building and Editing Documents

Documents are in Markdown (with with some additional syntax provided by extensions) and are converted to HTML via Python
Markdown. If you would like to build and preview the documentation, you must have these packages installed:

- @Python-Markdown/markdown: the Markdown parser.
- @mkdocs/mkdocs: the document site generator.
- @squidfunk/mkdocs-material: a material theme for MkDocs.
- @facelessuser/pymdown-extensions: this Python Markdown extension bundle.

In order to build and preview the documents, just run the command below from the root of the project and you should be
able to view the documents at `localhost:8000` in your browser. After that, you should be able to update the documents
and have your browser preview update live.

```
mkdocs serve
```

## Spell Checking Documents

Spell checking is performed via @facelessuser/pyspelling.

During validation we build the docs and spell check various files in the project. [Aspell][aspell] must be installed and
in the path.  Currently this project uses one of the more recent versions of Aspell.  It is not expected that everyone
will install and run Aspell locally, but it will be run in CI tests for pull requests.

In order to perform the spell check locally, it is expected you are setup to build the documents, and that you have
Aspell installed in your system path (if needed you can use the `--binary` option to point to the location of your
Aspell binary). It is also expected that you have the `en` dictionary installed as well. To initiate the spell check,
run the following command from the root of the project.

You will need to make sure the documents are built first:

```
mkdocs build --clean
```

And then run the spell checker.

```
pyspelling
```

It should print out the files with the misspelled words if any are found.  If you find it prints words that are not
misspelled, you can add them in `docs/src/dictionary/en-custom.text`.

## Validation Tests

In order to preserve good code health, a test suite has been put together with pytest (@pytest-dev/pytest).  To run
these tests, you can use the following command:

```
py.test
```

### Running Validation With Tox

Tox (@tox-dev/tox) is a great way to run the validation tests, spelling checks, and linting in virtual environments so
as not to mess with your current working environment. Tox will use the specified Python version for the given
environment and create a virtual environment and install all the needed requirements (minus Aspell).  You could also
setup your own virtual environments with the Virtualenv module without Tox, and manually do the same.

First, you need to have Tox installed:

```
pip install tox
```

By running Tox, it will walk through all the environments and create them (assuming you have all the python versions on
your machine) and run the related tests.  See `tox.ini` to learn more.

```
tox
```

If you don't have all the Python versions needed to test all the environments, those entries will fail. To run the tests
for specific versions of Python, you specify the environment with `-e PXY` where `X` is the major version and `Y` is the
minor version.

```
tox -e py37
```

To target linting:

```
tox -e lint
```

To select spell checking and document building:

```
tox -e documents
```

## Code Coverage

When running the validation tests through Tox, it is setup to track code coverage via the Coverage
(@bitbucket:ned/coveragepy) module.  Coverage is run on each `pyxx` environment.  If you've made changes to the code,
you can clear the old coverage data:

```
coverage erase
```

Then run each unit test environment to generate coverage data. All the data from each run is merged together.  HTML is
output for each file in `.tox/pyXX/tmp`.  You can use these to see areas that are not covered/exercised yet with
testing.

You can checkout `tox.ini` to see how this is accomplished.

--8<-- "links.txt"
bracex-2.2.1/docs/src/markdown/about/license.md000066400000000000000000000021231414064635700214070ustar00rootroot00000000000000# License

MIT License

Copyright (c) 2018 - 2021 Isaac Muse 

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.
bracex-2.2.1/docs/src/markdown/index.md000066400000000000000000000070621414064635700177710ustar00rootroot00000000000000# Bracex

## Overview

Bracex is a brace expanding library (à la Bash) for Python. Brace expanding is used to generate arbitrary strings.


```shell-session
$ echo {{a,b},c}d
ad bd cd
```

Bracex adds this ability to Python:

```pycon3
>>> bracex.expand(r'file-{1,2,3}.txt')
['file-1.txt', 'file-2.txt', 'file-3.txt']
```

and as a command:

```shell-session
$ python3 -m bracex -0 "base/{a,b}/{1..2}" | xargs -0 mkdir -p
$ tree base/
base/
├── a
│   ├── 1
│   └── 2
└── b
    ├── 1
    └── 2
```

- **Why Bracex over other solutions?**

    Bracex actually follows pretty closely to how Bash processes braces. It is not a 1:1 implementation of how Bash
    handles braces, but generally, it follows very closely. Almost all of the test cases are run through Bash first,
    then our implementation is compared against the results Bash gives. There are a few cases where we have purposely
    deviated. For instance, we are not handling Bash's command line inputs, so we are not giving special meaning to back
    ticks and quotes at this time.

    On the command line Bracex can handle more expansions than Bash itself.

More Examples:

```pycon3
>>> bracex.expand(r'-v{,,}')
['-v', '-v', '-v']
```

Nested braces:

```pycon3
>>> bracex.expand(r'file-{{a,b},c}d.txt')
['file-ad.txt', 'file-bd.txt', 'file-cd.txt']
```

Numerical sequences:

```pycon3
>>> bracex.expand(r'file{0..3}.txt')
['file0.txt', 'file1.txt', 'file2.txt', 'file3.txt']
```

```pycon3
>>> bracex.expand(r'file{0..6..2}.txt')
['file0.txt', 'file2.txt', 'file4.txt', 'file6.txt']
```

```pycon3
>>> bracex.expand(r'file{00..10..5}.jpg')
['file00.jpg', 'file05.jpg', 'file10.jpg']
```

Alphabetic sequences:

```pycon3
>>> bracex.expand(r'file{A..D}.txt')
['fileA.txt', 'fileB.txt', 'fileC.txt', 'fileD.txt']
```

```pycon3
>>> bracex.expand(r'file{A..G..2}.txt')
['fileA.txt', 'fileC.txt', 'fileE.txt', 'fileG.txt']
```

Allows escaping:

```pycon3
>>> bracex.expand(r'file\{00..10..5}.jpg')
['file{00..10..5}.jpg']
```

```pycon3
>>> bracex.expand(r'file\{00..10..5}.jpg', keep_escapes=True)
['file\\{00..10..5}.jpg']
```

Bracex will **not** expand braces in the form of `${...}`:

```pycon3
>>> bracex.expand(r'file${a,b,c}.jpg')
['file${a,b,c}.jpg']
```

## Install

```shell-session
$ pip install bracex
```

## API

### `expand()`

```py3
def expand(string, keep_escapes=False, limit=1000):
```

`expand` accepts a string and returns a list of expanded strings. It will always return at least a single empty string
`[""]`. By default, escapes will be resolved and the backslashes reduced accordingly, but `keep_escapes` will process
the escapes without stripping them out.

By default, brace expansion growth is limited to `1000`. This limit can be configured via the `limit` option. If you
would like to remove the limit option, you simply set `limit` to `0`.

### `iexpand()`

```py3
def iexpand(string, keep_escapes=False, limit=1000):
```

`iexpand` is just like `expand` except it returns a generator.

## Command line interface

```shell-session
$ python3 -m bracex --help
usage: python -mbracex [-h] [--terminator STR | -0] [--version] expression

Expands a bash-style brace expression, and outputs each expansion.

positional arguments:
  expression            Brace expression to expand

optional arguments:
  -h, --help            show this help message and exit
  --terminator STR, -t STR
                        Terminate each expansion with string STR (default: \n)
  -0                    Terminate each expansion with a NUL character
  --version             show program's version number and exit
```
bracex-2.2.1/docs/theme/000077500000000000000000000000001414064635700150245ustar00rootroot00000000000000bracex-2.2.1/docs/theme/announce.html000066400000000000000000000011701414064635700175170ustar00rootroot00000000000000Sponsorship
is now available!

  

bracex-2.2.1/mkdocs.yml000066400000000000000000000051451414064635700150020ustar00rootroot00000000000000site_name: Bracex
site_url: https://facelessuser.github.io/bracex
repo_url: https://github.com/facelessuser/bracex
edit_uri: tree/master/docs/src/markdown
site_description: Python spell checker.
copyright: |
  Copyright © 2018 - 2021 Isaac Muse

docs_dir: docs/src/markdown
theme:
  name: material
  custom_dir: docs/theme
  icon:
    logo: material/book-open-page-variant
  palette:
    primary: deep purple
    accent: deep purple
  font:
    text: Roboto
    code: Roboto Mono
  features:
    - navigation.tabs
    - navigation.top
    - naviagtion.instant
  pymdownx:
    sponsor: "https://github.com/sponsors/facelessuser"

nav:
  - Home:
    - Usage: index.md
  - About:
    - Contributing & Support: about/contributing.md
    - Development: about/development.md
    - Changelog: about/changelog.md
    - License: about/license.md

markdown_extensions:
  - markdown.extensions.toc:
      slugify: !!python/object/apply:pymdownx.slugs.slugify {kwds: {case: lower}}
      permalink: ""
  - markdown.extensions.admonition:
  - markdown.extensions.smarty:
      smart_quotes: false
  - pymdownx.betterem:
  - markdown.extensions.attr_list:
  - markdown.extensions.def_list:
  - markdown.extensions.tables:
  - markdown.extensions.abbr:
  - markdown.extensions.footnotes:
  - markdown.extensions.md_in_html:
  - pymdownx.superfences:
      preserve_tabs: true
  - pymdownx.highlight:
      extend_pygments_lang:
        - name: pycon3
          lang: pycon
          options:
            python3: true
  - pymdownx.inlinehilite:
  - pymdownx.magiclink:
      repo_url_shortener: true
      repo_url_shorthand: true
      social_url_shorthand: true
      user: facelessuser
      repo: bracex
  - pymdownx.tilde:
  - pymdownx.caret:
  - pymdownx.smartsymbols:
  - pymdownx.emoji:
      emoji_index: !!python/name:materialx.emoji.twemoji
      emoji_generator: !!python/name:materialx.emoji.to_svg
  - pymdownx.escapeall:
      hardbreak: True
      nbsp: True
  - pymdownx.tasklist:
      custom_checkbox: true
  - pymdownx.progressbar:
  - pymdownx.striphtml:
  - pymdownx.snippets:
      base_path: docs/src/markdown/_snippets
  - pymdownx.keys:
      separator: "\uff0b"
  - pymdownx.details:
  - pymdownx.tabbed:
      alternate_style: true
  - pymdownx.saneheaders:

extra:
  social:
    - icon: fontawesome/brands/github
      link: https://github.com/facelessuser
    - icon: fontawesome/brands/discord
      link: https://discord.gg/TWs8Tgr

plugins:
  - search
  - git-revision-date-localized
  - mkdocs_pymdownx_material_extras
  - minify:
      minify_html: true
bracex-2.2.1/pyproject.toml000066400000000000000000000002721414064635700157070ustar00rootroot00000000000000[build-system]
requires = [
    "setuptools>=42",
    "wheel"
]

build-backend = "setuptools.build_meta"

[tool.mypy]
files = [
    "bracex/*.py"
]
strict = true
show_error_codes = true
bracex-2.2.1/requirements/000077500000000000000000000000001414064635700155155ustar00rootroot00000000000000bracex-2.2.1/requirements/docs.txt000066400000000000000000000001611414064635700172040ustar00rootroot00000000000000mkdocs_pymdownx_material_extras==1.5.4
mkdocs-git-revision-date-localized-plugin
mkdocs-minify-plugin
pyspelling
bracex-2.2.1/requirements/lint.txt000066400000000000000000000001041414064635700172170ustar00rootroot00000000000000flake8
flake8_docstrings
pep8-naming
flake8-mutable
flake8-builtins
bracex-2.2.1/requirements/project.txt000066400000000000000000000000001414064635700177120ustar00rootroot00000000000000bracex-2.2.1/requirements/test.txt000066400000000000000000000000401414064635700172270ustar00rootroot00000000000000pytest
pytest-cov
coverage
mypy
bracex-2.2.1/setup.cfg000066400000000000000000000000451414064635700146120ustar00rootroot00000000000000[metadata]
license_file = LICENSE.md
bracex-2.2.1/setup.py000066400000000000000000000042011414064635700145010ustar00rootroot00000000000000#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Setup package."""
from setuptools import setup, find_packages
import os
import importlib


def get_version():
    """Get version and version_info without importing the entire module."""

    path = os.path.join(os.path.dirname(__file__), 'bracex', '__meta__.py')
    spec = importlib.util.spec_from_file_location("__meta__", path)
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    vi = module.__version_info__
    return vi._get_canonical(), vi._get_dev_status()


def get_requirements(req):
    """Load list of dependencies."""

    install_requires = []
    with open(req) as f:
        for line in f:
            if not line.startswith("#"):
                install_requires.append(line.strip())
    return install_requires


def get_description():
    """Get long description."""

    with open("README.md", 'r') as f:
        desc = f.read()
    return desc


VER, DEVSTATUS = get_version()

setup(
    name='bracex',
    python_requires=">=3.6",
    version=VER,
    keywords='bash brace expand',
    description='Bash style brace expander.',
    long_description=get_description(),
    long_description_content_type='text/markdown',
    author='Isaac Muse',
    author_email='Isaac.Muse@gmail.com',
    url='https://github.com/facelessuser/bracex',
    packages=find_packages(exclude=['tests', 'tools']),
    package_data={"bracex": ["py.typed"]},
    install_requires=get_requirements("requirements/project.txt"),
    license='MIT License',
    classifiers=[
        'Development Status :: %s' % DEVSTATUS,
        'Environment :: Console',
        'Intended Audience :: Developers',
        'License :: OSI Approved :: MIT License',
        'Operating System :: OS Independent',
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3.6',
        'Programming Language :: Python :: 3.7',
        'Programming Language :: Python :: 3.8',
        'Programming Language :: Python :: 3.9',
        'Programming Language :: Python :: 3.10',
        'Topic :: Software Development :: Libraries :: Python Modules',
        'Typing :: Typed'
    ]
)
bracex-2.2.1/tests/000077500000000000000000000000001414064635700141345ustar00rootroot00000000000000bracex-2.2.1/tests/__init__.py000066400000000000000000000000301414064635700162360ustar00rootroot00000000000000"""Unit test module."""
bracex-2.2.1/tests/brace-cases.txt000066400000000000000000000057011414064635700170500ustar00rootroot00000000000000# Cases lifted from: https://github.com/juliangruber/brace-expansion
# skip quotes for now
# "{x,x}"
# {"x,x"}
# {x","x}
# '{a,b}{{a,b},a,b}'
A{b,{d,e},{f,g}}Z
PRE-{a,b}{{a,b},a,b}-POST
\\{a,b}{{a,b},a,b}
{{a,b}
{a,b}}
{,}
a{,}
{,}b
a{,}b
a{b}c
a{1..5}b
a{01..5}b
a{-01..5}b
a{-01..5..3}b
a{001..9}b
a{b,c{d,e},{f,g}h}x{y,z
a{b,c{d,e},{f,g}h}x{y,z\\}
a{b,c{d,e},{f,g}h}x{y,z}
a{b{c{d,e}f{x,y{{g}h
a{b{c{d,e}f{x,y{}g}h
a{b{c{d,e}f{x,y}}g}h
a{b{c{d,e}f}g}h
a{{x,y},z}b
f{x,y{g,z}}h
f{x,y{{g,z}}h
f{x,y{{g,z}}h}
f{x,y{{g}h
f{x,y{{g}}h
f{x,y{}g}h
z{a,b{,c}d
z{a,b},c}d
{-01..5}
{-05..100..5}
{-05..100}
{0..5..2}
{0001..05..2}
{0001..-5..2}
{0001..-5..-2}
{0001..5..-2}
{01..5}
{1..05}
{1..05..3}
{05..100}
{0a..0z}
{a,b\\}c,d}
{a,b{c,d}
{a,b}c,d}
{a..F}
{A..f}
{a..Z}
{A..z}
{z..A}
{Z..a}
{a..F..2}
{A..f..02}
{a..Z..5}
d{a..Z..5}b
{A..z..10}
{z..A..-2}
{Z..a..20}
{a{,b}
{a},b}
{x,y{,}g}
{x,y{}g}
{{a,b}
{{a,b},c}
{{a,b}c}
{{a,b},}
X{{a,b},}X
{{a,b},}c
{{a,b}.}
{{a,b}}
X{a..#}X
# this next one is an empty string

{-10..00}
# Need to escape slashes in here for reasons i guess.
{a,\\\\{a,b}c}
{a,\\{a,b}c}
a,\\{b,c}
{-10.\\.00}
#### bash tests/braces.tests
# Note that some tests are edited out because some features of
# bash are intentionally not supported in this brace expander.
ff{c,b,a}
f{d,e,f}g
{l,n,m}xyz
{abc\\,def}
{abc}
{x\\,y,\\{abc\\},trie}
# not impementing back-ticks obviously
# XXXX\\{`echo a b c | tr ' ' ','`\\}
{}
# We only ever have to worry about parsing a single argument,
# not a command line, so spaces have a different meaning than bash.
# { }
}
{
abcd{efgh
# spaces
# foo {1,2} bar
# not impementing back-ticks obviously
# `zecho foo {1,2} bar`
# $(zecho foo {1,2} bar)
# ${var} is not a variable here, like it is in bash. omit.
# foo{bar,${var}.}
# foo{bar,${var}}
# isaacs: skip quotes for now
# "${var}"{x,y}
# $var{x,y}
# ${var}{x,y}
# new sequence brace operators
{1..10}
# this doesn't work yet
{0..10,braces}
# but this does
{{0..10},braces}
x{{0..10},braces}y
{3..3}
x{3..3}y
{10..1}
{10..1}y
x{10..1}y
{a..f}
{f..a}
{a..A}
{A..a}
{f..f}
# mixes are incorrectly-formed brace expansions
{1..f}
{f..1}
# spaces
# 0{1..9} {10..20}
# do negative numbers work?
{-1..-10}
{-20..0}
# weirdly-formed brace expansions -- fixed in post-bash-3.1
a-{b{d,e}}-c
a-{bdef-{g,i}-c
# isaacs: skip quotes for now
# {"klklkl"}{1,2,3}
# isaacs: this is a valid test, though
{klklkl}{1,2,3}
# {"x,x"}
{1..10..2}
{-1..-10..2}
{-1..-10..-2}
{10..1..-2}
{10..1..2}
{1..20..2}
{1..20..20}
{100..0..5}
{100..0..-5}
{a..z}
{a..z..2}
{z..a..-2}
# make sure brace expansion handles ints > 2**31 - 1 using intmax_t
{2147483645..2147483649}
# unwanted zero-padding -- fixed post-bash-4.0
{10..0..2}
{10..0..-2}
{-50..-0..5}
# bad
{1..10.f}
{1..ff}
{1..10..ff}
{1.20..2}
{1..20..f2}
{1..20..2f}
{1..2f..2}
{1..ff..2}
{1..ff}
{1..f}
{1..0f}
{1..10f}
{1..10.f}
{},b}.h
y{},a}x
{}{},a}b
{{},a}}b
{}{{},a}}b
{}a,b}c
# Zero is an acceptable incrementer, just assume 1
{1..10..0}
{1..10..-0}
{a..d..0}
{a..d..-0}
bracex-2.2.1/tests/brace-results.txt000066400000000000000000000211031414064635700174450ustar00rootroot00000000000000A{b,{d,e},{f,g}}Z
[AbZ]
[AdZ]
[AeZ]
[AfZ]
[AgZ]><><><><><><><\{a,b}{{a,b},a,b}
[{a,b}a]
[{a,b}b]
[{a,b}a]
[{a,b}b]><><><><{{a,b}
[{a]
[{b]><><><><{a,b}}
[a}]
[b}]><><><><{,}
><><><><><><><{,}b
[b]
[b]><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><{-01..5}
[-01]
[000]
[001]
[002]
[003]
[004]
[005]><><><><{-05..100..5}
[-05]
[000]
[005]
[010]
[015]
[020]
[025]
[030]
[035]
[040]
[045]
[050]
[055]
[060]
[065]
[070]
[075]
[080]
[085]
[090]
[095]
[100]><><><><{-05..100}
[-05]
[-04]
[-03]
[-02]
[-01]
[000]
[001]
[002]
[003]
[004]
[005]
[006]
[007]
[008]
[009]
[010]
[011]
[012]
[013]
[014]
[015]
[016]
[017]
[018]
[019]
[020]
[021]
[022]
[023]
[024]
[025]
[026]
[027]
[028]
[029]
[030]
[031]
[032]
[033]
[034]
[035]
[036]
[037]
[038]
[039]
[040]
[041]
[042]
[043]
[044]
[045]
[046]
[047]
[048]
[049]
[050]
[051]
[052]
[053]
[054]
[055]
[056]
[057]
[058]
[059]
[060]
[061]
[062]
[063]
[064]
[065]
[066]
[067]
[068]
[069]
[070]
[071]
[072]
[073]
[074]
[075]
[076]
[077]
[078]
[079]
[080]
[081]
[082]
[083]
[084]
[085]
[086]
[087]
[088]
[089]
[090]
[091]
[092]
[093]
[094]
[095]
[096]
[097]
[098]
[099]
[100]><><><><{0..5..2}
[0]
[2]
[4]><><><><{0001..05..2}
[0001]
[0003]
[0005]><><><><{0001..-5..2}
[0001]
[-001]
[-003]
[-005]><><><><{0001..-5..-2}
[0001]
[-001]
[-003]
[-005]><><><><{0001..5..-2}
[0001]
[0003]
[0005]><><><><{01..5}
[01]
[02]
[03]
[04]
[05]><><><><{1..05}
[01]
[02]
[03]
[04]
[05]><><><><{1..05..3}
[01]
[04]><><><><{05..100}
[005]
[006]
[007]
[008]
[009]
[010]
[011]
[012]
[013]
[014]
[015]
[016]
[017]
[018]
[019]
[020]
[021]
[022]
[023]
[024]
[025]
[026]
[027]
[028]
[029]
[030]
[031]
[032]
[033]
[034]
[035]
[036]
[037]
[038]
[039]
[040]
[041]
[042]
[043]
[044]
[045]
[046]
[047]
[048]
[049]
[050]
[051]
[052]
[053]
[054]
[055]
[056]
[057]
[058]
[059]
[060]
[061]
[062]
[063]
[064]
[065]
[066]
[067]
[068]
[069]
[070]
[071]
[072]
[073]
[074]
[075]
[076]
[077]
[078]
[079]
[080]
[081]
[082]
[083]
[084]
[085]
[086]
[087]
[088]
[089]
[090]
[091]
[092]
[093]
[094]
[095]
[096]
[097]
[098]
[099]
[100]><><><><{0a..0z}
[{0a..0z}]><><><><{a,b\}c,d}
[a]
[b}c]
[d]><><><><{a,b{c,d}
[{a,bc]
[{a,bd]><><><><{a,b}c,d}
[ac,d}]
[bc,d}]><><><><{a..F}
[a]
[`]
[_]
[^]
[]]
[]
[[]
[Z]
[Y]
[X]
[W]
[V]
[U]
[T]
[S]
[R]
[Q]
[P]
[O]
[N]
[M]
[L]
[K]
[J]
[I]
[H]
[G]
[F]><><><><{A..f}
[A]
[B]
[C]
[D]
[E]
[F]
[G]
[H]
[I]
[J]
[K]
[L]
[M]
[N]
[O]
[P]
[Q]
[R]
[S]
[T]
[U]
[V]
[W]
[X]
[Y]
[Z]
[[]
[]
[]]
[^]
[_]
[`]
[a]
[b]
[c]
[d]
[e]
[f]><><><><{a..Z}
[a]
[`]
[_]
[^]
[]]
[]
[[]
[Z]><><><><{A..z}
[A]
[B]
[C]
[D]
[E]
[F]
[G]
[H]
[I]
[J]
[K]
[L]
[M]
[N]
[O]
[P]
[Q]
[R]
[S]
[T]
[U]
[V]
[W]
[X]
[Y]
[Z]
[[]
[]
[]]
[^]
[_]
[`]
[a]
[b]
[c]
[d]
[e]
[f]
[g]
[h]
[i]
[j]
[k]
[l]
[m]
[n]
[o]
[p]
[q]
[r]
[s]
[t]
[u]
[v]
[w]
[x]
[y]
[z]><><><><{z..A}
[z]
[y]
[x]
[w]
[v]
[u]
[t]
[s]
[r]
[q]
[p]
[o]
[n]
[m]
[l]
[k]
[j]
[i]
[h]
[g]
[f]
[e]
[d]
[c]
[b]
[a]
[`]
[_]
[^]
[]]
[]
[[]
[Z]
[Y]
[X]
[W]
[V]
[U]
[T]
[S]
[R]
[Q]
[P]
[O]
[N]
[M]
[L]
[K]
[J]
[I]
[H]
[G]
[F]
[E]
[D]
[C]
[B]
[A]><><><><{Z..a}
[Z]
[[]
[]
[]]
[^]
[_]
[`]
[a]><><><><{a..F..2}
[a]
[_]
[]]
[[]
[Y]
[W]
[U]
[S]
[Q]
[O]
[M]
[K]
[I]
[G]><><><><{A..f..02}
[A]
[C]
[E]
[G]
[I]
[K]
[M]
[O]
[Q]
[S]
[U]
[W]
[Y]
[[]
[]]
[_]
[a]
[c]
[e]><><><><{a..Z..5}
[a]
[]><><><><><><><{A..z..10}
[A]
[K]
[U]
[_]
[i]
[s]><><><><{z..A..-2}
[z]
[x]
[v]
[t]
[r]
[p]
[n]
[l]
[j]
[h]
[f]
[d]
[b]
[`]
[^]
[]
[Z]
[X]
[V]
[T]
[R]
[P]
[N]
[L]
[J]
[H]
[F]
[D]
[B]><><><><{Z..a..20}
[Z]><><><><{a{,b}
[{a]
[{ab]><><><><{a},b}
[a}]
[b]><><><><{x,y{,}g}
[x]
[yg]
[yg]><><><><{x,y{}g}
[x]
[y{}g]><><><><{{a,b}
[{a]
[{b]><><><><{{a,b},c}
[a]
[b]
[c]><><><><{{a,b}c}
[{ac}]
[{bc}]><><><><{{a,b},}
[a]
[b]><><><><><><><{{a,b},}c
[ac]
[bc]
[c]><><><><{{a,b}.}
[{a.}]
[{b.}]><><><><{{a,b}}
[{a}]
[{b}]><><><><><><><
><><><><{-10..00}
[-10]
[-09]
[-08]
[-07]
[-06]
[-05]
[-04]
[-03]
[-02]
[-01]
[000]><><><><{a,\\{a,b}c}
[a]
[\ac]
[\bc]><><><><{a,\{a,b}c}
[ac}]
[{ac}]
[bc}]><><><><><><><{-10.\.00}
[{-10..00}]><><><><><><><><><><{l,n,m}xyz
[lxyz]
[nxyz]
[mxyz]><><><><{abc\,def}
[{abc,def}]><><><><{abc}
[{abc}]><><><><{x\,y,\{abc\},trie}
[x,y]
[{abc}]
[trie]><><><><{}
[{}]><><><><}
[}]><><><><{
[{]><><><><><><><{1..10}
[1]
[2]
[3]
[4]
[5]
[6]
[7]
[8]
[9]
[10]><><><><{0..10,braces}
[0..10]
[braces]><><><><{{0..10},braces}
[0]
[1]
[2]
[3]
[4]
[5]
[6]
[7]
[8]
[9]
[10]
[braces]><><><><><><><{3..3}
[3]><><><><><><><{10..1}
[10]
[9]
[8]
[7]
[6]
[5]
[4]
[3]
[2]
[1]><><><><{10..1}y
[10y]
[9y]
[8y]
[7y]
[6y]
[5y]
[4y]
[3y]
[2y]
[1y]><><><><><><><{a..f}
[a]
[b]
[c]
[d]
[e]
[f]><><><><{f..a}
[f]
[e]
[d]
[c]
[b]
[a]><><><><{a..A}
[a]
[`]
[_]
[^]
[]]
[]
[[]
[Z]
[Y]
[X]
[W]
[V]
[U]
[T]
[S]
[R]
[Q]
[P]
[O]
[N]
[M]
[L]
[K]
[J]
[I]
[H]
[G]
[F]
[E]
[D]
[C]
[B]
[A]><><><><{A..a}
[A]
[B]
[C]
[D]
[E]
[F]
[G]
[H]
[I]
[J]
[K]
[L]
[M]
[N]
[O]
[P]
[Q]
[R]
[S]
[T]
[U]
[V]
[W]
[X]
[Y]
[Z]
[[]
[]
[]]
[^]
[_]
[`]
[a]><><><><{f..f}
[f]><><><><{1..f}
[{1..f}]><><><><{f..1}
[{f..1}]><><><><{-1..-10}
[-1]
[-2]
[-3]
[-4]
[-5]
[-6]
[-7]
[-8]
[-9]
[-10]><><><><{-20..0}
[-20]
[-19]
[-18]
[-17]
[-16]
[-15]
[-14]
[-13]
[-12]
[-11]
[-10]
[-9]
[-8]
[-7]
[-6]
[-5]
[-4]
[-3]
[-2]
[-1]
[0]><><><><><><><><><><{klklkl}{1,2,3}
[{klklkl}1]
[{klklkl}2]
[{klklkl}3]><><><><{1..10..2}
[1]
[3]
[5]
[7]
[9]><><><><{-1..-10..2}
[-1]
[-3]
[-5]
[-7]
[-9]><><><><{-1..-10..-2}
[-1]
[-3]
[-5]
[-7]
[-9]><><><><{10..1..-2}
[10]
[8]
[6]
[4]
[2]><><><><{10..1..2}
[10]
[8]
[6]
[4]
[2]><><><><{1..20..2}
[1]
[3]
[5]
[7]
[9]
[11]
[13]
[15]
[17]
[19]><><><><{1..20..20}
[1]><><><><{100..0..5}
[100]
[95]
[90]
[85]
[80]
[75]
[70]
[65]
[60]
[55]
[50]
[45]
[40]
[35]
[30]
[25]
[20]
[15]
[10]
[5]
[0]><><><><{100..0..-5}
[100]
[95]
[90]
[85]
[80]
[75]
[70]
[65]
[60]
[55]
[50]
[45]
[40]
[35]
[30]
[25]
[20]
[15]
[10]
[5]
[0]><><><><{a..z}
[a]
[b]
[c]
[d]
[e]
[f]
[g]
[h]
[i]
[j]
[k]
[l]
[m]
[n]
[o]
[p]
[q]
[r]
[s]
[t]
[u]
[v]
[w]
[x]
[y]
[z]><><><><{a..z..2}
[a]
[c]
[e]
[g]
[i]
[k]
[m]
[o]
[q]
[s]
[u]
[w]
[y]><><><><{z..a..-2}
[z]
[x]
[v]
[t]
[r]
[p]
[n]
[l]
[j]
[h]
[f]
[d]
[b]><><><><{2147483645..2147483649}
[2147483645]
[2147483646]
[2147483647]
[2147483648]
[2147483649]><><><><{10..0..2}
[10]
[8]
[6]
[4]
[2]
[0]><><><><{10..0..-2}
[10]
[8]
[6]
[4]
[2]
[0]><><><><{-50..-0..5}
[-50]
[-45]
[-40]
[-35]
[-30]
[-25]
[-20]
[-15]
[-10]
[-5]
[0]><><><><{1..10.f}
[{1..10.f}]><><><><{1..ff}
[{1..ff}]><><><><{1..10..ff}
[{1..10..ff}]><><><><{1.20..2}
[{1.20..2}]><><><><{1..20..f2}
[{1..20..f2}]><><><><{1..20..2f}
[{1..20..2f}]><><><><{1..2f..2}
[{1..2f..2}]><><><><{1..ff..2}
[{1..ff..2}]><><><><{1..ff}
[{1..ff}]><><><><{1..f}
[{1..f}]><><><><{1..0f}
[{1..0f}]><><><><{1..10f}
[{1..10f}]><><><><{1..10.f}
[{1..10.f}]><><><><{},b}.h
[{},b}.h]><><><><><><><{}{},a}b
[{}}b]
[{}ab]><><><><{{},a}}b
[{}}b]
[a}b]><><><><{}{{},a}}b
[{}{}}b]
[{}a}b]><><><><{}a,b}c
[{}a,b}c]><><><><{1..10..0}
[1]
[2]
[3]
[4]
[5]
[6]
[7]
[8]
[9]
[10]><><><><{1..10..-0}
[1]
[2]
[3]
[4]
[5]
[6]
[7]
[8]
[9]
[10]><><><><{a..d..0}
[a]
[b]
[c]
[d]><><><><{a..d..-0}
[a]
[b]
[c]
[d]><><><><bracex-2.2.1/tests/generate.sh000077500000000000000000000010211414064635700162570ustar00rootroot00000000000000#!/usr/bin/env bash

set -e

# Bash 4.3 - 5.0 because of arbitrary need to pick a single standard.
if ! [[ "${BASH_VERSINFO[0]}.${BASH_VERSINFO[1]}" =~ (4\.[^1-2]|5\.[0-1])(\.\d+)? ]]; then
  echo "this script requires bash 4.3, 4.4, or 5.0" >&2
  exit 1
fi

CDPATH= cd "$(dirname "$0")"

cat brace-cases.txt | \
  while read case; do
    if [ "${case:0:1}" = "#" ]; then
      continue;
    fi;
    b="$($BASH -c 'for c in '"$case"'; do echo "[$c]"; done')"
    echo "$case"
    echo -n "$b><><><><";
  done > brace-results.txt
bracex-2.2.1/tests/test_brace.py000066400000000000000000000353121414064635700166250ustar00rootroot00000000000000"""Test braces.

Looking for brace test cases, I stumbled on https://github.com/juliangruber/brace-expansion.
The project contained great tests that mirror Bash 4.3's behavior.  And while this library
was written independently, we used their test sweet to bring this up to Bash 4.3 standard.
"""
import unittest
import pytest
import bracex
import ast
import re

RE_REMOVE = re.compile(r'^\[|\]$')
BRE_REMOVE = re.compile(br'^\[|\]$')


def get_bash_cases():
    """Setup module."""

    re_split = re.compile(r'><><><><')

    with open('tests/brace-results.txt', 'r') as r:
        # Split by test cases
        results = re_split.split(r.read())
        results.pop()
        # Split by line within test case
        wanted = [x.split('\n') for x in results]

    with open('tests/brace-cases.txt', 'r') as r:
        # Split by line ignoring commented lines.
        # We may have blank lines that get included,
        # But the test will compare those too, so it
        # isn't a problem.
        cases = [x.strip() for x in r.read().split('\n') if not x.startswith('#')]
        cases.pop()

    bash_cases = []
    while cases:
        bash_cases.append((cases.pop(0), wanted.pop(0)))
    return bash_cases


class TestBraces:
    """Test globbing."""

    dollar_cases = [
        ['${1..3}', ['${1..3}']],
        ['${a,b}${c,d}', ['${a,b}${c,d}']],
        ['x${a,b}x${c,d}x', ['x${a,b}x${c,d}x']]
    ]

    empty_cases = [
        ['-v{,,,,}', ['-v', '-v', '-v', '-v', '-v']],
        ['{,,}', ['']],
        ['', ['']]
    ]

    negative_incr_cases = [
        ['{3..1}', ['3', '2', '1']],
        ['{10..8}', ['10', '9', '8']],
        ['{10..08}', ['10', '09', '08']],
        ['{c..a}', ['c', 'b', 'a']],
        ['{4..0..2}', ['4', '2', '0']],
        ['{4..0..-2}', ['4', '2', '0']],
        ['{e..a..2}', ['e', 'c', 'a']]
    ]

    nested_cases = [
        ['{a,b{1..3},c}', ['a', 'b1', 'b2', 'b3', 'c']],
        ['{{A..Z},{a..z}}', list('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')],
        ['ppp{,config,oe{,conf}}', ['ppp', 'pppconfig', 'pppoe', 'pppoeconf']]
    ]

    order_cases = [
        ['a{d,c,b}e', ['ade', 'ace', 'abe']]
    ]

    pad_cases = [
        ['{09..11}', ['09', '10', '11']],
        ['{9..11}', ['9', '10', '11']]
    ]

    sequence_cases = [
        ['a{1..2}b{2..3}c', ['a1b2c', 'a1b3c', 'a2b2c', 'a2b3c']],
        ['{1..2}{2..3}', ['12', '13', '22', '23']],
        ['{0..8..2}', ['0', '2', '4', '6', '8']],
        ['{1..8..2}', ['1', '3', '5', '7']],
        ['{3..-2}', ['3', '2', '1', '0', '-1', '-2']],
        ['1{a..b}2{b..c}3', ['1a2b3', '1a2c3', '1b2b3', '1b2c3']],
        ['{a..b}{b..c}', ['ab', 'ac', 'bb', 'bc']],
        ['{a..k..2}', ['a', 'c', 'e', 'g', 'i', 'k']],
        ['{b..k..2}', ['b', 'd', 'f', 'h', 'j']]
    ]

    error_cases = [
        # This would fail in bash, but we won't fail because we aren't Bash
        ['{a,b,c}\\', ['a', 'b', 'c']]
    ]

    @staticmethod
    def eval_str_esc(string):
        r"""Evaluate buffer as a string buffer counting things like \\ as \."""

        return ast.literal_eval('"%s"' % string.strip().replace('"', '\\"'))

    @staticmethod
    def assert_equal(a, b):
        """Assert equal."""

        assert a == b, "Comparison between objects yielded False."

    @classmethod
    def eval_brace_cases(cls, case):
        """Evaluate the brace cases."""

        print("PATTERN: ", case[0])
        expanded_pattern = []
        try:
            expanded_pattern.extend(
                list(bracex.iexpand(case[0]))
            )
        except Exception:
            expanded_pattern.append(case[0])
        result = expanded_pattern
        goal = case[1]
        print('TEST: ', result, '<=^=>', goal, '\n')
        cls.assert_equal(result, goal)

    @pytest.mark.parametrize("case", error_cases)
    def test_internal_errors(self, case):
        """Test trailing escape."""

        self.eval_brace_cases(case)

    @pytest.mark.parametrize("case", dollar_cases)
    def test_dollar_expand(self, case):
        """Test that dollar expansions don't expand."""

        self.eval_brace_cases(case)

    @pytest.mark.parametrize("case", empty_cases)
    def test_empty_expand(self, case):
        """Test empty expansion."""

        self.eval_brace_cases(case)

    @pytest.mark.parametrize("case", negative_incr_cases)
    def test_negative_incr_expand(self, case):
        """Test negative increment expansion."""

        self.eval_brace_cases(case)

    @pytest.mark.parametrize("case", nested_cases)
    def test_nested_expand(self, case):
        """Test nested expansion."""

        self.eval_brace_cases(case)

    @pytest.mark.parametrize("case", order_cases)
    def test_order_expand(self, case):
        """Test ordered expansion."""

        self.eval_brace_cases(case)

    @pytest.mark.parametrize("case", pad_cases)
    def test_pad_expand(self, case):
        """Test padded expansion."""

        self.eval_brace_cases(case)

    @pytest.mark.parametrize("case", sequence_cases)
    def test_sequence_expand(self, case):
        """Test sequence expansion."""

        self.eval_brace_cases(case)

    @pytest.mark.parametrize("case", get_bash_cases())
    def test_bash_cases(self, case):
        """Test bash cases."""

        test_case = self.eval_str_esc(case[0])
        entry = case[1]

        print('TEST: ', test_case)
        self.assert_equal(test_case, entry[0])
        expansions = bracex.expand(test_case)
        if not (len(expansions) == 1 and not expansions[0]):
            index = 1
            for a in expansions:
                b = RE_REMOVE.sub('', entry[index])
                print('    ', a, '<==>', b)
                self.assert_equal(a, b)
                index += 1

        # Bytes
        test_case = test_case.encode('utf-8')
        self.assert_equal(test_case, entry[0].encode('utf-8'))
        expansions = bracex.expand(test_case)
        if not (len(expansions) == 1 and not expansions[0]):
            index = 1
            for a in expansions:
                b = BRE_REMOVE.sub(b'', entry[index].encode('utf-8'))
                print('    ', a, '<==>', b)
                self.assert_equal(a, b)
                index += 1


class TestExpansionLimit(unittest.TestCase):
    """Test brace expansion limit."""

    def test_expansion_limit_expand(self):
        """Test expansion limit."""

        self.assertEqual(len(bracex.expand('text{1,2}text{{3,4,{5,6}text{7,8}},{9}}', limit=14)), 14)
        with self.assertRaises(bracex.ExpansionLimitException):
            bracex.expand('text{1,2}text{{3,4,{5,6}text{7,8}},{9}}', limit=13)

    def test_expansion_no_limit_expand(self):
        """Test expansion no limit."""

        self.assertEqual(len(bracex.expand('text{1,2}text{{3,4,{5,6}text{7,8}},{9}}', limit=0)), 14)

    def test_expansion_char_limt_expand(self):
        """Test expansion character limit with `expand`."""

        self.assertEqual(len(bracex.expand('{a..k}', limit=11)), 11)
        with self.assertRaises(bracex.ExpansionLimitException):
            bracex.expand('{a..k}', limit=10)

    def test_expansion_char_reverse_limt_expand(self):
        """Test expansion character reversed limit with `expand`."""

        self.assertEqual(len(bracex.expand('{k..a}', limit=11)), 11)
        with self.assertRaises(bracex.ExpansionLimitException):
            bracex.expand('{k..a}', limit=10)

    def test_expansion_char_limt_expand_step2(self):
        """Test expansion character limit with `expand` step 2."""

        self.assertEqual(len(bracex.expand('{a..k..2}', limit=6)), 6)
        with self.assertRaises(bracex.ExpansionLimitException):
            bracex.expand('{a..k..2}', limit=5)

    def test_expansion_char_limt_expand_step3(self):
        """Test expansion character limit with `expand` step 3."""

        self.assertEqual(len(bracex.expand('{a..k..3}', limit=4)), 4)
        with self.assertRaises(bracex.ExpansionLimitException):
            bracex.expand('{a..k..3}', limit=3)

    def test_expansion_char_limt_expand_step5(self):
        """Test expansion character limit with `expand` step 5."""

        self.assertEqual(len(bracex.expand('{a..k..5}', limit=3)), 3)
        with self.assertRaises(bracex.ExpansionLimitException):
            bracex.expand('{a..k..5}', limit=2)

    def test_expansion_char_reverse_limt_expand_step2(self):
        """Test expansion character reversed limit with `expand` step 2."""

        self.assertEqual(len(bracex.expand('{k..a..2}', limit=6)), 6)
        with self.assertRaises(bracex.ExpansionLimitException):
            bracex.expand('{k..a..2}', limit=5)

    def test_expansion_char_reverse_limt_expand_step3(self):
        """Test expansion character reversed limit with `expand` step 3."""

        self.assertEqual(len(bracex.expand('{k..a..3}', limit=4)), 4)
        with self.assertRaises(bracex.ExpansionLimitException):
            bracex.expand('{k..a..3}', limit=3)

    def test_expansion_char_reverse_limt_expand_step5(self):
        """Test expansion character reversed limit with `expand` step 5."""

        self.assertEqual(len(bracex.expand('{k..a..5}', limit=3)), 3)
        with self.assertRaises(bracex.ExpansionLimitException):
            bracex.expand('{k..a..5}', limit=2)

    def test_expansion_char_limt_expand_step2_neg(self):
        """Test expansion character limit with `expand` step -2."""

        self.assertEqual(len(bracex.expand('{a..k..-2}', limit=6)), 6)
        with self.assertRaises(bracex.ExpansionLimitException):
            bracex.expand('{a..k..-2}', limit=5)

    def test_expansion_char_limt_expand_step3_neg(self):
        """Test expansion character limit with `expand` step -3."""

        self.assertEqual(len(bracex.expand('{a..k..-3}', limit=4)), 4)
        with self.assertRaises(bracex.ExpansionLimitException):
            bracex.expand('{a..k..-3}', limit=3)

    def test_expansion_char_limt_expand_step5_neg(self):
        """Test expansion character limit with `expand` step -5."""

        self.assertEqual(len(bracex.expand('{a..k..-5}', limit=3)), 3)
        with self.assertRaises(bracex.ExpansionLimitException):
            bracex.expand('{a..k..-5}', limit=2)

    def test_expansion_char_reverse_limt_expand_step2_neg(self):
        """Test expansion character reversed limit with `expand` step -2."""

        self.assertEqual(len(bracex.expand('{k..a..-2}', limit=6)), 6)
        with self.assertRaises(bracex.ExpansionLimitException):
            bracex.expand('{k..a..-2}', limit=5)

    def test_expansion_char_reverse_limt_expand_step3_neg(self):
        """Test expansion character reversed limit with `expand` step -3."""

        self.assertEqual(len(bracex.expand('{k..a..-3}', limit=4)), 4)
        with self.assertRaises(bracex.ExpansionLimitException):
            bracex.expand('{k..a..-3}', limit=3)

    def test_expansion_char_reverse_limt_expand_step5_neg(self):
        """Test expansion character reversed limit with `expand` step -5."""

        self.assertEqual(len(bracex.expand('{k..a..-5}', limit=3)), 3)
        with self.assertRaises(bracex.ExpansionLimitException):
            bracex.expand('{k..a..-5}', limit=2)

    def test_expansion_num_limt_expand(self):
        """Test expansion numeric limit with `expand`."""

        self.assertEqual(len(bracex.expand('{1..11}', limit=11)), 11)
        with self.assertRaises(bracex.ExpansionLimitException):
            bracex.expand('{1..11}', limit=10)

    def test_expansion_num_reverse_limt_expand(self):
        """Test expansion numeric reversed limit with `expand`."""

        self.assertEqual(len(bracex.expand('{11..1}', limit=11)), 11)
        with self.assertRaises(bracex.ExpansionLimitException):
            bracex.expand('{11..1}', limit=10)

    def test_expansion_num_negative_limt_expand(self):
        """Test expansion numeric negative limit with `expand`."""

        self.assertEqual(len(bracex.expand('{-1..-11}', limit=11)), 11)
        with self.assertRaises(bracex.ExpansionLimitException):
            bracex.expand('{-1..-11}', limit=10)

    def test_expansion_num_negative_reversed_limt_expand(self):
        """Test expansion numeric negative reversed limit with `expand`."""

        self.assertEqual(len(bracex.expand('{-11..-1}', limit=11)), 11)
        with self.assertRaises(bracex.ExpansionLimitException):
            bracex.expand('{-11..-1}', limit=10)

    def test_expansion_num_limt_expand_step(self):
        """Test expansion numeric limit with `expand` step."""

        self.assertEqual(len(bracex.expand('{1..11..5}', limit=3)), 3)
        with self.assertRaises(bracex.ExpansionLimitException):
            bracex.expand('{1..11..5}', limit=2)

    def test_expansion_num_reverse_limt_expand_step(self):
        """Test expansion numeric reversed limit with `expand` step."""

        self.assertEqual(len(bracex.expand('{11..1..3}', limit=4)), 4)
        with self.assertRaises(bracex.ExpansionLimitException):
            bracex.expand('{11..1..3}', limit=3)

    def test_expansion_num_negative_limt_expand_step(self):
        """Test expansion numeric negative limit with `expand`."""

        self.assertEqual(len(bracex.expand('{-1..-11..2}', limit=6)), 6)
        with self.assertRaises(bracex.ExpansionLimitException):
            bracex.expand('{-1..-11..2}', limit=5)

    def test_expansion_num_negative_reversed_limt_expand_step(self):
        """Test expansion numeric negative reversed limit with `expand`."""

        self.assertEqual(len(bracex.expand('{-11..-1..7}', limit=2)), 2)
        with self.assertRaises(bracex.ExpansionLimitException):
            bracex.expand('{-11..-1..7}', limit=1)

    def test_expansion_num_limt_expand_neg_step(self):
        """Test expansion numeric limit with `expand` negative step."""

        self.assertEqual(len(bracex.expand('{1..11..-5}', limit=3)), 3)
        with self.assertRaises(bracex.ExpansionLimitException):
            bracex.expand('{1..11..-5}', limit=2)

    def test_expansion_num_reverse_limt_expand_neg_step(self):
        """Test expansion numeric reversed limit with `expand` negative step."""

        self.assertEqual(len(bracex.expand('{11..1..-3}', limit=4)), 4)
        with self.assertRaises(bracex.ExpansionLimitException):
            bracex.expand('{11..1..-3}', limit=3)

    def test_expansion_num_negative_limt_expand_neg_step(self):
        """Test expansion numeric negative limit with `expand` negative step."""

        self.assertEqual(len(bracex.expand('{-1..-11..-2}', limit=6)), 6)
        with self.assertRaises(bracex.ExpansionLimitException):
            bracex.expand('{-1..-11..-2}', limit=5)

    def test_expansion_num_negative_reversed_limt_expand_neg_step(self):
        """Test expansion numeric negative reversed limit with `expand` negative step."""

        self.assertEqual(len(bracex.expand('{-11..-11..-7}', limit=1)), 1)
        with self.assertRaises(bracex.ExpansionLimitException):
            bracex.expand('{-11..-1..-7}', limit=1)
bracex-2.2.1/tests/test_main.py000066400000000000000000000061301414064635700164710ustar00rootroot00000000000000"""Test command module and argument handling."""
import pytest
from bracex.__main__ import main
from bracex import __version__


def test_expand_with_default_terminator(capsys):
    """Test that by default one expansion is printed per line."""
    with pytest.raises(SystemExit) as exinfo:
        main(['{a..c}'])
    capture = capsys.readouterr()
    assert capture.out == "a\nb\nc\n"
    assert capture.err == ""
    assert exinfo.value.code == 0


def test_expand_with_spaces(capsys):
    """Test that expansions can be space terminated."""
    with pytest.raises(SystemExit) as exinfo:
        main(['-t', ' ', '{a..c}'])
    capture = capsys.readouterr()
    assert capture.out == "a b c "
    assert capture.err == ""
    assert exinfo.value.code == 0


def test_expand_with_empty_terminators(capsys):
    """Test that expansions can be terminated by an empty string."""
    with pytest.raises(SystemExit) as exinfo:
        main(['-t', '', '{a..c}'])
    capture = capsys.readouterr()
    assert capture.out == "abc"
    assert capture.err == ""
    assert exinfo.value.code == 0


def test_expand_with_nul_terminators(capsys):
    """Test that expansions can be terminated by a NUL character."""
    with pytest.raises(SystemExit) as exinfo:
        main(['-0', '{a..c}'])
    capture = capsys.readouterr()
    assert capture.out == "a\x00b\x00c\x00"
    assert capture.err == ""
    assert exinfo.value.code == 0


def test_terminator_arguments_are_mutually_exclusive(capsys):
    """Test that contradicting terminators raise an error."""
    with pytest.raises(SystemExit) as exinfo:
        main(['-0', '--terminator', ' ', '{a..c}'])

    capture = capsys.readouterr()
    assert capture.out == ""
    assert capture.err.find("error: argument --terminator/-t: not allowed with argument -0") > 0
    assert exinfo.value.code > 0


def test_help(capsys):
    """Test that help is available."""
    with pytest.raises(SystemExit) as exinfo:
        main(['--help'])
    capture = capsys.readouterr()
    assert capture.out.startswith("usage:")
    assert capture.err == ""
    assert exinfo.value.code == 0


def test_version(capsys):
    """Test that the version is available."""
    with pytest.raises(SystemExit) as exinfo:
        main(['--version'])
    capture = capsys.readouterr()
    assert capture.out == f"{__version__}\n"
    assert capture.err == ""
    assert exinfo.value.code == 0


def test_no_args_is_considered_an_error(capsys):
    """Test that an error is reported when no expression is provided."""
    with pytest.raises(SystemExit) as exinfo:
        main([])
    capture = capsys.readouterr()
    assert capture.out == ""
    assert capture.err.endswith("error: the following arguments are required: expression\n")
    assert exinfo.value.code > 0


def test_excess_args_is_considered_an_error(capsys):
    """Test that an error is reported when too many arguments are provided."""
    with pytest.raises(SystemExit) as exinfo:
        main(['{a,b,c}', '{1..3}'])
    capture = capsys.readouterr()
    assert capture.out == ""
    assert capture.err.find("error: unrecognized arguments") > 0
    assert exinfo.value.code > 0
bracex-2.2.1/tests/test_versions.py000066400000000000000000000077711414064635700174310ustar00rootroot00000000000000"""Version tests."""
from __future__ import unicode_literals
import unittest
from bracex.__meta__ import Version, parse_version


class TestVersion(unittest.TestCase):
    """Test versions."""

    def test_version_output(self):
        """Test that versions generate proper strings."""

        assert Version(1, 0, 0, "final")._get_canonical() == "1.0"
        assert Version(1, 2, 0, "final")._get_canonical() == "1.2"
        assert Version(1, 2, 3, "final")._get_canonical() == "1.2.3"
        assert Version(1, 2, 0, "alpha", pre=4)._get_canonical() == "1.2a4"
        assert Version(1, 2, 0, "beta", pre=4)._get_canonical() == "1.2b4"
        assert Version(1, 2, 0, "candidate", pre=4)._get_canonical() == "1.2rc4"
        assert Version(1, 2, 0, "final", post=1)._get_canonical() == "1.2.post1"
        assert Version(1, 2, 3, ".dev-alpha", pre=1)._get_canonical() == "1.2.3a1.dev0"
        assert Version(1, 2, 3, ".dev")._get_canonical() == "1.2.3.dev0"
        assert Version(1, 2, 3, ".dev", dev=1)._get_canonical() == "1.2.3.dev1"

    def test_version_comparison(self):
        """Test that versions compare proper."""

        assert Version(1, 0, 0, "final") < Version(1, 2, 0, "final")
        assert Version(1, 2, 0, "alpha", pre=4) < Version(1, 2, 0, "final")
        assert Version(1, 2, 0, "final") < Version(1, 2, 0, "final", post=1)
        assert Version(1, 2, 3, ".dev-beta", pre=2) < Version(1, 2, 3, "beta", pre=2)
        assert Version(1, 2, 3, ".dev") < Version(1, 2, 3, ".dev-beta", pre=2)
        assert Version(1, 2, 3, ".dev") < Version(1, 2, 3, ".dev", dev=1)

    def test_version_parsing(self):
        """Test version parsing."""

        assert parse_version(
            Version(1, 0, 0, "final")._get_canonical()
        ) == Version(1, 0, 0, "final")
        assert parse_version(
            Version(1, 2, 0, "final")._get_canonical()
        ) == Version(1, 2, 0, "final")
        assert parse_version(
            Version(1, 2, 3, "final")._get_canonical()
        ) == Version(1, 2, 3, "final")
        assert parse_version(
            Version(1, 2, 0, "alpha", pre=4)._get_canonical()
        ) == Version(1, 2, 0, "alpha", pre=4)
        assert parse_version(
            Version(1, 2, 0, "beta", pre=4)._get_canonical()
        ) == Version(1, 2, 0, "beta", pre=4)
        assert parse_version(
            Version(1, 2, 0, "candidate", pre=4)._get_canonical()
        ) == Version(1, 2, 0, "candidate", pre=4)
        assert parse_version(
            Version(1, 2, 0, "final", post=1)._get_canonical()
        ) == Version(1, 2, 0, "final", post=1)
        assert parse_version(
            Version(1, 2, 3, ".dev-alpha", pre=1)._get_canonical()
        ) == Version(1, 2, 3, ".dev-alpha", pre=1)
        assert parse_version(
            Version(1, 2, 3, ".dev")._get_canonical()
        ) == Version(1, 2, 3, ".dev")
        assert parse_version(
            Version(1, 2, 3, ".dev", dev=1)._get_canonical()
        ) == Version(1, 2, 3, ".dev", dev=1)

    def test_asserts(self):
        """Test asserts."""

        with self.assertRaises(ValueError):
            Version("1", "2", "3")
        with self.assertRaises(ValueError):
            Version(1, 2, 3, 1)
        with self.assertRaises(ValueError):
            Version("1", "2", "3")
        with self.assertRaises(ValueError):
            Version(1, 2, 3, "bad")
        with self.assertRaises(ValueError):
            Version(1, 2, 3, "alpha")
        with self.assertRaises(ValueError):
            Version(1, 2, 3, "alpha", pre=1, dev=1)
        with self.assertRaises(ValueError):
            Version(1, 2, 3, "alpha", pre=1, post=1)
        with self.assertRaises(ValueError):
            Version(1, 2, 3, ".dev-alpha")
        with self.assertRaises(ValueError):
            Version(1, 2, 3, ".dev-alpha", pre=1, post=1)
        with self.assertRaises(ValueError):
            Version(1, 2, 3, pre=1)
        with self.assertRaises(ValueError):
            Version(1, 2, 3, dev=1)
        with self.assertRaises(ValueError):
            parse_version('bad&version')
bracex-2.2.1/tox.ini000066400000000000000000000013341414064635700143060ustar00rootroot00000000000000[tox]
skipsdist=true
envlist=
    py36,py37,py38,py39,
    py310,
    lint

[testenv]
passenv= *
deps=
    -rrequirements/project.txt
    -rrequirements/test.txt
commands=
    {envbindir}/mypy
    {envbindir}/py.test --cov bracex --cov-append tests
    {envbindir}/coverage html -d {envtmpdir}/coverage
    {envbindir}/coverage xml
    {envbindir}/coverage report --show-missing

[testenv:lint]
passenv= *
deps=
    -rrequirements/lint.txt
commands=
    {envbindir}/flake8 .

[testenv:documents]
passenv= *
deps=
    -rrequirements/docs.txt
commands=
    mkdocs build --clean --verbose --strict
    pyspelling

[flake8]
exclude=build/*,.tox/*,bracex/pep562.py
max-line-length=120
ignore=D202,D203,D401,N802,N801,N803,N806,E741,N818