[
  {
    "path": ".editorconfig",
    "content": "# Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Editor configuration, see http://editorconfig.org\nroot = true\n\n[*.rs]\ncharset = utf-8\nindent_style = space\nindent_size = 4\ninsert_final_newline = true\ntrim_trailing_whitespace = false\nmax_line_length = 100\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"cargo\"\n    directory: \"/\"\n    schedule:\n      interval: \"daily\"\n\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"daily\"\n"
  },
  {
    "path": ".github/workflows/python-build.yml",
    "content": "#\n# Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nname: Python Build\n\non:\n  push:\n    branches:\n      - main\n    paths:\n      - 'Cargo.lock'\n      - 'Cargo.toml'\n      - 'pyproject.toml'\n      - 'requirements.txt'\n      - 'src/**'\n      - 'tests/**'\n      - '**.yml'\n  pull_request:\n    branches:\n      - main\n    paths:\n      - 'Cargo.lock'\n      - 'Cargo.toml'\n      - 'pyproject.toml'\n      - 'requirements.txt'\n      - 'src/**'\n      - 'tests/**'\n      - '**.yml'\n\njobs:\n  python-build:\n      name: Python ${{ matrix.python-version }} on ${{ matrix.name }}\n\n      runs-on: ${{ matrix.os }}\n\n      strategy:\n        fail-fast: false\n        matrix:\n          os: [ ubuntu-latest, macos-latest, windows-latest ]\n          python-version: [ '3.12', '3.13', '3.14' ]\n          include:\n            - os: ubuntu-latest\n              name: Linux 64-Bit\n\n            - os: macos-latest\n              name: MacOS 64-Bit\n\n            - os: windows-latest\n              name: Windows 64-Bit\n\n      steps:\n        - name: Check out repository\n          uses: actions/checkout@v6\n\n        - name: Set up Python\n          uses: actions/setup-python@v6\n          with:\n            python-version: ${{ matrix.python-version }}\n            cache: 'pip'\n\n        - name: Install maturin and pytest\n          run: pip install -r requirements.txt\n\n        - name: Build Python extension\n          run: maturin build\n\n        - name: Install Python extension\n          run: pip install --find-links=target/wheels grex\n\n        - name: Run Python unit tests\n          run: pytest tests/python/test_grex.py\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "#\n# Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nname: Release\n\non:\n  push:\n    tags:\n      - v1.*\n\njobs:\n  rust-release-build:\n    name: ${{ matrix.name }}\n\n    runs-on: ${{ matrix.os }}\n\n    strategy:\n      matrix:\n        os: [ubuntu-latest, macos-latest, windows-latest]\n        include:\n          - os: ubuntu-latest\n            name: Rust Release Build on Linux\n            x86_64-target: x86_64-unknown-linux-musl\n            aarch64-target: aarch64-unknown-linux-musl\n\n          - os: macos-latest\n            name: Rust Release Build on MacOS\n            x86_64-target: x86_64-apple-darwin\n            aarch64-target: aarch64-apple-darwin\n\n          - os: windows-latest\n            name: Rust Release Build on Windows\n            x86_64-target: x86_64-pc-windows-msvc\n            aarch64-target: aarch64-pc-windows-msvc\n\n    steps:\n      - name: Check out repository\n        uses: actions/checkout@v6\n\n      - name: Build x86_64 target in release mode\n        uses: houseabsolute/actions-rust-cross@v1\n        with:\n          target: ${{ matrix.x86_64-target }}\n          args: '--release --locked'\n\n      - name: Build aarch64 target in release mode\n        uses: houseabsolute/actions-rust-cross@v1\n        with:\n          target: ${{ matrix.aarch64-target }}\n          args: '--release --locked'\n\n      - name: Get latest release version number\n        id: get_version\n        uses: battila7/get-version-action@v2\n\n      - name: Create x86_64 zip file on Windows\n        if: ${{ matrix.os == 'windows-latest' }}\n        run: |\n          choco install zip\n          cd target/${{ matrix.x86_64-target }}/release\n          zip grex-${{ steps.get_version.outputs.version }}-${{ matrix.x86_64-target }}.zip grex.exe\n          cd ../../..\n\n      - name: Create aarch64 zip file on Windows\n        if: ${{ matrix.os == 'windows-latest' }}\n        run: |\n          cd target/${{ matrix.aarch64-target }}/release\n          zip grex-${{ steps.get_version.outputs.version }}-${{ matrix.aarch64-target }}.zip grex.exe\n          cd ../../..\n\n      - name: Create x86_64 tar.gz file on Linux and macOS\n        if: ${{ matrix.os != 'windows-latest' }}\n        run: |\n          chmod +x target/${{ matrix.x86_64-target }}/release/grex\n          tar -zcf target/${{ matrix.x86_64-target }}/release/grex-${{ steps.get_version.outputs.version }}-${{ matrix.x86_64-target }}.tar.gz -C target/${{ matrix.x86_64-target }}/release grex\n\n      - name: Create aarch64 tar.gz file on Linux and macOS\n        if: ${{ matrix.os != 'windows-latest' }}\n        run: |\n          chmod +x target/${{ matrix.aarch64-target }}/release/grex\n          tar -zcf target/${{ matrix.aarch64-target }}/release/grex-${{ steps.get_version.outputs.version }}-${{ matrix.aarch64-target }}.tar.gz -C target/${{ matrix.aarch64-target }}/release grex\n\n      - name: Upload release and assets to GitHub\n        uses: svenstaro/upload-release-action@v2\n        with:\n          repo_token: ${{ secrets.GITHUB_TOKEN }}\n          tag: ${{ github.ref }}\n          release_name: grex ${{ steps.get_version.outputs.version-without-v }}\n          file_glob: true\n          file: target/*/release/grex-${{ steps.get_version.outputs.version }}-*.{zip,tar.gz}\n\n  python-linux-release-build:\n    name: Python Release Build on Linux and target ${{ matrix.target }}\n    needs: rust-release-build\n\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        target: [ x86_64, aarch64 ]\n        linux: [ auto, musllinux_1_2 ]\n\n    steps:\n      - name: Check out repository\n        uses: actions/checkout@v6\n\n      - name: Build wheels\n        uses: PyO3/maturin-action@v1\n        with:\n          target: ${{ matrix.target }}\n          args: --release --out dist -i 3.12 3.13 3.14 pypy3.11\n          sccache: 'true'\n          manylinux: ${{ matrix.linux }}\n\n      - name: Upload wheels\n        uses: actions/upload-artifact@v5\n        with:\n          name: linux-${{ matrix.linux }}-${{ matrix.target }}-wheels\n          path: dist\n\n  python-windows-release-build:\n    name: Python Release Build on Windows and target ${{ matrix.target }}\n    needs: rust-release-build\n\n    runs-on: windows-latest\n\n    strategy:\n      matrix:\n        target: [ x86_64, aarch64 ]\n\n    steps:\n      - name: Check out repository\n        uses: actions/checkout@v6\n\n      - name: Build wheels\n        uses: PyO3/maturin-action@v1\n        with:\n          target: ${{ matrix.target }}\n          args: --release --out dist -i 3.12 3.13 3.14\n          sccache: 'true'\n\n      - name: Upload wheels\n        uses: actions/upload-artifact@v5\n        with:\n          name: windows-${{ matrix.target }}-wheels\n          path: dist\n\n  python-macos-release-build:\n    name: Python Release Build on MacOS and target ${{ matrix.target }}\n    needs: rust-release-build\n\n    runs-on: macos-latest\n\n    strategy:\n      matrix:\n        target: [ x86_64, aarch64 ]\n\n    steps:\n      - name: Check out repository\n        uses: actions/checkout@v6\n\n      - name: Build wheels\n        uses: PyO3/maturin-action@v1\n        with:\n          target: ${{ matrix.target }}\n          args: --release --out dist -i 3.12 3.13 3.14 pypy3.11\n          sccache: 'true'\n\n      - name: Upload wheels\n        uses: actions/upload-artifact@v5\n        with:\n          name: macos-${{ matrix.target }}-wheels\n          path: dist\n\n  python-release-upload:\n    name: Publish wheels to PyPI\n    needs: [ python-linux-release-build, python-windows-release-build, python-macos-release-build ]\n\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Download wheels from previous jobs\n        uses: actions/download-artifact@v6\n        with:\n          path: wheels\n          merge-multiple: true\n\n      - name: Upload to PyPI\n        uses: PyO3/maturin-action@v1\n        env:\n          MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}\n        with:\n          command: upload\n          args: --skip-existing wheels/*.whl\n\n  rust-release-upload:\n    name: Upload to crates.io\n    needs: [ python-linux-release-build, python-windows-release-build, python-macos-release-build ]\n\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Check out repository\n        uses: actions/checkout@v6\n\n      - name: Upload release to crates.io\n        uses: katyo/publish-crates@v2\n        with:\n          registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/rust-build.yml",
    "content": "#\n# Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nname: Rust Build\n\non:\n  push:\n    branches:\n      - main\n    paths:\n      - 'Cargo.lock'\n      - 'Cargo.toml'\n      - 'src/**'\n      - 'tests/**'\n      - '**.yml'\n  pull_request:\n    branches:\n      - main\n    paths:\n      - 'Cargo.lock'\n      - 'Cargo.toml'\n      - 'src/**'\n      - 'tests/**'\n      - '**.yml'\n\njobs:\n  rust-build:\n    name: Rust on ${{ matrix.name }}\n\n    runs-on: ${{ matrix.os }}\n\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-latest, macos-latest, windows-latest]\n        include:\n          - os: ubuntu-latest\n            name: Linux 64-Bit\n            target: x86_64-unknown-linux-musl\n\n          - os: macos-latest\n            name: MacOS 64-Bit\n            target: x86_64-apple-darwin\n            env:\n              MACOSX_DEPLOYMENT_TARGET: 10.7\n\n          - os: windows-latest\n            name: Windows 64-Bit\n            target: x86_64-pc-windows-msvc\n\n    steps:\n      - name: Check out repository\n        uses: actions/checkout@v6\n\n      - name: Add rustup target\n        run: rustup target add ${{ matrix.target }}\n\n      - name: Store or retrieve cargo caches\n        uses: actions/cache@v4\n        with:\n          path: |\n            ~/.cargo/bin/\n            ~/.cargo/registry/index/\n            ~/.cargo/registry/cache/\n            ~/.cargo/git/db/\n            target/\n          key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}\n\n      - name: Build target in debug mode\n        run: cargo build --target ${{ matrix.target }} --locked\n\n      - name: Test target in debug mode\n        run: cargo test --target ${{ matrix.target }}\n\n      - name: Check Clippy lints\n        run: cargo clippy --target ${{ matrix.target }} -- -Dwarnings\n\n  wasm-build:\n    name: WASM Build\n    needs: rust-build\n\n    runs-on: macos-latest\n\n    steps:\n      - name: Check out repository\n        uses: actions/checkout@v6\n\n      - name: Install wasm-pack\n        run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh\n\n      - name: Install Firefox and Geckodriver # not available anymore in macos-latest\n        run: |\n          brew install --cask firefox\n          brew install geckodriver\n\n      #- name: Enable Safari web driver\n      #  run: sudo safaridriver --enable\n\n      - name: Run WASM integration tests on NodeJS\n        run: wasm-pack test --node -- --no-default-features\n\n      - name: Run WASM integration tests in Chrome\n        run: wasm-pack test --headless --chrome -- --no-default-features\n\n      - name: Run WASM integration tests in Firefox\n        run: wasm-pack test --headless --firefox -- --no-default-features\n\n      # Safari WASM tests not working, reason unclear\n      # Increasing driver timeout does not seem to work\n      # https://github.com/wasm-bindgen/wasm-bindgen/pull/4320\n\n      #- name: Run WASM integration tests in Safari\n      #  env:\n      #    WASM_BINDGEN_TEST_DRIVER_TIMEOUT: 10\n      #  run: wasm-pack test --headless --safari -- --no-default-features\n\n  coverage-report:\n    name: Coverage Report\n    needs: rust-build\n    if: ${{ github.event_name == 'push' }}\n\n    runs-on: ubuntu-latest\n\n    container:\n      image: xd009642/tarpaulin:develop-nightly\n      options: --security-opt seccomp=unconfined\n\n    steps:\n      - name: Check out repository\n        uses: actions/checkout@v6\n\n      - name: Generate coverage report\n        run: cargo +nightly tarpaulin --ignore-config --ignore-panics --ignore-tests --exclude-files src/python.rs src/main.rs src/wasm.rs --verbose --timeout 900 --out xml\n\n      - name: Workaround for codecov/feedback#263\n        run: git config --global --add safe.directory \"$GITHUB_WORKSPACE\"\n\n      - name: Upload coverage report\n        uses: codecov/codecov-action@v4\n        with:\n          token: ${{ secrets.CODECOV_TOKEN }}\n          fail_ci_if_error: true\n"
  },
  {
    "path": ".gitignore",
    "content": "# Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n/pkg/\n/target/\n**/*.rs.bk\n\n.idea\n.project\n.c9/\n*.launch\n.settings/\n.metadata/\n.venv\n*.sublime-workspace\nbin/\ntmp/\nout/\n*.iml\n*.ipr\n*.iws\n*.bak\n*.tmp\n*.class\n*.html\n.buildpath\n.classpath\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n\n.DS_Store\nThumbs.db\n$RECYCLE.BIN/\n._*\n.AppleDouble\n.LSOverride\n*.lnk\nDesktop.ini\nehthumbs.db\n\n*.proptest-regressions\n"
  },
  {
    "path": "Cargo.toml",
    "content": "#\n# Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n[package]\nname = \"grex\"\nversion = \"1.4.6\"\nauthors = [\"Peter M. Stahl <pemistahl@gmail.com>\"]\ndescription = \"\"\"\ngrex generates regular expressions from user-provided test cases.\n\"\"\"\nhomepage = \"https://github.com/pemistahl/grex\"\nrepository = \"https://github.com/pemistahl/grex\"\ndocumentation = \"https://docs.rs/grex\"\nlicense = \"Apache-2.0\"\nreadme = \"README.md\"\nedition = \"2021\"\ncategories = [\"command-line-utilities\", \"parsing\"]\nkeywords = [\"pattern\", \"regex\", \"regexp\"]\n\n[lib]\ncrate-type = [\"cdylib\", \"rlib\"]\n\n[dependencies]\nitertools = \"0.14.0\"\nndarray = \"0.17.1\"\npetgraph = {version = \"0.8.3\", default-features = false, features = [\"stable_graph\"]}\nregex = \"1.12.2\"\nunicode-general-category = \"1.1.0\"\nunicode-segmentation = \"1.12.0\"\n\n[target.'cfg(not(target_family = \"wasm\"))'.dependencies]\nclap = {version = \"4.5.53\", features = [\"derive\", \"wrap_help\"], optional = true}\npyo3 = {version = \"0.27.1\", optional = true}\n\n[target.'cfg(target_family = \"wasm\")'.dependencies]\nwasm-bindgen = \"0.2.105\"\n\n[dev-dependencies]\nindoc = \"2.0.7\"\nrstest = \"0.26.1\"\n\n[target.'cfg(not(target_family = \"wasm\"))'.dev-dependencies]\nassert_cmd = \"2.1.1\"\ncriterion = \"0.7.0\"\npredicates = \"3.1.3\"\nproptest = \"1.9.0\"\ntempfile = \"3.23.0\"\n\n[target.'cfg(target_family = \"wasm\")'.dev-dependencies]\nwasm-bindgen-test = \"0.3.55\"\n\n[features]\ndefault = [\"cli\"]\ncli = [\"clap\"]\npython = [\"pyo3\"]\n\n[[bench]]\nname = \"benchmark\"\nharness = false\n\n[profile.bench]\ndebug = true\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n\n  ![grex](https://raw.githubusercontent.com/pemistahl/grex/main/logo.png)\n\n  <br>\n\n  [![rust build status](https://github.com/pemistahl/grex/actions/workflows/rust-build.yml/badge.svg)](https://github.com/pemistahl/grex/actions/workflows/rust-build.yml)\n  [![python build status](https://github.com/pemistahl/grex/actions/workflows/python-build.yml/badge.svg)](https://github.com/pemistahl/grex/actions/workflows/python-build.yml)\n  [![docs.rs](https://docs.rs/grex/badge.svg)](https://docs.rs/grex)\n  [![codecov](https://codecov.io/gh/pemistahl/grex/branch/main/graph/badge.svg)](https://codecov.io/gh/pemistahl/grex)\n  [![dependency status](https://deps.rs/crate/grex/1.4.6/status.svg)](https://deps.rs/crate/grex/1.4.6)\n  [![demo](https://img.shields.io/badge/-Demo%20Website-orange?logo=HTML5&labelColor=white)](https://pemistahl.github.io/grex-js/)\n  \n  [![downloads](https://img.shields.io/crates/d/grex.svg)](https://crates.io/crates/grex)\n  [![crates.io](https://img.shields.io/crates/v/grex.svg)](https://crates.io/crates/grex)\n  [![lib.rs](https://img.shields.io/badge/lib.rs-v1.4.6-blue)](https://lib.rs/crates/grex)\n  ![supported Python versions](https://img.shields.io/badge/Python-%3E%3D%203.12-blue?logo=Python&logoColor=yellow)\n  [![pypi](https://img.shields.io/badge/PYPI-v1.0.2-blue?logo=PyPI&logoColor=yellow)](https://pypi.org/project/grex)\n  [![license](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)\n\n  [![Linux 64-bit Download](https://img.shields.io/badge/Linux%2064bit%20Download-v1.4.6-blue?logo=Linux)](https://github.com/pemistahl/grex/releases/download/v1.4.6/grex-v1.4.6-x86_64-unknown-linux-musl.tar.gz)\n  [![Linux ARM64 Download](https://img.shields.io/badge/Linux%20ARM64%20Download-v1.4.6-blue?logo=Linux)](https://github.com/pemistahl/grex/releases/download/v1.4.6/grex-v1.4.6-aarch64-unknown-linux-musl.tar.gz)  \n  \n  [![MacOS 64-bit Download](https://img.shields.io/badge/macOS%2064bit%20Download-v1.4.6-blue?logo=Apple)](https://github.com/pemistahl/grex/releases/download/v1.4.6/grex-v1.4.6-x86_64-apple-darwin.tar.gz)\n  [![MacOS ARM64 Download](https://img.shields.io/badge/macOS%20ARM64%20Download-v1.4.6-blue?logo=Apple)](https://github.com/pemistahl/grex/releases/download/v1.4.6/grex-v1.4.6-aarch64-apple-darwin.tar.gz)\n  \n  [![Windows 64-bit Download](https://img.shields.io/badge/Windows%2064bit%20Download-v1.4.6-blue?logo=Windows)](https://github.com/pemistahl/grex/releases/download/v1.4.6/grex-v1.4.6-x86_64-pc-windows-msvc.zip)\n  [![Windows ARM64 Download](https://img.shields.io/badge/Windows%20ARM64%20Download-v1.4.6-blue?logo=Windows)](https://github.com/pemistahl/grex/releases/download/v1.4.6/grex-v1.4.6-aarch64-pc-windows-msvc.zip)\n</div>\n\n<br>\n\n![grex demo](https://raw.githubusercontent.com/pemistahl/grex/main/demo.gif)\n\n<br>\n\n## 1. What does this tool do?\n\n*grex* is a library as well as a command-line utility that is meant to simplify the often \ncomplicated and tedious task of creating regular expressions. It does so by automatically \ngenerating a single regular expression from user-provided test cases. The resulting\nexpression is guaranteed to match the test cases which it was generated from.\n\nThis project has started as a Rust port of the JavaScript tool \n[*regexgen*](https://github.com/devongovett/regexgen) written by \n[Devon Govett](https://github.com/devongovett). Although a lot of further useful features \ncould be added to it, its development was apparently ceased several years ago. The plan \nis now to add these new features to *grex* as Rust really shines when it comes to \ncommand-line tools. *grex* offers all features that *regexgen* provides, and more.\n\nThe philosophy of this project is to generate the most specific regular expression \npossible by default which exactly matches the given input only and nothing else. \nWith the use of command-line flags (in the CLI tool) or preprocessing methods \n(in the library), more generalized expressions can be created.\n\nThe produced expressions are [Perl-compatible regular expressions](https://www.pcre.org) which are also \ncompatible with the regular expression parser in Rust's [*regex* crate](https://crates.io/crates/regex).\nOther regular expression parsers or respective libraries from other programming languages \nhave not been tested so far, but they ought to be mostly compatible as well.\n\n## 2. Do I still need to learn to write regexes then?\n\n**Definitely, yes!** Using the standard settings, *grex* produces a regular expression that is guaranteed\nto match only the test cases given as input and nothing else. \nThis has been verified by [property tests](https://github.com/pemistahl/grex/blob/main/tests/property_tests.rs).\nHowever, if the conversion to shorthand character classes such as `\\w` is enabled, the resulting regex matches\na much wider scope of test cases. Knowledge about the consequences of this conversion is essential for finding\na correct regular expression for your business domain.\n\n*grex* uses an algorithm that tries to find the shortest possible regex for the given test cases.\nVery often though, the resulting expression is still longer or more complex than it needs to be.\nIn such cases, a more compact or elegant regex can be created only by hand.\nAlso, every regular expression engine has different built-in optimizations. *grex* does not know anything\nabout those and therefore cannot optimize its regexes for a specific engine.\n\n**So, please learn how to write regular expressions!** The currently best use case for *grex* is to find\nan initial correct regex which should be inspected by hand if further optimizations are possible.  \n\n## 3. Current Features\n- literals\n- character classes\n- detection of common prefixes and suffixes\n- detection of repeated substrings and conversion to `{min,max}` quantifier notation\n- alternation using `|` operator\n- optionality using `?` quantifier\n- escaping of non-ascii characters, with optional conversion of astral code points to surrogate pairs\n- case-sensitive or case-insensitive matching\n- capturing or non-capturing groups\n- optional anchors `^` and `$`\n- fully compliant to [Unicode Standard 16.0](https://unicode.org/versions/Unicode15.0.0)\n- fully compatible with [*regex* crate 1.11.0+](https://crates.io/crates/regex)\n- correctly handles graphemes consisting of multiple Unicode symbols\n- reads input strings from the command-line or from a file\n- produces more readable expressions indented on multiple using optional verbose mode \n- optional syntax highlighting for nicer output in supported terminals\n\n## 4. How to install?\n\n### 4.1 The command-line tool\n\nYou can download the self-contained executable for your platform above and put it in a place of your choice. \nAlternatively, pre-compiled 64-Bit binaries are available within the package managers [Scoop](https://scoop.sh) \n(for Windows), [Homebrew](https://brew.sh) (for macOS and Linux), [MacPorts](https://www.macports.org) (for macOS), and [Huber](https://github.com/innobead/huber) (for macOS, Linux and Windows). \n[Raúl Piracés](https://github.com/piraces) has contributed a [Chocolatey Windows package](https://community.chocolatey.org/packages/grex).\n\n*grex* is also hosted on [crates.io](https://crates.io/crates/grex), \nthe official Rust package registry. If you are a Rust developer and already have the Rust \ntoolchain installed, you can install by compiling from source using \n[*cargo*](https://doc.rust-lang.org/cargo/), the Rust package manager.\nSo the summary of your installation options is:\n\n```\n( brew | cargo | choco | huber | port | scoop ) install grex\n```\n\n### 4.2 The library\n\nIn order to use *grex* as a library, simply add it as a dependency to your `Cargo.toml` file:\n\n```toml\n[dependencies]\ngrex = { version = \"1.4.6\", default-features = false }\n```\n\nThe dependency *clap* is only needed for the command-line tool.\nBy disabling the default features, the download and compilation of clap is prevented for the library.\n\n## 5. How to use?\n\nDetailed explanations of the available settings are provided in the [library section](#52-the-library).\nAll settings can be freely combined with each other.\n\n### 5.1 The command-line tool\n\nTest cases are passed either directly (`grex a b c`) or from a file (`grex -f test_cases.txt`).\n*grex* is able to receive its input from Unix pipelines as well, e.g. `cat test_cases.txt | grex -`.\n\nThe following table shows all available flags and options:\n\n```\n$ grex -h\n\ngrex 1.4.6\n© 2019-today Peter M. Stahl <pemistahl@gmail.com>\nLicensed under the Apache License, Version 2.0\nDownloadable from https://crates.io/crates/grex\nSource code at https://github.com/pemistahl/grex\n\ngrex generates regular expressions from user-provided test cases.\n\nUsage: grex [OPTIONS] {INPUT...|--file <FILE>}\n\nInput:\n  [INPUT]...         One or more test cases separated by blank space\n  -f, --file <FILE>  Reads test cases on separate lines from a file\n\nDigit Options:\n  -d, --digits      Converts any Unicode decimal digit to \\d\n  -D, --non-digits  Converts any character which is not a Unicode decimal digit to \\D\n\nWhitespace Options:\n  -s, --spaces      Converts any Unicode whitespace character to \\s\n  -S, --non-spaces  Converts any character which is not a Unicode whitespace character to \\S\n\nWord Options:\n  -w, --words      Converts any Unicode word character to \\w\n  -W, --non-words  Converts any character which is not a Unicode word character to \\W\n\nEscaping Options:\n  -e, --escape           Replaces all non-ASCII characters with unicode escape sequences\n      --with-surrogates  Converts astral code points to surrogate pairs if --escape is set\n\nRepetition Options:\n  -r, --repetitions\n          Detects repeated non-overlapping substrings and converts them to {min,max} quantifier\n          notation\n      --min-repetitions <QUANTITY>\n          Specifies the minimum quantity of substring repetitions to be converted if --repetitions\n          is set [default: 1]\n      --min-substring-length <LENGTH>\n          Specifies the minimum length a repeated substring must have in order to be converted if\n          --repetitions is set [default: 1]\n\nAnchor Options:\n      --no-start-anchor  Removes the caret anchor `^` from the resulting regular expression\n      --no-end-anchor    Removes the dollar sign anchor `$` from the resulting regular expression\n      --no-anchors       Removes the caret and dollar sign anchors from the resulting regular\n                         expression\n\nDisplay Options:\n  -x, --verbose   Produces a nicer-looking regular expression in verbose mode\n  -c, --colorize  Provides syntax highlighting for the resulting regular expression\n\nMiscellaneous Options:\n  -i, --ignore-case     Performs case-insensitive matching, letters match both upper and lower case\n  -g, --capture-groups  Replaces non-capturing groups with capturing ones\n  -h, --help            Prints help information\n  -v, --version         Prints version information\n\n \n```\n\n### 5.2 The library\n\n#### 5.2.1 Default settings\n\nTest cases are passed either from a collection via [`RegExpBuilder::from()`](https://docs.rs/grex/1.4.6/grex/struct.RegExpBuilder.html#method.from) \nor from a file via [`RegExpBuilder::from_file()`](https://docs.rs/grex/1.4.6/grex/struct.RegExpBuilder.html#method.from_file).\nIf read from a file, each test case must be on a separate line. Lines may be ended with either a newline `\\n` or a carriage\nreturn with a line feed `\\r\\n`.\n\n```rust\nuse grex::RegExpBuilder;\n\nlet regexp = RegExpBuilder::from(&[\"a\", \"aa\", \"aaa\"]).build();\nassert_eq!(regexp, \"^a(?:aa?)?$\");\n```\n\n#### 5.2.2 Convert to character classes\n\n```rust\nuse grex::RegExpBuilder;\n\nlet regexp = RegExpBuilder::from(&[\"a\", \"aa\", \"123\"])\n    .with_conversion_of_digits()\n    .with_conversion_of_words()\n    .build();\nassert_eq!(regexp, \"^(\\\\d\\\\d\\\\d|\\\\w(?:\\\\w)?)$\");\n```\n\n#### 5.2.3 Convert repeated substrings\n\n```rust\nuse grex::RegExpBuilder;\n\nlet regexp = RegExpBuilder::from(&[\"aa\", \"bcbc\", \"defdefdef\"])\n    .with_conversion_of_repetitions()\n    .build();\nassert_eq!(regexp, \"^(?:a{2}|(?:bc){2}|(?:def){3})$\");\n```\n\nBy default, *grex* converts each substring this way which is at least a single character long \nand which is subsequently repeated at least once. You can customize these two parameters if you like.\n\nIn the following example, the test case `aa` is not converted to `a{2}` because the repeated substring \n`a` has a length of 1, but the minimum substring length has been set to 2.\n\n```rust\nuse grex::RegExpBuilder;\n\nlet regexp = RegExpBuilder::from(&[\"aa\", \"bcbc\", \"defdefdef\"])\n    .with_conversion_of_repetitions()\n    .with_minimum_substring_length(2)\n    .build();\nassert_eq!(regexp, \"^(?:aa|(?:bc){2}|(?:def){3})$\");\n```\n\nSetting a minimum number of 2 repetitions in the next example, only the test case `defdefdef` will be\nconverted because it is the only one that is repeated twice.\n\n```rust\nuse grex::RegExpBuilder;\n\nlet regexp = RegExpBuilder::from(&[\"aa\", \"bcbc\", \"defdefdef\"])\n    .with_conversion_of_repetitions()\n    .with_minimum_repetitions(2)\n    .build();\nassert_eq!(regexp, \"^(?:bcbc|aa|(?:def){3})$\");\n```\n\n#### 5.2.4 Escape non-ascii characters\n\n```rust\nuse grex::RegExpBuilder;\n\nlet regexp = RegExpBuilder::from(&[\"You smell like 💩.\"])\n    .with_escaping_of_non_ascii_chars(false)\n    .build();\nassert_eq!(regexp, \"^You smell like \\\\u{1f4a9}\\\\.$\");\n```\n\nOld versions of JavaScript do not support unicode escape sequences for the astral code planes \n(range `U+010000` to `U+10FFFF`). In order to support these symbols in JavaScript regular \nexpressions, the conversion to surrogate pairs is necessary. More information on that matter \ncan be found [here](https://mathiasbynens.be/notes/javascript-unicode).\n\n```rust\nuse grex::RegExpBuilder;\n\nlet regexp = RegExpBuilder::from(&[\"You smell like 💩.\"])\n    .with_escaped_non_ascii_chars(true)\n    .build();\nassert_eq!(regexp, \"^You smell like \\\\u{d83d}\\\\u{dca9}\\\\.$\");\n```\n\n#### 5.2.5 Case-insensitive matching\n\nThe regular expressions that *grex* generates are case-sensitive by default.\nCase-insensitive matching can be enabled like so:\n\n```rust\nuse grex::RegExpBuilder;\n\nlet regexp = RegExpBuilder::from(&[\"big\", \"BIGGER\"])\n    .with_case_insensitive_matching()\n    .build();\nassert_eq!(regexp, \"(?i)^big(?:ger)?$\");\n```\n\n#### 5.2.6 Capturing Groups\n\nNon-capturing groups are used by default. \nExtending the previous example, you can switch to capturing groups instead.\n\n```rust\nuse grex::RegExpBuilder;\n\nlet regexp = RegExpBuilder::from(&[\"big\", \"BIGGER\"])\n    .with_case_insensitive_matching()\n    .with_capturing_groups()\n    .build();\nassert_eq!(regexp, \"(?i)^big(ger)?$\");\n```\n\n#### 5.2.7 Verbose mode\n\nIf you find the generated regular expression hard to read, you can enable verbose mode.\nThe expression is then put on multiple lines and indented to make it more pleasant to the eyes.\n\n```rust\nuse grex::RegExpBuilder;\nuse indoc::indoc;\n\nlet regexp = RegExpBuilder::from(&[\"a\", \"b\", \"bcd\"])\n    .with_verbose_mode()\n    .build();\n\nassert_eq!(regexp, indoc!(\n    r#\"\n    (?x)\n    ^\n      (?:\n        b\n        (?:\n          cd\n        )?\n        |\n        a\n      )\n    $\"#\n));\n```\n\n#### 5.2.8 Disable anchors\n\nBy default, the anchors `^` and `$` are put around every generated regular expression in order\nto ensure that it matches only the test cases given as input. Often enough, however, it is\ndesired to use the generated pattern as part of a larger one. For this purpose, the anchors\ncan be disabled, either separately or both of them.\n\n```rust\nuse grex::RegExpBuilder;\n\nlet regexp = RegExpBuilder::from(&[\"a\", \"aa\", \"aaa\"])\n    .without_anchors()\n    .build();\nassert_eq!(regexp, \"a(?:aa?)?\");\n```\n\n### 5.3 Examples\n\nThe following examples show the various supported regex syntax features:\n\n```shell\n$ grex a b c\n^[a-c]$\n\n$ grex a c d e f\n^[ac-f]$\n\n$ grex a b x de\n^(?:de|[abx])$\n\n$ grex abc bc\n^a?bc$\n\n$ grex a b bc\n^(?:bc?|a)$\n\n$ grex [a-z]\n^\\[a\\-z\\]$\n\n$ grex -r b ba baa baaa\n^b(?:a{1,3})?$\n\n$ grex -r b ba baa baaaa\n^b(?:a{1,2}|a{4})?$\n\n$ grex y̆ a z\n^(?:y̆|[az])$\nNote: \nGrapheme y̆ consists of two Unicode symbols:\nU+0079 (Latin Small Letter Y)\nU+0306 (Combining Breve)\n\n$ grex \"I ♥ cake\" \"I ♥ cookies\"\n^I ♥ c(?:ookies|ake)$\nNote:\nInput containing blank space must be \nsurrounded by quotation marks.\n```\n\nThe string `\"I ♥♥♥ 36 and ٣ and 💩💩.\"` serves as input for the following examples using the command-line notation:\n\n```shell\n$ grex <INPUT>\n^I ♥♥♥ 36 and ٣ and 💩💩\\.$\n\n$ grex -e <INPUT>\n^I \\u{2665}\\u{2665}\\u{2665} 36 and \\u{663} and \\u{1f4a9}\\u{1f4a9}\\.$\n\n$ grex -e --with-surrogates <INPUT>\n^I \\u{2665}\\u{2665}\\u{2665} 36 and \\u{663} and \\u{d83d}\\u{dca9}\\u{d83d}\\u{dca9}\\.$\n\n$ grex -d <INPUT>\n^I ♥♥♥ \\d\\d and \\d and 💩💩\\.$\n\n$ grex -s <INPUT>\n^I\\s♥♥♥\\s36\\sand\\s٣\\sand\\s💩💩\\.$\n\n$ grex -w <INPUT>\n^\\w ♥♥♥ \\w\\w \\w\\w\\w \\w \\w\\w\\w 💩💩\\.$\n\n$ grex -D <INPUT>\n^\\D\\D\\D\\D\\D\\D36\\D\\D\\D\\D\\D٣\\D\\D\\D\\D\\D\\D\\D\\D$\n\n$ grex -S <INPUT>\n^\\S \\S\\S\\S \\S\\S \\S\\S\\S \\S \\S\\S\\S \\S\\S\\S$\n\n$ grex -dsw <INPUT>\n^\\w\\s♥♥♥\\s\\d\\d\\s\\w\\w\\w\\s\\d\\s\\w\\w\\w\\s💩💩\\.$\n\n$ grex -dswW <INPUT>\n^\\w\\s\\W\\W\\W\\s\\d\\d\\s\\w\\w\\w\\s\\d\\s\\w\\w\\w\\s\\W\\W\\W$\n\n$ grex -r <INPUT>\n^I ♥{3} 36 and ٣ and 💩{2}\\.$\n\n$ grex -er <INPUT>\n^I \\u{2665}{3} 36 and \\u{663} and \\u{1f4a9}{2}\\.$\n\n$ grex -er --with-surrogates <INPUT>\n^I \\u{2665}{3} 36 and \\u{663} and (?:\\u{d83d}\\u{dca9}){2}\\.$\n\n$ grex -dgr <INPUT>\n^I ♥{3} \\d(\\d and ){2}💩{2}\\.$\n\n$ grex -rs <INPUT>\n^I\\s♥{3}\\s36\\sand\\s٣\\sand\\s💩{2}\\.$\n\n$ grex -rw <INPUT>\n^\\w ♥{3} \\w(?:\\w \\w{3} ){2}💩{2}\\.$\n\n$ grex -Dr <INPUT>\n^\\D{6}36\\D{5}٣\\D{8}$\n\n$ grex -rS <INPUT>\n^\\S \\S(?:\\S{2} ){2}\\S{3} \\S \\S{3} \\S{3}$\n\n$ grex -rW <INPUT>\n^I\\W{5}36\\Wand\\W٣\\Wand\\W{4}$\n\n$ grex -drsw <INPUT>\n^\\w\\s♥{3}\\s\\d(?:\\d\\s\\w{3}\\s){2}💩{2}\\.$\n\n$ grex -drswW <INPUT>\n^\\w\\s\\W{3}\\s\\d(?:\\d\\s\\w{3}\\s){2}\\W{3}$\n```                                                                                                                            \n\n## 6. How to build?\n\nIn order to build the source code yourself, you need the \n[stable Rust toolchain](https://www.rust-lang.org/tools/install) installed on your machine \nso that [*cargo*](https://doc.rust-lang.org/cargo/), the Rust package manager is available.\n**Please note**: Rust >= 1.70.0 is required to build the CLI. For the library part, Rust < 1.70.0 is sufficient.\n\n```shell\ngit clone https://github.com/pemistahl/grex.git\ncd grex\ncargo build\n```\n\nThe source code is accompanied by an extensive test suite consisting of unit tests, integration \ntests and property tests. For running them, simply say:\n\n```shell\ncargo test\n```\n\nBenchmarks measuring the performance of several settings can be run with:\n\n```shell\ncargo bench\n```\n\n## 7. Python extension module\n\nWith the help of [PyO3](https://github.com/PyO3/pyo3) and\n[Maturin](https://github.com/PyO3/maturin), the library has been compiled to a\nPython extension module so that it can be used within any Python software as well.\nIt is available in the [Python Package Index](https://pypi.org/project/grex) and can \nbe installed with:\n\n```shell\npip install grex\n```\n\nTo build the Python extension module yourself, create a virtual environment and install \n[Maturin](https://github.com/PyO3/maturin).\n\n```shell\npython -m venv /path/to/virtual/environment\nsource /path/to/virtual/environment/bin/activate\npip install maturin\nmaturin build\n```\n\nThe Python library contains a single class named `RegExpBuilder` that can be imported like so:\n\n```python\nfrom grex import RegExpBuilder\n```\n\n## 8. WebAssembly support\n\nThis library can be compiled to [WebAssembly (WASM)](https://webassembly.org) which allows to use *grex*\nin any JavaScript-based project, be it in the browser or in the back end running on [Node.js](https://nodejs.org).\n\nThe easiest way to compile is to use [`wasm-pack`](https://rustwasm.github.io/wasm-pack). After the installation,\nyou can, for instance, build the library with the web target so that it can be directly used in the browser:\n\n    wasm-pack build --target web\n\nThis creates a directory named `pkg` on the top-level of this repository, containing the compiled wasm files\nand JavaScript and TypeScript bindings. In an HTML file, you can then call *grex* like the following, for instance:\n\n```html\n<script type=\"module\">\n    import init, { RegExpBuilder } from \"./pkg/grex.js\";\n\n    init().then(_ => {\n        alert(RegExpBuilder.from([\"hello\", \"world\"]).build());\n    });\n</script>\n```\n\nThere are also some integration tests available both for Node.js and for the browsers Chrome, Firefox and Safari.\nTo run them, simply say:\n\n    wasm-pack test --node --headless --chrome --firefox --safari\n\nIf the tests fail to start in Safari, you need to enable Safari's web driver first by running:\n\n    sudo safaridriver --enable\n\nThe output of `wasm-pack` will be hosted in a [separate repository](https://github.com/pemistahl/grex-js) which\nallows to add further JavaScript-related configuration, tests and documentation. *grex* will then be added to the\n[npm registry](https://www.npmjs.com) as well, allowing for an easy download and installation within every JavaScript\nor TypeScript project.\n\nThere is a [demo website](https://pemistahl.github.io/grex-js/) available where you can give grex a try.\n\n![demo website](https://raw.githubusercontent.com/pemistahl/grex/main/website.jpg)\n\n## 9. How does it work?\n\n1. A [deterministic finite automaton](https://en.wikipedia.org/wiki/Deterministic_finite_automaton) (DFA) \nis created from the input strings.\n\n2. The number of states and transitions between states in the DFA is reduced by applying \n[Hopcroft's DFA minimization algorithm](https://en.wikipedia.org/wiki/DFA_minimization#Hopcroft.27s_algorithm).\n\n3. The minimized DFA is expressed as a system of linear equations which are solved with \n[Brzozowski's algebraic method](http://cs.stackexchange.com/questions/2016/how-to-convert-finite-automata-to-regular-expressions#2392), \nresulting in the final regular expression.\n\n## 10. What's next for version 1.5.0?\n\nTake a look at the [planned issues](https://github.com/pemistahl/grex/milestone/5).\n\n## 11. Contributions\n\nIn case you want to contribute something to *grex*, I encourage you to do so.\nDo you have ideas for cool features? Or have you found any bugs so far? \nFeel free to open an issue or send a pull request. It's very much appreciated. :-)\n"
  },
  {
    "path": "README_PYPI.md",
    "content": "<div align=\"center\">\n\n![grex](https://raw.githubusercontent.com/pemistahl/grex/main/logo.png)\n\n<br>\n\n[![build status](https://github.com/pemistahl/grex/actions/workflows/python-build.yml/badge.svg)](https://github.com/pemistahl/grex/actions/workflows/python-build.yml)\n[![codecov](https://codecov.io/gh/pemistahl/grex/branch/main/graph/badge.svg)](https://codecov.io/gh/pemistahl/grex)\n[![demo](https://img.shields.io/badge/-Demo%20Website-orange?logo=HTML5&labelColor=white)](https://pemistahl.github.io/grex-js/)\n![supported Python versions](https://img.shields.io/badge/Python-%3E%3D%203.12-blue?logo=Python&logoColor=yellow)\n[![pypi](https://img.shields.io/badge/PYPI-v1.0.2-blue?logo=PyPI&logoColor=yellow)](https://pypi.org/project/grex)\n[![license](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)\n</div>\n\n<br>\n\n## 1. What does this library do?\n\n*grex* is a library that is meant to simplify the often complicated and tedious\ntask of creating regular expressions. It does so by automatically generating a\nsingle regular expression from user-provided test cases. The resulting\nexpression is guaranteed to match the test cases which it was generated from.\n\nThis project has started as a [Rust port](https://github.com/pemistahl/grex) of\nthe JavaScript tool [*regexgen*](https://github.com/devongovett/regexgen)\nwritten by [Devon Govett](https://github.com/devongovett). Although a lot of\nfurther useful features could be added to it, its development was apparently\nceased several years ago. The Rust library offers new features and extended\nUnicode support. With the help of [PyO3](https://github.com/PyO3/pyo3) and \n[Maturin](https://github.com/PyO3/maturin), the library has been compiled to a \nPython extension module so that it can be used within any Python software as well.\n\nThe philosophy of this project is to generate the most specific regular expression\npossible by default which exactly matches the given input only and nothing else.\nWith the use of preprocessing methods, more generalized expressions can be created.\n\nThe produced expressions are [Perl-compatible regular expressions](https://www.pcre.org) which are also\ncompatible with the [regular expression module](https://docs.python.org/3/library/re.html) in Python's \nstandard library.\n\nThere is a [demo website](https://pemistahl.github.io/grex-js/) available where you can give grex a try.\n\n![demo website](https://raw.githubusercontent.com/pemistahl/grex/main/website.jpg)\n\n## 2. Do I still need to learn to write regexes then?\n\n**Definitely, yes!** Using the standard settings, *grex* produces a regular expression that is guaranteed\nto match only the test cases given as input and nothing else. However, if the conversion to shorthand\ncharacter classes such as `\\w` is enabled, the resulting regex matches a much wider scope of test cases.\nKnowledge about the consequences of this conversion is essential for finding a correct regular expression\nfor your business domain.\n\n*grex* uses an algorithm that tries to find the shortest possible regex for the given test cases.\nVery often though, the resulting expression is still longer or more complex than it needs to be.\nIn such cases, a more compact or elegant regex can be created only by hand.\nAlso, every regular expression engine has different built-in optimizations. *grex* does not know anything\nabout those and therefore cannot optimize its regexes for a specific engine.\n\n**So, please learn how to write regular expressions!** The currently best use case for *grex* is to find\nan initial correct regex which should be inspected by hand if further optimizations are possible.\n\n## 3. Current Features\n\n- literals\n- character classes\n- detection of common prefixes and suffixes\n- detection of repeated substrings and conversion to `{min,max}` quantifier notation\n- alternation using `|` operator\n- optionality using `?` quantifier\n- escaping of non-ascii characters, with optional conversion of astral code points to surrogate pairs\n- case-sensitive or case-insensitive matching\n- capturing or non-capturing groups\n- optional anchors `^` and `$`\n- fully compliant to [Unicode Standard 16.0](https://unicode.org/versions/Unicode16.0.0)\n- correctly handles graphemes consisting of multiple Unicode symbols\n- produces more readable expressions indented on multiple using optional verbose mode\n- optional syntax highlighting for nicer output in supported terminals\n\n## 4. How to install?\n\n*grex* is available in the [Python Package Index](https://pypi.org/project/grex) and can be installed with:\n\n```\npip install grex\n```\n\nThe current version 1.0.2 corresponds to the latest version 1.4.6 of the Rust\nlibrary and command-line tool.\n\n## 5. How to use?\n\nThis library contains a single class named `RegExpBuilder` that can be imported like so:\n\n```python\nfrom grex import RegExpBuilder\n```\n\n### 5.1 Default settings\n\n```python\npattern = RegExpBuilder.from_test_cases([\"a\", \"aa\", \"aaa\"]).build()\nassert pattern == \"^a(?:aa?)?$\"\n```\n\n### 5.2 Convert to character classes\n\n```python\npattern = (RegExpBuilder.from_test_cases([\"a\", \"aa\", \"123\"])\n    .with_conversion_of_digits()\n    .with_conversion_of_words()\n    .build())\nassert pattern == \"^(?:\\\\d\\\\d\\\\d|\\\\w(?:\\\\w)?)$\"\n```\n\n### 5.3 Convert repeated substrings\n\n```python\npattern = (RegExpBuilder.from_test_cases([\"aa\", \"bcbc\", \"defdefdef\"])\n    .with_conversion_of_repetitions()\n    .build())\nassert pattern == \"^(?:a{2}|(?:bc){2}|(?:def){3})$\"\n```\n\nBy default, *grex* converts each substring this way which is at least a single character long\nand which is subsequently repeated at least once. You can customize these two parameters if you like.\n\nIn the following example, the test case `aa` is not converted to `a{2}` because the repeated substring\n`a` has a length of 1, but the minimum substring length has been set to 2.\n\n```python\npattern = (RegExpBuilder.from_test_cases([\"aa\", \"bcbc\", \"defdefdef\"])\n    .with_conversion_of_repetitions()\n    .with_minimum_substring_length(2)\n    .build())\nassert pattern == \"^(?:aa|(?:bc){2}|(?:def){3})$\"\n```\n\nSetting a minimum number of 2 repetitions in the next example, only the test case `defdefdef` will be\nconverted because it is the only one that is repeated twice.\n\n```python\npattern = (RegExpBuilder.from_test_cases([\"aa\", \"bcbc\", \"defdefdef\"])\n    .with_conversion_of_repetitions()\n    .with_minimum_repetitions(2)\n    .build())\nassert pattern == \"^(?:bcbc|aa|(?:def){3})$\"\n```\n\n### 5.4 Escape non-ascii characters\n\n```python\npattern = (RegExpBuilder.from_test_cases([\"You smell like 💩.\"])\n    .with_escaping_of_non_ascii_chars(use_surrogate_pairs=False)\n    .build())\nassert pattern == \"^You smell like \\\\U0001f4a9\\\\.$\"\n```\n\nOld versions of JavaScript do not support unicode escape sequences for the astral code planes\n(range `U+010000` to `U+10FFFF`). In order to support these symbols in JavaScript regular\nexpressions, the conversion to surrogate pairs is necessary. More information on that matter\ncan be found [here](https://mathiasbynens.be/notes/javascript-unicode).\n\n```python\npattern = (RegExpBuilder.from_test_cases([\"You smell like 💩.\"])\n    .with_escaping_of_non_ascii_chars(use_surrogate_pairs=True)\n    .build())\nassert pattern == \"^You smell like \\\\ud83d\\\\udca9\\\\.$\"\n```\n\n### 5.5 Case-insensitive matching\n\nThe regular expressions that *grex* generates are case-sensitive by default.\nCase-insensitive matching can be enabled like so:\n\n```python\npattern = (RegExpBuilder.from_test_cases([\"big\", \"BIGGER\"])\n    .with_case_insensitive_matching()\n    .build())\nassert pattern == \"(?i)^big(?:ger)?$\"\n```\n\n### 5.6 Capturing Groups\n\nNon-capturing groups are used by default.\nExtending the previous example, you can switch to capturing groups instead.\n\n```python\npattern = (RegExpBuilder.from_test_cases([\"big\", \"BIGGER\"])\n    .with_case_insensitive_matching()\n    .with_capturing_groups()\n    .build())\nassert pattern == \"(?i)^big(ger)?$\"\n```\n\n### 5.7 Verbose mode\n\nIf you find the generated regular expression hard to read, you can enable verbose mode.\nThe expression is then put on multiple lines and indented to make it more pleasant to the eyes.\n\n```python\nimport inspect\n\npattern = (RegExpBuilder.from_test_cases([\"a\", \"b\", \"bcd\"])\n    .with_verbose_mode()\n    .build())\n\nassert pattern == inspect.cleandoc(\"\"\"\n    (?x)\n    ^\n      (?:\n        b\n        (?:\n          cd\n        )?\n        |\n        a\n      )\n    $\n    \"\"\"\n)\n```\n\n### 5.8 Disable anchors\n\nBy default, the anchors `^` and `$` are put around every generated regular expression in order\nto ensure that it matches only the test cases given as input. Often enough, however, it is\ndesired to use the generated pattern as part of a larger one. For this purpose, the anchors\ncan be disabled, either separately or both of them.\n\n```python\npattern = (RegExpBuilder.from_test_cases([\"a\", \"aa\", \"aaa\"])\n    .without_anchors()\n    .build())\nassert pattern == \"a(?:aa?)?\"\n```\n\n## 6. How to build?\n\nIn order to build the source code yourself, you need the\n[stable Rust toolchain](https://www.rust-lang.org/tools/install) installed on your machine\nso that [*cargo*](https://doc.rust-lang.org/cargo/), the Rust package manager is available.\n\n```shell\ngit clone https://github.com/pemistahl/grex.git\ncd grex\ncargo build\n```\n\nTo build the Python extension module, create a virtual environment and install [Maturin](https://github.com/PyO3/maturin).\n\n```shell\npython -m venv /path/to/virtual/environment\nsource /path/to/virtual/environment/bin/activate\npip install maturin\nmaturin build\n```\n\nThe Rust source code is accompanied by an extensive test suite consisting of unit tests, integration\ntests and property tests. For running them, simply say:\n\n```shell\ncargo test\n```\n\nAdditional Python tests can be run after installing pytest which is an optional dependency:\n\n```shell\nmaturin develop --extras=test\npytest tests/python/test_grex.py\n```\n"
  },
  {
    "path": "RELEASE_NOTES.md",
    "content": "## grex 1.4.6 (released on 14 Nov 2025)\n\n### Improvements\n- All characters from the current Unicode standard 16.0 are now fully supported.\n\n### Changes\n\n- The unmaintained unic-* dependencies have been replaced by @jqnatividad. (#337)\n- All other dependencies have been updated to their latest versions.\n- Support for Python 3.14 has been added.\n- Support for Python < 3.12 has been dropped.\n\n## grex 1.4.5 (released on 06 Mar 2024)\n\n### Improvements\n\n- Type stubs for the Python bindings are now available, allowing better static code \n  analysis, better code completion in supported IDEs and easier understanding of the library's API.\n- The code for creating regular expressions in verbose mode has been simplified and is more performant now.\n- ARM64 binaries are now provided for every major platform (Linux, macOs, Windows).\n\n### Bug Fixes\n\n- For a small set of special characters, *grex* produced incorrect regular expressions when\n  the case-insensitivity feature was enabled. This has been fixed.\n\n### Changes\n- All dependencies have been updated to their latest versions.\n\n## grex 1.4.4 (released on 24 Aug 2023)\n\n### Bug Fixes\n- The Python release workflow was incorrect as it produced too many wheels for upload.\n  This has been fixed.\n\n## grex 1.4.3 (released on 24 Aug 2023)\n\n### Features\n- Python bindings are now available for the library. Use grex within any Python software. (#172)\n\n### Changes\n- All dependencies have been updated to their latest versions.\n\n## grex 1.4.2 (released on 26 Jul 2023)\n\n### Improvements\n- All characters from the current Unicode standard 15.0 are now fully supported. (#128)\n- A proper exit code is now returned if the provided user input cannot be handled by the CLI.\n  Big thanks to @spenserblack for the respective pull request. (#165)\n\n### Changes\n- It is not possible anymore to call `RegExpBuilder.with_syntax_highlighting()` in the library\n  as it only makes sense for the CLI.\n- The dependency `atty` has been removed in favor of `std::io::IsTerminal` in Rust >= 1.70.0.\n  As a result, Rust >= 1.70.0 is now needed to compile the CLI. \n- All remaining dependencies have been updated to their latest versions.\n\n### Bug Fixes\n- Several bugs have been fixed that caused incorrect expressions to be generated in rare cases.\n\n## grex 1.4.1 (released on 21 Oct 2022)\n\n### Changes\n- `clap` has been updated to version 4.0. The help output by `grex -h` now looks a little different.\n\n### Bug Fixes\n- A bug in the grapheme segmentation was fixed that caused test cases which contain backslashes to produce\n  incorrect regular expressions.\n\n## grex 1.4.0 (released on 26 Jul 2022)\n\n### Features\n- The library can now be compiled to WebAssembly and be used in any JavaScript project. (#82)\n- The supported character set for regular expression generation has been updated to the current Unicode Standard 14.0.\n- `structopt` has been replaced with `clap` providing much nicer help output for the command-line tool.\n\n### Improvements\n- The regular expression generation performance has been significantly improved, especially for generating very long\n  expressions from a large set of test cases. This has been accomplished by reducing the number of memory allocations,\n  removing deprecated code and applying several minor optimizations.\n\n### Bug Fixes\n- Several bugs have been fixed that caused incorrect expressions to be generated in rare cases.\n\n## grex 1.3.0 (released on 15 Sep 2021)\n\n### Features\n- anchors can now be disabled so that the generated expression can be used as part of a larger one (#30)\n- the command-line tool can now be used within Unix pipelines (#45)\n\n### Changes\n- Additional methods have been added to `RegExpBuilder` in order to replace the enum `Feature` and make the library API more consistent. (#47)\n\n### Bug Fixes\n- Under rare circumstances, the conversion of repetitions did not work. This has been fixed. (#36)\n\n## grex 1.2.0 (released on 28 Mar 2021)\n\n### Features\n- verbose mode is now supported with the `--verbose` flag to produce regular expressions which are easier to read (#17)\n\n## grex 1.1.0 (released on 17 Apr 2020)\n\n### Features\n- case-insensitive matching regexes are now supported with the `--ignore-case` command-line flag or with `Feature::CaseInsensitivity` in the library (#23)\n- non-capturing groups are now the default; capturing groups can be enabled with the `--capture-groups` command-line flag or with `Feature::CapturingGroup` in the library (#15)\n- a lower bound for the conversion of repeated substrings can now be set by specifying `--min-repetitions` and `--min-substring-length` or using the library methods `RegExpBuilder.with_minimum_repetitions()` and `RegExpBuilder.with_minimum_substring_length()` (#10)\n- test cases can now be passed from a file within the library as well using `RegExpBuilder::from_file()` (#13)\n\n### Changes\n\n- the rules for the conversion of test cases to shorthand character classes have been updated to be compliant to the newest Unicode Standard 13.0 (#21)\n- the dependency on the unmaintained linked-list crate has been removed (#24)\n\n### Bug Fixes\n\n- test cases starting with a hyphen are now correctly parsed on the command-line (#12)\n- the common substring detection algorithm now uses optionality expressions where possible instead of redundant union operations (#22)\n\n### Test Coverage\n- new unit tests, integration tests and property tests have been added\n\n## grex 1.0.0 (released on 02 Feb 2020)\n\n### Features\n- conversion to character classes `\\d`, `\\D`, `\\s`, `\\S`, `\\w`, `\\W` is now supported\n- repetition detection now works with arbitrarily nested expressions. Input strings such as `aaabaaab` which were previously converted to `^(aaab){2}$` are now converted to `^(a{3}b){2}$`.\n- optional syntax highlighting for the produced regular expressions can now be enabled using the `--colorize` command-line flag or with the library method `RegExpBuilder.with_syntax_highlighting()`\n\n### Test Coverage\n- new unit tests, integration tests and property tests have been added\n\n## grex 0.3.2 (released on 12 Jan 2020)\n\n### Test Coverage\n- new property tests have been added that revealed new bugs\n\n### Bug Fixes\n- entire rewrite of the repetition detection algorithm\n- the former algorithm produced wrong regular expressions or even panicked for certain test cases\n\n## grex 0.3.1 (released on 06 Jan 2020)\n\n### Test Coverage\n- property tests have been added using the [proptest](https://crates.io/crates/proptest) crate \n- big thanks go to [Christophe Biocca](https://github.com/christophebiocca) for pointing me to the concept of property tests in the first place and for writing an initial implementation of these tests\n\n### Bug Fixes\n- some regular expression specific characters were not escaped correctly in the generated expression\n- expressions consisting of a single alternation such as `^(abc|xyz)$` were missing the outer parentheses. This caused an erroneous match of strings such as `abc123` or `456xyz` because of precedence rules.\n- the created DFA was wrong for repetition conversion in some corner cases. The input `a, aa, aaa, aaaa, aaab` previously returned the expression `^a{1,4}b?$` which erroneously matches `aaaab`. Now the correct expression `^(a{3}b|a{1,4})$` is returned.\n\n### Documentation\n- some minor documentation updates\n\n## grex 0.3.0 (released on 24 Dec 2019)\n\n### Features\n- *grex* is now also available as a library\n- escaping of non-ascii characters is now supported with the `-e` flag\n- astral code points can be converted to surrogate with the `--with-surrogates` flag\n- repeated non-overlapping substrings can be converted to `{min,max}` quantifier notation using the `-r` flag\n\n### Bug Fixes\n- many many many bug fixes :-O\n\n## grex 0.2.0 (released on 20 Oct 2019)\n\n### Features\n- character classes are now supported\n- input strings can now be read from a text file\n\n### Changes\n- unicode characters are not escaped anymore by default\n- the performance of the DFA minimization algorithm has been improved for large DFAs\n- regular expressions are now always surrounded by anchors `^` and `$`\n\n### Bug Fixes\n- fixed a bug that caused a panic when giving an empty string as input\n\n## grex 0.1.0 (released on 06 Oct 2019)\n\nThis is the very first release of *grex*. It aims at simplifying the construction of regular expressions based on matching example input.\n\n### Features\n- literals\n- detection of common prefixes and suffixes\n- alternation using `|` operator\n- optionality using `?` quantifier\n- concatenation of all of the former\n"
  },
  {
    "path": "benches/benchmark.rs",
    "content": "/*\n * Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse criterion::{criterion_group, criterion_main, Criterion};\nuse grex::RegExpBuilder;\nuse itertools::Itertools;\nuse std::fs::File;\nuse std::io::Read;\n\nfn load_test_cases() -> Vec<String> {\n    let mut f = File::open(\"./benches/testcases.txt\").expect(\"Test cases could not be loaded\");\n    let mut s = String::new();\n    f.read_to_string(&mut s).unwrap();\n    s.split(\"\\n\")\n        .map(|test_case| test_case.to_string())\n        .collect_vec()\n}\n\nfn benchmark_grex_with_default_settings(c: &mut Criterion) {\n    let test_cases = load_test_cases();\n    c.bench_function(\"grex with default settings\", |bencher| {\n        bencher.iter(|| RegExpBuilder::from(&test_cases).build())\n    });\n}\n\nfn benchmark_grex_with_conversion_of_repetitions(c: &mut Criterion) {\n    let test_cases = load_test_cases();\n    c.bench_function(\"grex with conversion of repetitions\", |bencher| {\n        bencher.iter(|| {\n            RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .build()\n        })\n    });\n}\n\nfn benchmark_grex_with_conversion_of_digits(c: &mut Criterion) {\n    let test_cases = load_test_cases();\n    c.bench_function(\"grex with conversion of digits\", |bencher| {\n        bencher.iter(|| {\n            RegExpBuilder::from(&test_cases)\n                .with_conversion_of_digits()\n                .build()\n        })\n    });\n}\n\nfn benchmark_grex_with_conversion_of_non_digits(c: &mut Criterion) {\n    let test_cases = load_test_cases();\n    c.bench_function(\"grex with conversion of non-digits\", |bencher| {\n        bencher.iter(|| {\n            RegExpBuilder::from(&test_cases)\n                .with_conversion_of_non_digits()\n                .build()\n        })\n    });\n}\n\nfn benchmark_grex_with_conversion_of_words(c: &mut Criterion) {\n    let test_cases = load_test_cases();\n    c.bench_function(\"grex with conversion of words\", |bencher| {\n        bencher.iter(|| {\n            RegExpBuilder::from(&test_cases)\n                .with_conversion_of_words()\n                .build()\n        })\n    });\n}\n\nfn benchmark_grex_with_conversion_of_non_words(c: &mut Criterion) {\n    let test_cases = load_test_cases();\n    c.bench_function(\"grex with conversion of non-words\", |bencher| {\n        bencher.iter(|| {\n            RegExpBuilder::from(&test_cases)\n                .with_conversion_of_non_words()\n                .build()\n        })\n    });\n}\n\nfn benchmark_grex_with_conversion_of_whitespace(c: &mut Criterion) {\n    let test_cases = load_test_cases();\n    c.bench_function(\"grex with conversion of whitespace\", |bencher| {\n        bencher.iter(|| {\n            RegExpBuilder::from(&test_cases)\n                .with_conversion_of_whitespace()\n                .build()\n        })\n    });\n}\n\nfn benchmark_grex_with_conversion_of_non_whitespace(c: &mut Criterion) {\n    let test_cases = load_test_cases();\n    c.bench_function(\"grex with conversion of non-whitespace\", |bencher| {\n        bencher.iter(|| {\n            RegExpBuilder::from(&test_cases)\n                .with_conversion_of_non_whitespace()\n                .build()\n        })\n    });\n}\n\nfn benchmark_grex_with_case_insensitive_matching(c: &mut Criterion) {\n    let test_cases = load_test_cases();\n    c.bench_function(\"grex with case-insensitive matching\", |bencher| {\n        bencher.iter(|| {\n            RegExpBuilder::from(&test_cases)\n                .with_case_insensitive_matching()\n                .build()\n        })\n    });\n}\n\nfn benchmark_grex_with_verbose_mode(c: &mut Criterion) {\n    let test_cases = load_test_cases();\n    c.bench_function(\"grex with verbose mode\", |bencher| {\n        bencher.iter(|| RegExpBuilder::from(&test_cases).with_verbose_mode().build())\n    });\n}\n\ncriterion_group!(\n    benches,\n    benchmark_grex_with_default_settings,\n    benchmark_grex_with_conversion_of_repetitions,\n    benchmark_grex_with_conversion_of_digits,\n    benchmark_grex_with_conversion_of_non_digits,\n    benchmark_grex_with_conversion_of_words,\n    benchmark_grex_with_conversion_of_non_words,\n    benchmark_grex_with_conversion_of_whitespace,\n    benchmark_grex_with_conversion_of_non_whitespace,\n    benchmark_grex_with_case_insensitive_matching,\n    benchmark_grex_with_verbose_mode\n);\n\ncriterion_main!(benches);\n"
  },
  {
    "path": "benches/testcases.txt",
    "content": "Rocket Sled\nElysian Heirloom\nKaleb's Favor\nBlazing Renegade\nFlash Fire\nSilence\nTalir's Favored\nTimekeeper\nOasis Sanctuary\nRolant's Favor\nMantle of Justice\nEilyn's Favor\nThunderbird\nPrimal Incarnation\nVampire Bat\nVara's Favor\nDevouring Shadow\nSeat of Order\nSeat of Fury\nSeat of Impulse\nSeat of Vengeance\nSeat of Glory\nSeat of Progress\nSeat of Chaos\nSeat of Mystery\nSeat of Cunning\nSeat of Wisdom\nFirebomb\nGrenadin\nIron Sword\nMagmahound\nWisp\nRhinarc\nSentinel\nOwl\nGemblade\nFrog\nSnowball\nPig\nSerpent Hatchling\nCarnosaur\nStormdancer\nIllusionary Dragon\nSpiteling\nVengeful Gargoyle\nMuertis, Pale Rider\nOcci, Pale Rider\nSangu, Pale Rider\nVolan, Pale Rider\nDirewood Beast\n"
  },
  {
    "path": "demo.tape",
    "content": "# demo.gif created with https://github.com/charmbracelet/vhs on macOS 13 (Ventura)\n\nRequire grex\nOutput demo.gif\n\nSet Shell zsh\nSet Theme \"Whimsy\"\nSet Width 1200\nSet Height 850\nSet TypingSpeed 150ms\n\nType \"grex -c 'regexes are awesome' 'regexes are awful'\"\nSleep 3s\nEnter\nSleep 10s\n\nUp\nLeft 42\nType \" --verbose\"\nSleep 3s\nEnter\nSleep 15s\nType \"clear\"\nEnter\n\nType \"grex -c haha HAHAHA\"\nSleep 3s\nEnter\nSleep 10s\n\nUp\nLeft 12\nType \" --repetitions\"\nSleep 3s\nEnter\nSleep 10s\n\nUp\nLeft 12\nType \" --verbose\"\nSleep 3s\nEnter\nSleep 15s\n\nUp\nLeft 12\nType \" --ignore-case\"\nSleep 3s\nEnter\nSleep 15s\n"
  },
  {
    "path": "grex.pyi",
    "content": "#\n# Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom typing import List\n\n\nclass RegExpBuilder:\n    \"\"\"This class builds regular expressions from user-provided test cases.\"\"\"\n\n    @classmethod\n    def from_test_cases(cls, test_cases: List[str]) -> \"RegExpBuilder\":\n        \"\"\"Specify the test cases to build the regular expression from.\n\n        The test cases need not be sorted because `RegExpBuilder` sorts them internally.\n\n        Args:\n            test_cases (list[str]): The list of test cases\n\n        Raises:\n            ValueError: if `test_cases` is empty\n        \"\"\"\n\n    def with_conversion_of_digits(self) -> \"RegExpBuilder\":\n        \"\"\"Convert any Unicode decimal digit to character class `\\d`.\n\n        This method takes precedence over `with_conversion_of_words` if both are set.\n        Decimal digits are converted to `\\d`, the remaining word characters to `\\w`.\n\n        This method takes precedence over `with_conversion_of_non_whitespace` if both are set.\n        Decimal digits are converted to `\\d`, the remaining non-whitespace characters to `\\S`.\n        \"\"\"\n\n    def with_conversion_of_non_digits(self) -> \"RegExpBuilder\":\n        \"\"\"Convert any character which is not a Unicode decimal digit to character class `\\D`.\n\n        This method takes precedence over `with_conversion_of_non_words` if both are set.\n        Non-digits which are also non-word characters are converted to `\\D`.\n\n        This method takes precedence over `with_conversion_of_non_whitespace` if both are set.\n        Non-digits which are also non-space characters are converted to `\\D`.\n        \"\"\"\n\n    def with_conversion_of_whitespace(self) -> \"RegExpBuilder\":\n        \"\"\"Convert any Unicode whitespace character to character class `\\s`.\n\n        This method takes precedence over `with_conversion_of_non_digits` if both are set.\n        Whitespace characters are converted to `\\s`, the remaining non-digit characters to `\\D`.\n\n        This method takes precedence over `with_conversion_of_non_words` if both are set.\n        Whitespace characters are converted to `\\s`, the remaining non-word characters to `\\W`.\n        \"\"\"\n\n    def with_conversion_of_non_whitespace(self) -> \"RegExpBuilder\":\n        \"\"\"Convert any character which is not a Unicode whitespace character to character class `\\S`.\"\"\"\n\n    def with_conversion_of_words(self) -> \"RegExpBuilder\":\n        \"\"\"Convert any Unicode word character to character class `\\w`.\n\n        This method takes precedence over `with_conversion_of_non_digits` if both are set.\n        Word characters are converted to `\\w`, the remaining non-digit characters to `\\D`.\n\n        This method takes precedence over `with_conversion_of_non_whitespace` if both are set.\n        Word characters are converted to `\\w`, the remaining non-space characters to `\\S`.\n        \"\"\"\n\n    def with_conversion_of_non_words(self) -> \"RegExpBuilder\":\n        \"\"\"Convert any character which is not a Unicode word character to character class `\\W`.\n\n        This method takes precedence over `with_conversion_of_non_whitespace` if both are set.\n        Non-words which are also non-space characters are converted to `\\W`.\n        \"\"\"\n\n    def with_conversion_of_repetitions(self) -> \"RegExpBuilder\":\n        \"\"\"Detect repeated non-overlapping substrings and to convert them to `{min,max}` quantifier notation.\"\"\"\n\n    def with_case_insensitive_matching(self) -> \"RegExpBuilder\":\n        \"\"\"Enable case-insensitive matching of test cases so that letters match both upper and lower case.\"\"\"\n\n    def with_capturing_groups(self) -> \"RegExpBuilder\":\n        \"\"\"Replace non-capturing groups with capturing ones.\"\"\"\n\n    def with_minimum_repetitions(self, quantity: int) -> \"RegExpBuilder\":\n        \"\"\"Specify the minimum quantity of substring repetitions to be converted\n        if `with_conversion_of_repetitions` is set.\n\n        If the quantity is not explicitly set with this method, a default value of 1 will be used.\n\n        Args:\n            quantity (int): The minimum quantity of substring repetitions\n\n        Raises:\n            ValueError: if `quantity` is zero\n        \"\"\"\n\n    def with_minimum_substring_length(self, length: int) -> \"RegExpBuilder\":\n        \"\"\"Specify the minimum length a repeated substring must have in order\n        to be converted if `with_conversion_of_repetitions` is set.\n\n        If the length is not explicitly set with this method, a default value of 1 will be used.\n\n        Args:\n            length (int): The minimum substring length\n\n        Raises:\n            ValueError: if `length` is zero\n        \"\"\"\n\n    def with_escaping_of_non_ascii_chars(self, use_surrogate_pairs: bool) -> \"RegExpBuilder\":\n        \"\"\"Convert non-ASCII characters to unicode escape sequences.\n\n        The parameter `use_surrogate_pairs` specifies whether to convert astral\n        code planes (range `U+010000` to `U+10FFFF`) to surrogate pairs.\n\n        Args:\n            use_surrogate_pairs (bool): Whether to convert astral code planes to surrogate pairs\n        \"\"\"\n\n    def with_verbose_mode(self) -> \"RegExpBuilder\":\n        \"\"\" Produce a nicer looking regular expression in verbose mode.\"\"\"\n\n    def without_start_anchor(self) -> \"RegExpBuilder\":\n        \"\"\"Remove the caret anchor '^' from the resulting regular expression,\n        thereby allowing to match the test cases also when they do not occur\n        at the start of a string.\n        \"\"\"\n\n    def without_end_anchor(self) -> \"RegExpBuilder\":\n        \"\"\"Remove the dollar sign anchor '$' from the resulting regular expression,\n        thereby allowing to match the test cases also when they do not occur\n        at the end of a string.\n        \"\"\"\n\n    def without_anchors(self) -> \"RegExpBuilder\":\n        \"\"\"Remove the caret and dollar sign anchors from the resulting regular expression,\n        thereby allowing to match the test cases also when they occur within a larger\n        string that contains other content as well.\n        \"\"\"\n\n    def build(self) -> str:\n        \"\"\"Build the actual regular expression using the previously given settings.\"\"\"\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[project]\nname = \"grex\"\nversion = \"1.0.2\"\nauthors = [{name = \"Peter M. Stahl\", email = \"pemistahl@gmail.com\"}]\ndescription = \"grex generates regular expressions from user-provided test cases.\"\nreadme = \"README_PYPI.md\"\nrequires-python = \">=3.12\"\nlicense = {file = \"LICENSE\"}\nkeywords = [\"pattern\", \"regex\", \"regexp\"]\nclassifiers = [\n    \"Development Status :: 5 - Production/Stable\",\n    \"Intended Audience :: Developers\",\n    \"Intended Audience :: Information Technology\",\n    \"Intended Audience :: Science/Research\",\n    \"License :: OSI Approved :: Apache Software License\",\n    \"Programming Language :: Python :: 3.12\",\n    \"Programming Language :: Python :: 3.13\",\n    \"Programming Language :: Python :: 3.14\",\n    \"Programming Language :: Rust\",\n    \"Topic :: Software Development :: Libraries :: Python Modules\",\n    \"Topic :: Text Processing\"\n]\n\n[project.urls]\nhomepage = \"https://github.com/pemistahl/grex\"\nrepository = \"https://github.com/pemistahl/grex\"\n\n[project.optional-dependencies]\ntest = [\"pytest == 9.0.1\"]\n\n[tool.maturin]\nno-default-features = true\nfeatures = [\"pyo3/extension-module\", \"pyo3/generate-import-lib\", \"python\"]\n\n[build-system]\nrequires = [\"maturin>=1.1,<2.0\"]\nbuild-backend = \"maturin\"\n\n"
  },
  {
    "path": "requirements.txt",
    "content": "maturin == 1.10.1\npytest == 9.0.1\n"
  },
  {
    "path": "src/builder.rs",
    "content": "/*\n * Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse crate::config::RegExpConfig;\nuse crate::regexp::RegExp;\nuse itertools::Itertools;\nuse std::io::ErrorKind;\nuse std::path::PathBuf;\n\npub(crate) const MISSING_TEST_CASES_MESSAGE: &str =\n    \"No test cases have been provided for regular expression generation\";\n\npub(crate) const MINIMUM_REPETITIONS_MESSAGE: &str =\n    \"Quantity of minimum repetitions must be greater than zero\";\n\npub(crate) const MINIMUM_SUBSTRING_LENGTH_MESSAGE: &str =\n    \"Minimum substring length must be greater than zero\";\n\n/// This struct builds regular expressions from user-provided test cases.\n#[derive(Clone)]\n#[cfg_attr(feature = \"python\", pyo3::prelude::pyclass)]\npub struct RegExpBuilder {\n    pub(crate) test_cases: Vec<String>,\n    pub(crate) config: RegExpConfig,\n}\n\nimpl RegExpBuilder {\n    /// Specifies the test cases to build the regular expression from.\n    ///\n    /// The test cases need not be sorted because `RegExpBuilder` sorts them internally.\n    ///\n    /// ⚠ Panics if `test_cases` is empty.\n    pub fn from<T: Clone + Into<String>>(test_cases: &[T]) -> Self {\n        if test_cases.is_empty() {\n            panic!(\"{}\", MISSING_TEST_CASES_MESSAGE);\n        }\n        Self {\n            test_cases: test_cases.iter().cloned().map(|it| it.into()).collect_vec(),\n            config: RegExpConfig::new(),\n        }\n    }\n\n    /// Specifies a text file containing test cases to build the regular expression from.\n    ///\n    /// The test cases need not be sorted because `RegExpBuilder` sorts them internally.\n    ///\n    /// Each test case needs to be on a separate line.\n    /// Lines may be ended with either a newline (`\\n`) or\n    /// a carriage return with a line feed (`\\r\\n`).\n    /// The final line ending is optional.\n    ///\n    /// ⚠ Panics if:\n    /// - the file cannot be found\n    /// - the file's encoding is not valid UTF-8 data\n    /// - the file cannot be opened because of conflicting permissions\n    pub fn from_file<T: Into<PathBuf>>(file_path: T) -> Self {\n        match std::fs::read_to_string(file_path.into()) {\n            Ok(file_content) => Self {\n                test_cases: file_content.lines().map(|it| it.to_string()).collect_vec(),\n                config: RegExpConfig::new(),\n            },\n            Err(error) => match error.kind() {\n                ErrorKind::NotFound => panic!(\"The specified file could not be found\"),\n                ErrorKind::InvalidData => {\n                    panic!(\"The specified file's encoding is not valid UTF-8\")\n                }\n                ErrorKind::PermissionDenied => {\n                    panic!(\"Permission denied: The specified file could not be opened\")\n                }\n                _ => panic!(\"{}\", error),\n            },\n        }\n    }\n\n    /// Converts any Unicode decimal digit to character class `\\d`.\n    ///\n    /// This method takes precedence over\n    /// [`with_conversion_of_words`](Self::with_conversion_of_words) if both are set.\n    /// Decimal digits are converted to `\\d`, the remaining word characters to `\\w`.\n    ///\n    /// This method takes precedence over\n    /// [`with_conversion_of_non_whitespace`](Self::with_conversion_of_non_whitespace) if both are set.\n    /// Decimal digits are converted to `\\d`, the remaining non-whitespace characters to `\\S`.\n    pub fn with_conversion_of_digits(&mut self) -> &mut Self {\n        self.config.is_digit_converted = true;\n        self\n    }\n\n    /// Converts any character which is not a Unicode decimal digit to character class `\\D`.\n    ///\n    /// This method takes precedence over\n    /// [`with_conversion_of_non_words`](Self::with_conversion_of_non_words) if both are set.\n    /// Non-digits which are also non-word characters are converted to `\\D`.\n    ///\n    /// This method takes precedence over\n    /// [`with_conversion_of_non_whitespace`](Self::with_conversion_of_non_whitespace) if both are set.\n    /// Non-digits which are also non-space characters are converted to `\\D`.\n    pub fn with_conversion_of_non_digits(&mut self) -> &mut Self {\n        self.config.is_non_digit_converted = true;\n        self\n    }\n\n    /// Converts any Unicode whitespace character to character class `\\s`.\n    ///\n    /// This method takes precedence over\n    /// [`with_conversion_of_non_digits`](Self::with_conversion_of_non_digits) if both are set.\n    /// Whitespace characters are converted to `\\s`, the remaining non-digit characters to `\\D`.\n    ///\n    /// This method takes precedence over\n    /// [`with_conversion_of_non_words`](Self::with_conversion_of_non_words) if both are set.\n    /// Whitespace characters are converted to `\\s`, the remaining non-word characters to `\\W`.\n    pub fn with_conversion_of_whitespace(&mut self) -> &mut Self {\n        self.config.is_space_converted = true;\n        self\n    }\n\n    /// Converts any character which is not a Unicode whitespace character to character class `\\S`.\n    pub fn with_conversion_of_non_whitespace(&mut self) -> &mut Self {\n        self.config.is_non_space_converted = true;\n        self\n    }\n\n    /// Converts any Unicode word character to character class `\\w`.\n    ///\n    /// This method takes precedence over\n    /// [`with_conversion_of_non_digits`](Self::with_conversion_of_non_digits) if both are set.\n    /// Word characters are converted to `\\w`, the remaining non-digit characters to `\\D`.\n    ///\n    /// This method takes precedence over\n    /// [`with_conversion_of_non_whitespace`](Self::with_conversion_of_non_whitespace) if both are set.\n    /// Word characters are converted to `\\w`, the remaining non-space characters to `\\S`.\n    pub fn with_conversion_of_words(&mut self) -> &mut Self {\n        self.config.is_word_converted = true;\n        self\n    }\n\n    /// Converts any character which is not a Unicode word character to character class `\\W`.\n    ///\n    /// This method takes precedence over\n    /// [`with_conversion_of_non_whitespace`](Self::with_conversion_of_non_whitespace) if both are set.\n    /// Non-words which are also non-space characters are converted to `\\W`.\n    pub fn with_conversion_of_non_words(&mut self) -> &mut Self {\n        self.config.is_non_word_converted = true;\n        self\n    }\n\n    /// Detects repeated non-overlapping substrings and\n    /// to convert them to `{min,max}` quantifier notation.\n    pub fn with_conversion_of_repetitions(&mut self) -> &mut Self {\n        self.config.is_repetition_converted = true;\n        self\n    }\n\n    /// Enables case-insensitive matching of test cases\n    /// so that letters match both upper and lower case.\n    pub fn with_case_insensitive_matching(&mut self) -> &mut Self {\n        self.config.is_case_insensitive_matching = true;\n        self\n    }\n\n    /// Replaces non-capturing groups with capturing ones.\n    pub fn with_capturing_groups(&mut self) -> &mut Self {\n        self.config.is_capturing_group_enabled = true;\n        self\n    }\n\n    /// Specifies the minimum quantity of substring repetitions to be converted if\n    /// [`with_conversion_of_repetitions`](Self::with_conversion_of_repetitions) is set.\n    ///\n    /// If the quantity is not explicitly set with this method, a default value of 1 will be used.\n    ///\n    /// ⚠ Panics if `quantity` is zero.\n    pub fn with_minimum_repetitions(&mut self, quantity: u32) -> &mut Self {\n        if quantity == 0 {\n            panic!(\"{}\", MINIMUM_REPETITIONS_MESSAGE);\n        }\n        self.config.minimum_repetitions = quantity;\n        self\n    }\n\n    /// Specifies the minimum length a repeated substring must have in order to be converted if\n    /// [`with_conversion_of_repetitions`](Self::with_conversion_of_repetitions) is set.\n    ///\n    /// If the length is not explicitly set with this method, a default value of 1 will be used.\n    ///\n    /// ⚠ Panics if `length` is zero.\n    pub fn with_minimum_substring_length(&mut self, length: u32) -> &mut Self {\n        if length == 0 {\n            panic!(\"{}\", MINIMUM_SUBSTRING_LENGTH_MESSAGE);\n        }\n        self.config.minimum_substring_length = length;\n        self\n    }\n\n    /// Converts non-ASCII characters to unicode escape sequences.\n    /// The parameter `use_surrogate_pairs` specifies whether to convert astral code planes\n    /// (range `U+010000` to `U+10FFFF`) to surrogate pairs.\n    pub fn with_escaping_of_non_ascii_chars(&mut self, use_surrogate_pairs: bool) -> &mut Self {\n        self.config.is_non_ascii_char_escaped = true;\n        self.config.is_astral_code_point_converted_to_surrogate = use_surrogate_pairs;\n        self\n    }\n\n    /// Produces a nicer looking regular expression in verbose mode.\n    pub fn with_verbose_mode(&mut self) -> &mut Self {\n        self.config.is_verbose_mode_enabled = true;\n        self\n    }\n\n    /// Removes the caret anchor '^' from the resulting regular\n    /// expression, thereby allowing to match the test cases also when they do not occur\n    /// at the start of a string.\n    pub fn without_start_anchor(&mut self) -> &mut Self {\n        self.config.is_start_anchor_disabled = true;\n        self\n    }\n\n    /// Removes the dollar sign anchor '$' from the resulting regular\n    /// expression, thereby allowing to match the test cases also when they do not occur\n    /// at the end of a string.\n    pub fn without_end_anchor(&mut self) -> &mut Self {\n        self.config.is_end_anchor_disabled = true;\n        self\n    }\n\n    /// Removes the caret and dollar sign anchors from the resulting\n    /// regular expression, thereby allowing to match the test cases also when they occur\n    /// within a larger string that contains other content as well.\n    pub fn without_anchors(&mut self) -> &mut Self {\n        self.config.is_start_anchor_disabled = true;\n        self.config.is_end_anchor_disabled = true;\n        self\n    }\n\n    /// Provides syntax highlighting for the resulting regular expression.\n    ///\n    /// ⚠ This method may only be used if the resulting regular expression is meant to\n    /// be printed to the console. The regex string representation returned from enabling\n    /// this setting cannot be fed into the [*regex*](https://crates.io/crates/regex) crate.\n    #[cfg(feature = \"cli\")]\n    #[doc(hidden)]\n    pub fn with_syntax_highlighting(&mut self) -> &mut Self {\n        self.config.is_output_colorized = true;\n        self\n    }\n\n    /// Builds the actual regular expression using the previously given settings.\n    pub fn build(&mut self) -> String {\n        RegExp::from(&mut self.test_cases, &self.config).to_string()\n    }\n}\n"
  },
  {
    "path": "src/char_range.rs",
    "content": "/*\n * Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/// A lightweight replacement for unic_char_range::CharRange\n/// Represents a closed range of Unicode characters\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub(crate) struct CharRange {\n    start: char,\n    end: char,\n}\n\nimpl CharRange {\n    /// Creates a closed character range from start to end (inclusive)\n    pub(crate) fn closed(start: char, end: char) -> Self {\n        Self { start, end }\n    }\n\n    /// Checks if the given character is within this range\n    pub(crate) fn contains(&self, c: char) -> bool {\n        c >= self.start && c <= self.end\n    }\n\n    /// Returns an iterator over all valid Unicode scalar values\n    /// This includes U+0000 to U+D7FF and U+E000 to U+10FFFF\n    /// (excludes surrogate code points U+D800 to U+DFFF)\n    pub(crate) fn all() -> CharRangeIter {\n        CharRangeIter {\n            current: '\\0',\n            done: false,\n        }\n    }\n}\n\n/// Iterator over all valid Unicode scalar values\npub(crate) struct CharRangeIter {\n    current: char,\n    done: bool,\n}\n\nimpl Iterator for CharRangeIter {\n    type Item = char;\n\n    fn next(&mut self) -> Option<Self::Item> {\n        if self.done {\n            return None;\n        }\n\n        let result = self.current;\n\n        // Get the next valid Unicode scalar value\n        let mut next_code_point = self.current as u32 + 1;\n\n        // Skip over surrogate code points (U+D800 to U+DFFF) and find next valid char\n        loop {\n            if next_code_point > 0x10FFFF {\n                // We've reached the end of valid Unicode code points\n                self.done = true;\n                break;\n            }\n\n            match char::from_u32(next_code_point) {\n                Some(next_char) => {\n                    self.current = next_char;\n                    break;\n                }\n                None => {\n                    // Invalid code point (likely surrogate), skip to next\n                    next_code_point += 1;\n                }\n            }\n        }\n\n        Some(result)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_char_range_contains() {\n        let range = CharRange::closed('a', 'z');\n        assert!(range.contains('a'));\n        assert!(range.contains('m'));\n        assert!(range.contains('z'));\n        assert!(!range.contains('A'));\n        assert!(!range.contains('0'));\n    }\n\n    #[test]\n    fn test_char_range_all() {\n        let all_chars: Vec<char> = CharRange::all().take(10).collect();\n        assert_eq!(all_chars[0], '\\0');\n        assert_eq!(all_chars.len(), 10);\n    }\n\n    #[test]\n    fn test_char_range_all_count() {\n        // Valid Unicode scalar values: 0x110000 total code points - 0x800 surrogates = 0x10F800\n        let count = CharRange::all().count();\n        assert_eq!(count, 0x10F800);\n    }\n}\n"
  },
  {
    "path": "src/cluster.rs",
    "content": "/*\n * Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse crate::char_range::CharRange;\nuse crate::config::RegExpConfig;\nuse crate::grapheme::Grapheme;\nuse crate::unicode_tables::{DECIMAL_NUMBER, WHITE_SPACE, WORD};\nuse itertools::Itertools;\nuse std::cmp::Ordering;\nuse std::collections::HashMap;\nuse std::ops::Range;\nuse std::sync::LazyLock;\nuse unicode_general_category::GeneralCategory as GC;\nuse unicode_segmentation::UnicodeSegmentation;\n\n#[derive(Clone, Debug, Eq, PartialEq)]\npub(crate) struct GraphemeCluster<'a> {\n    graphemes: Vec<Grapheme>,\n    config: &'a RegExpConfig,\n}\n\nimpl<'a> GraphemeCluster<'a> {\n    pub(crate) fn from(s: &str, config: &'a RegExpConfig) -> Self {\n        Self {\n            graphemes: UnicodeSegmentation::graphemes(s, true)\n                .flat_map(|it| {\n                    let contains_backslash = it.chars().count() == 2 && it.contains('\\\\');\n                    let contains_combining_mark_or_unassigned_chars = it.chars().any(|c| {\n                        let category = unicode_general_category::get_general_category(c);\n                        matches!(\n                            category,\n                            // Mark categories\n                            GC::NonspacingMark | GC::SpacingMark | GC::EnclosingMark |\n                            // Other categories\n                            GC::Control | GC::Format | GC::Surrogate | GC::PrivateUse | GC::Unassigned\n                        )\n                    });\n\n                    if contains_backslash || contains_combining_mark_or_unassigned_chars {\n                        it.chars()\n                            .map(|c| {\n                                Grapheme::from(\n                                    &c.to_string(),\n                                    config.is_capturing_group_enabled,\n                                    config.is_output_colorized,\n                                    config.is_verbose_mode_enabled,\n                                )\n                            })\n                            .collect_vec()\n                    } else {\n                        vec![Grapheme::from(\n                            it,\n                            config.is_capturing_group_enabled,\n                            config.is_output_colorized,\n                            config.is_verbose_mode_enabled,\n                        )]\n                    }\n                })\n                .collect_vec(),\n            config,\n        }\n    }\n\n    pub(crate) fn from_graphemes(graphemes: Vec<Grapheme>, config: &'a RegExpConfig) -> Self {\n        Self { graphemes, config }\n    }\n\n    pub(crate) fn new(grapheme: Grapheme, config: &'a RegExpConfig) -> Self {\n        Self {\n            graphemes: vec![grapheme],\n            config,\n        }\n    }\n\n    pub(crate) fn convert_to_char_classes(&mut self) {\n        let is_digit_converted = self.config.is_digit_converted;\n        let is_non_digit_converted = self.config.is_non_digit_converted;\n        let is_space_converted = self.config.is_space_converted;\n        let is_non_space_converted = self.config.is_non_space_converted;\n        let is_word_converted = self.config.is_word_converted;\n        let is_non_word_converted = self.config.is_non_word_converted;\n\n        for grapheme in self.graphemes.iter_mut() {\n            grapheme.chars = grapheme\n                .chars\n                .iter()\n                .map(|it| {\n                    it.chars()\n                        .map(|c| {\n                            if is_digit_converted && is_digit(c) {\n                                \"\\\\d\".to_string()\n                            } else if is_word_converted && is_word(c) {\n                                \"\\\\w\".to_string()\n                            } else if is_space_converted && is_space(c) {\n                                \"\\\\s\".to_string()\n                            } else if is_non_digit_converted && !is_digit(c) {\n                                \"\\\\D\".to_string()\n                            } else if is_non_word_converted && !is_word(c) {\n                                \"\\\\W\".to_string()\n                            } else if is_non_space_converted && !is_space(c) {\n                                \"\\\\S\".to_string()\n                            } else {\n                                c.to_string()\n                            }\n                        })\n                        .join(\"\")\n                })\n                .collect_vec();\n        }\n    }\n\n    pub(crate) fn convert_repetitions(&mut self) {\n        let mut repetitions = vec![];\n        convert_repetitions(self.graphemes(), repetitions.as_mut(), self.config);\n        if !repetitions.is_empty() {\n            self.graphemes = repetitions;\n        }\n    }\n\n    pub(crate) fn merge(\n        first: &GraphemeCluster,\n        second: &GraphemeCluster,\n        config: &'a RegExpConfig,\n    ) -> Self {\n        let mut graphemes = vec![];\n        graphemes.extend_from_slice(&first.graphemes);\n        graphemes.extend_from_slice(&second.graphemes);\n        Self { graphemes, config }\n    }\n\n    pub(crate) fn graphemes(&self) -> &Vec<Grapheme> {\n        &self.graphemes\n    }\n\n    pub(crate) fn graphemes_mut(&mut self) -> &mut Vec<Grapheme> {\n        &mut self.graphemes\n    }\n\n    pub(crate) fn size(&self) -> usize {\n        self.graphemes.len()\n    }\n\n    pub(crate) fn char_count(&self, is_non_ascii_char_escaped: bool) -> usize {\n        self.graphemes\n            .iter()\n            .map(|it| it.char_count(is_non_ascii_char_escaped))\n            .sum()\n    }\n\n    pub(crate) fn is_empty(&self) -> bool {\n        self.graphemes.is_empty()\n    }\n}\n\nfn is_digit(c: char) -> bool {\n    static VALID_NUMERIC_CHARS: LazyLock<Vec<CharRange>> =\n        LazyLock::new(|| convert_chars_to_range(DECIMAL_NUMBER));\n    VALID_NUMERIC_CHARS.iter().any(|range| range.contains(c))\n}\n\nfn is_word(c: char) -> bool {\n    static VALID_ALPHANUMERIC_CHARS: LazyLock<Vec<CharRange>> =\n        LazyLock::new(|| convert_chars_to_range(WORD));\n    VALID_ALPHANUMERIC_CHARS\n        .iter()\n        .any(|range| range.contains(c))\n}\n\nfn is_space(c: char) -> bool {\n    static VALID_SPACE_CHARS: LazyLock<Vec<CharRange>> =\n        LazyLock::new(|| convert_chars_to_range(WHITE_SPACE));\n    VALID_SPACE_CHARS.iter().any(|range| range.contains(c))\n}\n\nfn convert_repetitions(\n    graphemes: &[Grapheme],\n    repetitions: &mut Vec<Grapheme>,\n    config: &RegExpConfig,\n) {\n    let repeated_substrings = collect_repeated_substrings(graphemes);\n    let ranges_of_repetitions = create_ranges_of_repetitions(repeated_substrings, config);\n    let coalesced_repetitions = coalesce_repetitions(ranges_of_repetitions);\n    replace_graphemes_with_repetitions(coalesced_repetitions, graphemes, repetitions, config)\n}\n\nfn collect_repeated_substrings(graphemes: &[Grapheme]) -> HashMap<Vec<String>, Vec<usize>> {\n    let mut map = HashMap::new();\n\n    for i in 0..graphemes.len() {\n        let suffix = &graphemes[i..];\n        for j in 1..=graphemes.len() / 2 {\n            if suffix.len() >= j {\n                let prefix = suffix[..j].iter().map(|it| it.value()).collect_vec();\n                let indices = map.entry(prefix).or_insert_with(Vec::new);\n                indices.push(i);\n            }\n        }\n    }\n    map\n}\n\nfn create_ranges_of_repetitions(\n    repeated_substrings: HashMap<Vec<String>, Vec<usize>>,\n    config: &RegExpConfig,\n) -> Vec<(Range<usize>, Vec<String>)> {\n    let mut repetitions = Vec::<(Range<usize>, Vec<String>)>::new();\n\n    for (prefix_length, group) in &repeated_substrings\n        .iter()\n        .filter(|&(prefix, indices)| {\n            indices\n                .iter()\n                .tuple_windows()\n                .all(|(first, second)| (second - first) >= prefix.len())\n        })\n        .sorted_by_key(|&(prefix, _)| prefix.len())\n        .rev()\n        .chunk_by(|&(prefix, _)| prefix.len())\n    {\n        for (prefix, indices) in group.sorted_by_key(|&(_, indices)| indices[0]) {\n            indices\n                .iter()\n                .map(|it| *it..it + prefix_length)\n                .coalesce(|x, y| {\n                    if x.end == y.start {\n                        Ok(x.start..y.end)\n                    } else {\n                        Err((x, y))\n                    }\n                })\n                .filter(|range| {\n                    let count = ((range.end - range.start) / prefix_length) as u32;\n                    count > config.minimum_repetitions\n                })\n                .for_each(|range| repetitions.push((range, prefix.clone())));\n        }\n    }\n    repetitions\n}\n\nfn coalesce_repetitions(\n    ranges_of_repetitions: Vec<(Range<usize>, Vec<String>)>,\n) -> Vec<(Range<usize>, Vec<String>)> {\n    ranges_of_repetitions\n        .iter()\n        .sorted_by(|&(first_range, _), &(second_range, _)| {\n            match second_range.end.cmp(&first_range.end) {\n                Ordering::Equal => first_range.start.cmp(&second_range.start),\n                other => other,\n            }\n        })\n        .coalesce(|first_tup, second_tup| {\n            let first_range = &first_tup.0;\n            let second_range = &second_tup.0;\n\n            if (first_range.contains(&second_range.start)\n                || first_range.contains(&second_range.end))\n                && second_range.end != first_range.start\n            {\n                Ok(first_tup)\n            } else {\n                Err((first_tup, second_tup))\n            }\n        })\n        .map(|(range, substr)| (range.clone(), substr.clone()))\n        .collect_vec()\n}\n\nfn replace_graphemes_with_repetitions(\n    coalesced_repetitions: Vec<(Range<usize>, Vec<String>)>,\n    graphemes: &[Grapheme],\n    repetitions: &mut Vec<Grapheme>,\n    config: &RegExpConfig,\n) {\n    if coalesced_repetitions.is_empty() {\n        return;\n    }\n\n    for grapheme in graphemes {\n        repetitions.push(grapheme.clone());\n    }\n\n    for (range, substr) in coalesced_repetitions.iter() {\n        if range.end > repetitions.len() {\n            break;\n        }\n\n        let count = ((range.end - range.start) / substr.len()) as u32;\n\n        if substr.len() < config.minimum_substring_length as usize {\n            continue;\n        }\n\n        repetitions.splice(\n            range.clone(),\n            [Grapheme::new(\n                substr.clone(),\n                count,\n                count,\n                config.is_capturing_group_enabled,\n                config.is_output_colorized,\n                config.is_verbose_mode_enabled,\n            )]\n            .iter()\n            .cloned(),\n        );\n    }\n\n    for new_grapheme in repetitions.iter_mut() {\n        convert_repetitions(\n            &new_grapheme\n                .chars\n                .iter()\n                .map(|it| {\n                    Grapheme::from(\n                        it,\n                        config.is_capturing_group_enabled,\n                        config.is_output_colorized,\n                        config.is_verbose_mode_enabled,\n                    )\n                })\n                .collect_vec(),\n            new_grapheme.repetitions.as_mut(),\n            config,\n        );\n    }\n}\n\nfn convert_chars_to_range(chars: &[(char, char)]) -> Vec<CharRange> {\n    chars\n        .iter()\n        .map(|&(start, end)| CharRange::closed(start, end))\n        .collect_vec()\n}\n"
  },
  {
    "path": "src/component.rs",
    "content": "/*\n * Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse crate::quantifier::Quantifier;\nuse std::fmt::{Display, Formatter, Result};\n\npub(crate) enum Component {\n    CapturedLeftParenthesis,\n    CapturedParenthesizedExpression(String, bool, bool),\n    Caret(bool),\n    CharClass(String),\n    DollarSign(bool),\n    Hyphen,\n    IgnoreCaseFlag,\n    IgnoreCaseAndVerboseModeFlag,\n    LeftBracket,\n    Pipe,\n    Quantifier(Quantifier, bool),\n    Repetition(u32, bool),\n    RepetitionRange(u32, u32, bool),\n    RightBracket,\n    RightParenthesis,\n    UncapturedLeftParenthesis,\n    UncapturedParenthesizedExpression(String, bool, bool),\n    VerboseModeFlag,\n}\n\nimpl Component {\n    pub(crate) fn to_repr(&self, is_output_colorized: bool) -> String {\n        match is_output_colorized {\n            true => self.to_colored_string(false),\n            false => self.to_string(),\n        }\n    }\n\n    pub(crate) fn to_colored_string(&self, is_escaped: bool) -> String {\n        match self {\n            Component::CapturedLeftParenthesis => Self::green_bold(&self.to_string(), is_escaped),\n            Component::CapturedParenthesizedExpression(\n                expr,\n                is_verbose_mode_enabled,\n                has_final_line_break,\n            ) => {\n                if *is_verbose_mode_enabled {\n                    if *has_final_line_break {\n                        format!(\n                            \"\\n{}\\n{}\\n{}\\n\",\n                            Component::CapturedLeftParenthesis.to_colored_string(is_escaped),\n                            expr,\n                            Component::RightParenthesis.to_colored_string(is_escaped)\n                        )\n                    } else {\n                        format!(\n                            \"\\n{}\\n{}\\n{}\",\n                            Component::CapturedLeftParenthesis.to_colored_string(is_escaped),\n                            expr,\n                            Component::RightParenthesis.to_colored_string(is_escaped)\n                        )\n                    }\n                } else {\n                    format!(\n                        \"{}{}{}\",\n                        Component::CapturedLeftParenthesis.to_colored_string(is_escaped),\n                        expr,\n                        Component::RightParenthesis.to_colored_string(is_escaped)\n                    )\n                }\n            }\n            Component::Caret(is_verbose_mode_enabled) => {\n                if *is_verbose_mode_enabled {\n                    format!(\n                        \"{}\\n\",\n                        Self::yellow_bold(&Component::Caret(false).to_string(), is_escaped)\n                    )\n                } else {\n                    Self::yellow_bold(&self.to_string(), is_escaped)\n                }\n            }\n            Component::CharClass(value) => Self::black_on_bright_yellow(value, is_escaped),\n            Component::DollarSign(is_verbose_mode_enabled) => {\n                if *is_verbose_mode_enabled {\n                    format!(\n                        \"\\n{}\",\n                        Self::yellow_bold(&Component::DollarSign(false).to_string(), is_escaped)\n                    )\n                } else {\n                    Self::yellow_bold(&self.to_string(), is_escaped)\n                }\n            }\n            Component::Hyphen => Self::cyan_bold(&self.to_string(), is_escaped),\n            Component::IgnoreCaseFlag => {\n                Self::bright_yellow_on_black(&self.to_string(), is_escaped)\n            }\n            Component::IgnoreCaseAndVerboseModeFlag => {\n                format!(\"{}\\n\", Self::bright_yellow_on_black(\"(?ix)\", is_escaped))\n            }\n            Component::LeftBracket => Self::cyan_bold(&self.to_string(), is_escaped),\n            Component::Pipe => Self::red_bold(&self.to_string(), is_escaped),\n            Component::Quantifier(quantifier, is_verbose_mode_enabled) => {\n                if *is_verbose_mode_enabled {\n                    format!(\n                        \"{}\\n\",\n                        Self::purple_bold(&quantifier.to_string(), is_escaped)\n                    )\n                } else {\n                    Self::purple_bold(&self.to_string(), is_escaped)\n                }\n            }\n            Component::Repetition(num, is_verbose_mode_enabled) => {\n                if *is_verbose_mode_enabled {\n                    format!(\n                        \"{}\\n\",\n                        Self::white_on_bright_blue(\n                            &Component::Repetition(*num, false).to_string(),\n                            is_escaped\n                        )\n                    )\n                } else {\n                    Self::white_on_bright_blue(&self.to_string(), is_escaped)\n                }\n            }\n            Component::RepetitionRange(min, max, is_verbose_mode_enabled) => {\n                if *is_verbose_mode_enabled {\n                    format!(\n                        \"{}\\n\",\n                        Self::white_on_bright_blue(\n                            &Component::RepetitionRange(*min, *max, false).to_string(),\n                            is_escaped\n                        )\n                    )\n                } else {\n                    Self::white_on_bright_blue(&self.to_string(), is_escaped)\n                }\n            }\n            Component::RightBracket => Self::cyan_bold(&self.to_string(), is_escaped),\n            Component::RightParenthesis => Self::green_bold(&self.to_string(), is_escaped),\n            Component::UncapturedLeftParenthesis => Self::green_bold(&self.to_string(), is_escaped),\n            Component::UncapturedParenthesizedExpression(\n                expr,\n                is_verbose_mode_enabled,\n                has_final_line_break,\n            ) => {\n                if *is_verbose_mode_enabled {\n                    if *has_final_line_break {\n                        format!(\n                            \"\\n{}\\n{}\\n{}\\n\",\n                            Component::UncapturedLeftParenthesis.to_colored_string(is_escaped),\n                            expr,\n                            Component::RightParenthesis.to_colored_string(is_escaped)\n                        )\n                    } else {\n                        format!(\n                            \"\\n{}\\n{}\\n{}\",\n                            Component::UncapturedLeftParenthesis.to_colored_string(is_escaped),\n                            expr,\n                            Component::RightParenthesis.to_colored_string(is_escaped)\n                        )\n                    }\n                } else {\n                    format!(\n                        \"{}{}{}\",\n                        Component::UncapturedLeftParenthesis.to_colored_string(is_escaped),\n                        expr,\n                        Component::RightParenthesis.to_colored_string(is_escaped)\n                    )\n                }\n            }\n            Component::VerboseModeFlag => {\n                format!(\"{}\\n\", Self::bright_yellow_on_black(\"(?x)\", is_escaped))\n            }\n        }\n    }\n\n    fn black_on_bright_yellow(value: &str, is_escaped: bool) -> String {\n        Self::color_code(\"103;30\", value, is_escaped)\n    }\n\n    fn bright_yellow_on_black(value: &str, is_escaped: bool) -> String {\n        Self::color_code(\"40;93\", value, is_escaped)\n    }\n\n    fn cyan_bold(value: &str, is_escaped: bool) -> String {\n        Self::color_code(\"1;36\", value, is_escaped)\n    }\n\n    fn green_bold(value: &str, is_escaped: bool) -> String {\n        Self::color_code(\"1;32\", value, is_escaped)\n    }\n\n    fn purple_bold(value: &str, is_escaped: bool) -> String {\n        Self::color_code(\"1;35\", value, is_escaped)\n    }\n\n    fn red_bold(value: &str, is_escaped: bool) -> String {\n        Self::color_code(\"1;31\", value, is_escaped)\n    }\n\n    fn white_on_bright_blue(value: &str, is_escaped: bool) -> String {\n        Self::color_code(\"104;37\", value, is_escaped)\n    }\n\n    fn yellow_bold(value: &str, is_escaped: bool) -> String {\n        Self::color_code(\"1;33\", value, is_escaped)\n    }\n\n    fn color_code(code: &str, value: &str, is_escaped: bool) -> String {\n        if is_escaped {\n            format!(\"\\u{1b}\\\\[{}m\\\\{}\\u{1b}\\\\[0m\", code, value)\n        } else {\n            format!(\"\\u{1b}[{}m{}\\u{1b}[0m\", code, value)\n        }\n    }\n}\n\nimpl Display for Component {\n    fn fmt(&self, f: &mut Formatter<'_>) -> Result {\n        write!(\n            f,\n            \"{}\",\n            match self {\n                Component::CapturedLeftParenthesis => \"(\".to_string(),\n                Component::CapturedParenthesizedExpression(\n                    expr,\n                    is_verbose_mode_enabled,\n                    has_final_line_break,\n                ) =>\n                    if *is_verbose_mode_enabled {\n                        if *has_final_line_break {\n                            format!(\n                                \"\\n{}\\n{}\\n{}\\n\",\n                                Component::CapturedLeftParenthesis,\n                                expr,\n                                Component::RightParenthesis\n                            )\n                        } else {\n                            format!(\n                                \"\\n{}\\n{}\\n{}\",\n                                Component::CapturedLeftParenthesis,\n                                expr,\n                                Component::RightParenthesis\n                            )\n                        }\n                    } else {\n                        format!(\n                            \"{}{}{}\",\n                            Component::CapturedLeftParenthesis,\n                            expr,\n                            Component::RightParenthesis\n                        )\n                    },\n                Component::Caret(is_verbose_mode_enabled) =>\n                    if *is_verbose_mode_enabled {\n                        \"^\\n\".to_string()\n                    } else {\n                        \"^\".to_string()\n                    },\n                Component::CharClass(value) => value.clone(),\n                Component::DollarSign(is_verbose_mode_enabled) =>\n                    if *is_verbose_mode_enabled {\n                        \"\\n$\".to_string()\n                    } else {\n                        \"$\".to_string()\n                    },\n                Component::Hyphen => \"-\".to_string(),\n                Component::IgnoreCaseFlag => \"(?i)\".to_string(),\n                Component::IgnoreCaseAndVerboseModeFlag => \"(?ix)\\n\".to_string(),\n                Component::LeftBracket => \"[\".to_string(),\n                Component::Pipe => \"|\".to_string(),\n                Component::Quantifier(quantifier, is_verbose_mode_enabled) =>\n                    if *is_verbose_mode_enabled {\n                        format!(\"{}\\n\", quantifier)\n                    } else {\n                        quantifier.to_string()\n                    },\n                Component::Repetition(num, is_verbose_mode_enabled) => {\n                    if *num == 0 && *is_verbose_mode_enabled {\n                        \"{\\\\d+\\\\}\\n\".to_string()\n                    } else if *num == 0 {\n                        \"{\\\\d+\\\\}\".to_string()\n                    } else if *is_verbose_mode_enabled {\n                        format!(\"{{{}}}\\n\", num)\n                    } else {\n                        format!(\"{{{}}}\", num)\n                    }\n                }\n                Component::RepetitionRange(min, max, is_verbose_mode_enabled) => {\n                    if *min == 0 && *max == 0 && *is_verbose_mode_enabled {\n                        \"{\\\\d+,\\\\d+\\\\}\\n\".to_string()\n                    } else if *min == 0 && *max == 0 {\n                        \"{\\\\d+,\\\\d+\\\\}\".to_string()\n                    } else if *is_verbose_mode_enabled {\n                        format!(\"{{{},{}}}\\n\", min, max)\n                    } else {\n                        format!(\"{{{},{}}}\", min, max)\n                    }\n                }\n                Component::RightBracket => \"]\".to_string(),\n                Component::RightParenthesis => \")\".to_string(),\n                Component::UncapturedLeftParenthesis => \"(?:\".to_string(),\n                Component::UncapturedParenthesizedExpression(\n                    expr,\n                    is_verbose_mode_enabled,\n                    has_final_line_break,\n                ) => {\n                    if *is_verbose_mode_enabled {\n                        if *has_final_line_break {\n                            format!(\n                                \"\\n{}\\n{}\\n{}\\n\",\n                                Component::UncapturedLeftParenthesis,\n                                expr,\n                                Component::RightParenthesis\n                            )\n                        } else {\n                            format!(\n                                \"\\n{}\\n{}\\n{}\",\n                                Component::UncapturedLeftParenthesis,\n                                expr,\n                                Component::RightParenthesis\n                            )\n                        }\n                    } else {\n                        format!(\n                            \"{}{}{}\",\n                            Component::UncapturedLeftParenthesis,\n                            expr,\n                            Component::RightParenthesis\n                        )\n                    }\n                }\n                Component::VerboseModeFlag => \"(?x)\\n\".to_string(),\n            }\n        )\n    }\n}\n"
  },
  {
    "path": "src/config.rs",
    "content": "/*\n * Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#[derive(Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]\npub(crate) struct RegExpConfig {\n    pub(crate) minimum_repetitions: u32,\n    pub(crate) minimum_substring_length: u32,\n    pub(crate) is_digit_converted: bool,\n    pub(crate) is_non_digit_converted: bool,\n    pub(crate) is_space_converted: bool,\n    pub(crate) is_non_space_converted: bool,\n    pub(crate) is_word_converted: bool,\n    pub(crate) is_non_word_converted: bool,\n    pub(crate) is_repetition_converted: bool,\n    pub(crate) is_case_insensitive_matching: bool,\n    pub(crate) is_capturing_group_enabled: bool,\n    pub(crate) is_non_ascii_char_escaped: bool,\n    pub(crate) is_astral_code_point_converted_to_surrogate: bool,\n    pub(crate) is_verbose_mode_enabled: bool,\n    pub(crate) is_start_anchor_disabled: bool,\n    pub(crate) is_end_anchor_disabled: bool,\n    pub(crate) is_output_colorized: bool,\n}\n\nimpl RegExpConfig {\n    pub(crate) fn new() -> Self {\n        Self {\n            minimum_repetitions: 1,\n            minimum_substring_length: 1,\n            is_digit_converted: false,\n            is_non_digit_converted: false,\n            is_space_converted: false,\n            is_non_space_converted: false,\n            is_word_converted: false,\n            is_non_word_converted: false,\n            is_repetition_converted: false,\n            is_case_insensitive_matching: false,\n            is_capturing_group_enabled: false,\n            is_non_ascii_char_escaped: false,\n            is_astral_code_point_converted_to_surrogate: false,\n            is_verbose_mode_enabled: false,\n            is_start_anchor_disabled: false,\n            is_end_anchor_disabled: false,\n            is_output_colorized: false,\n        }\n    }\n\n    pub(crate) fn is_char_class_feature_enabled(&self) -> bool {\n        self.is_digit_converted\n            || self.is_non_digit_converted\n            || self.is_space_converted\n            || self.is_non_space_converted\n            || self.is_word_converted\n            || self.is_non_word_converted\n            || self.is_case_insensitive_matching\n            || self.is_capturing_group_enabled\n    }\n}\n"
  },
  {
    "path": "src/dfa.rs",
    "content": "/*\n * Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse crate::cluster::GraphemeCluster;\nuse crate::config::RegExpConfig;\nuse crate::grapheme::Grapheme;\nuse itertools::Itertools;\nuse petgraph::graph::NodeIndex;\nuse petgraph::stable_graph::{Edges, StableGraph};\nuse petgraph::visit::Dfs;\nuse petgraph::{Directed, Direction};\nuse std::cmp::{max, min};\nuse std::collections::{BTreeSet, HashMap, HashSet};\n\ntype State = NodeIndex<u32>;\ntype StateLabel = String;\ntype EdgeLabel = Grapheme;\n\npub(crate) struct Dfa<'a> {\n    alphabet: BTreeSet<Grapheme>,\n    graph: StableGraph<StateLabel, EdgeLabel>,\n    initial_state: State,\n    final_state_indices: HashSet<usize>,\n    config: &'a RegExpConfig,\n}\n\nimpl<'a> Dfa<'a> {\n    pub(crate) fn from(\n        grapheme_clusters: &[GraphemeCluster],\n        is_minimized: bool,\n        config: &'a RegExpConfig,\n    ) -> Self {\n        let mut dfa = Self::new(config);\n        for cluster in grapheme_clusters {\n            dfa.insert(cluster);\n        }\n        if is_minimized {\n            dfa.minimize();\n        }\n        dfa\n    }\n\n    pub(crate) fn state_count(&self) -> usize {\n        self.graph.node_count()\n    }\n\n    pub(crate) fn states_in_depth_first_order(&self) -> Vec<State> {\n        let mut depth_first_search = Dfs::new(&self.graph, self.initial_state);\n        let mut states = vec![];\n        while let Some(state) = depth_first_search.next(&self.graph) {\n            states.push(state);\n        }\n        states\n    }\n\n    pub(crate) fn outgoing_edges(&self, state: State) -> Edges<'_, Grapheme, Directed> {\n        self.graph.edges_directed(state, Direction::Outgoing)\n    }\n\n    pub(crate) fn is_final_state(&self, state: State) -> bool {\n        self.final_state_indices.contains(&state.index())\n    }\n\n    fn new(config: &'a RegExpConfig) -> Self {\n        let mut graph = StableGraph::new();\n        let initial_state = graph.add_node(\"\".to_string());\n        Self {\n            alphabet: BTreeSet::new(),\n            graph,\n            initial_state,\n            final_state_indices: HashSet::new(),\n            config,\n        }\n    }\n\n    fn insert(&mut self, cluster: &GraphemeCluster) {\n        let mut current_state = self.initial_state;\n\n        for grapheme in cluster.graphemes() {\n            self.alphabet.insert(grapheme.clone());\n            current_state = self.return_next_state(current_state, grapheme);\n        }\n        self.final_state_indices.insert(current_state.index());\n    }\n\n    fn return_next_state(&mut self, current_state: State, edge_label: &Grapheme) -> State {\n        match self.find_next_state(current_state, edge_label) {\n            Some(next_state) => next_state,\n            None => self.add_new_state(current_state, edge_label),\n        }\n    }\n\n    fn find_next_state(&mut self, current_state: State, grapheme: &Grapheme) -> Option<State> {\n        for next_state in self.graph.neighbors(current_state) {\n            let edge_idx = self.graph.find_edge(current_state, next_state).unwrap();\n            let current_grapheme = self.graph.edge_weight(edge_idx).unwrap();\n\n            if current_grapheme.value() != grapheme.value() {\n                continue;\n            }\n\n            if current_grapheme.maximum() == grapheme.maximum() - 1 {\n                let min = min(current_grapheme.minimum(), grapheme.minimum());\n                let max = max(current_grapheme.maximum(), grapheme.maximum());\n                let new_grapheme = Grapheme::new(\n                    grapheme.chars().clone(),\n                    min,\n                    max,\n                    self.config.is_capturing_group_enabled,\n                    self.config.is_output_colorized,\n                    self.config.is_verbose_mode_enabled,\n                );\n                self.graph\n                    .update_edge(current_state, next_state, new_grapheme);\n                return Some(next_state);\n            } else if current_grapheme.maximum() == grapheme.maximum() {\n                return Some(next_state);\n            }\n        }\n        None\n    }\n\n    fn add_new_state(&mut self, current_state: State, edge_label: &Grapheme) -> State {\n        let next_state = self.graph.add_node(\"\".to_string());\n        self.graph\n            .add_edge(current_state, next_state, edge_label.clone());\n        next_state\n    }\n\n    #[allow(clippy::many_single_char_names)]\n    fn minimize(&mut self) {\n        let mut p = self.get_initial_partition();\n        let mut w = p.iter().cloned().collect_vec();\n\n        while !w.is_empty() {\n            let a = w.drain(0..1).next().unwrap();\n\n            for edge_label in self.alphabet.iter() {\n                let x = self.get_parent_states(&a, edge_label);\n                let mut replacements = vec![];\n                let mut is_replacement_needed = true;\n                let mut start_idx = 0;\n\n                while is_replacement_needed {\n                    for (idx, y) in p.iter().enumerate().skip(start_idx) {\n                        if x.intersection(y).count() == 0 || y.difference(&x).count() == 0 {\n                            is_replacement_needed = false;\n                            continue;\n                        }\n\n                        let i = x.intersection(y).copied().collect::<HashSet<State>>();\n                        let d = y.difference(&x).copied().collect::<HashSet<State>>();\n\n                        is_replacement_needed = true;\n                        start_idx = idx;\n\n                        replacements.push((y.clone(), i, d));\n\n                        break;\n                    }\n\n                    if is_replacement_needed {\n                        let (_, i, d) = replacements.last().unwrap();\n\n                        p.remove(start_idx);\n                        p.insert(start_idx, i.clone());\n                        p.insert(start_idx + 1, d.clone());\n                    }\n                }\n\n                for (y, i, d) in replacements {\n                    if w.contains(&y) {\n                        let idx = w.iter().position(|it| it == &y).unwrap();\n                        w.remove(idx);\n                        w.push(i);\n                        w.push(d);\n                    } else if i.len() <= d.len() {\n                        w.push(i);\n                    } else {\n                        w.push(d);\n                    }\n                }\n            }\n        }\n\n        self.recreate_graph(p.iter().filter(|&it| !it.is_empty()).collect_vec());\n    }\n\n    fn get_initial_partition(&self) -> Vec<HashSet<State>> {\n        let (final_states, non_final_states): (HashSet<State>, HashSet<State>) = self\n            .graph\n            .node_indices()\n            .partition(|&state| !self.final_state_indices.contains(&state.index()));\n\n        vec![final_states, non_final_states]\n    }\n\n    fn get_parent_states(&self, a: &HashSet<State>, label: &Grapheme) -> HashSet<State> {\n        let mut x = HashSet::new();\n\n        for &state in a {\n            let direct_parent_states = self.graph.neighbors_directed(state, Direction::Incoming);\n            for parent_state in direct_parent_states {\n                let edge = self.graph.find_edge(parent_state, state).unwrap();\n                let grapheme = self.graph.edge_weight(edge).unwrap();\n                if grapheme.value() == label.value()\n                    && (grapheme.maximum() == label.maximum()\n                        || grapheme.minimum() == label.minimum())\n                {\n                    x.insert(parent_state);\n                    break;\n                }\n            }\n        }\n        x\n    }\n\n    fn recreate_graph(&mut self, p: Vec<&HashSet<State>>) {\n        let mut graph = StableGraph::<StateLabel, EdgeLabel>::new();\n        let mut final_state_indices = HashSet::new();\n        let mut state_mappings = HashMap::new();\n        let mut new_initial_state: Option<NodeIndex> = None;\n\n        for equivalence_class in p.iter() {\n            let new_state = graph.add_node(\"\".to_string());\n\n            for old_state in equivalence_class.iter() {\n                if self.initial_state == *old_state {\n                    new_initial_state = Some(new_state);\n                }\n                state_mappings.insert(*old_state, new_state);\n            }\n        }\n\n        for equivalence_class in p.iter() {\n            let old_source_state = *equivalence_class.iter().next().unwrap();\n            let new_source_state = state_mappings.get(&old_source_state).unwrap();\n\n            for old_target_state in self.graph.neighbors(old_source_state) {\n                let edge = self\n                    .graph\n                    .find_edge(old_source_state, old_target_state)\n                    .unwrap();\n\n                let grapheme = self.graph.edge_weight(edge).unwrap().clone();\n                let new_target_state = state_mappings.get(&old_target_state).unwrap();\n\n                graph.add_edge(*new_source_state, *new_target_state, grapheme.clone());\n\n                if self.final_state_indices.contains(&old_target_state.index()) {\n                    final_state_indices.insert(new_target_state.index());\n                }\n            }\n        }\n        self.initial_state = new_initial_state.unwrap();\n        self.final_state_indices = final_state_indices;\n        self.graph = graph;\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_state_count() {\n        let config = RegExpConfig::new();\n        let mut dfa = Dfa::new(&config);\n        assert_eq!(dfa.state_count(), 1);\n\n        dfa.insert(&GraphemeCluster::from(\"abcd\", &RegExpConfig::new()));\n        assert_eq!(dfa.state_count(), 5);\n    }\n\n    #[test]\n    fn test_is_final_state() {\n        let config = RegExpConfig::new();\n        let dfa = Dfa::from(\n            &[GraphemeCluster::from(\"abcd\", &RegExpConfig::new())],\n            true,\n            &config,\n        );\n\n        let intermediate_state = State::new(3);\n        assert_eq!(dfa.is_final_state(intermediate_state), false);\n\n        let final_state = State::new(4);\n        assert_eq!(dfa.is_final_state(final_state), true);\n    }\n\n    #[test]\n    fn test_outgoing_edges() {\n        let config = RegExpConfig::new();\n        let dfa = Dfa::from(\n            &[\n                GraphemeCluster::from(\"abcd\", &RegExpConfig::new()),\n                GraphemeCluster::from(\"abxd\", &RegExpConfig::new()),\n            ],\n            true,\n            &config,\n        );\n        let state = State::new(2);\n        let mut edges = dfa.outgoing_edges(state);\n\n        let first_edge = edges.next();\n        assert!(first_edge.is_some());\n        assert_eq!(\n            first_edge.unwrap().weight(),\n            &Grapheme::from(\"c\", false, false, false)\n        );\n\n        let second_edge = edges.next();\n        assert!(second_edge.is_some());\n        assert_eq!(\n            second_edge.unwrap().weight(),\n            &Grapheme::from(\"x\", false, false, false)\n        );\n\n        let third_edge = edges.next();\n        assert!(third_edge.is_none());\n    }\n\n    #[test]\n    fn test_states_in_depth_first_order() {\n        let config = RegExpConfig::new();\n        let dfa = Dfa::from(\n            &[\n                GraphemeCluster::from(\"abcd\", &RegExpConfig::new()),\n                GraphemeCluster::from(\"axyz\", &RegExpConfig::new()),\n            ],\n            true,\n            &config,\n        );\n        let states = dfa.states_in_depth_first_order();\n        assert_eq!(states.len(), 7);\n\n        let first_state = states.get(0).unwrap();\n        let mut edges = dfa.outgoing_edges(*first_state);\n        assert_eq!(\n            edges.next().unwrap().weight(),\n            &Grapheme::from(\"a\", false, false, false)\n        );\n        assert!(edges.next().is_none());\n\n        let second_state = states.get(1).unwrap();\n        edges = dfa.outgoing_edges(*second_state);\n        assert_eq!(\n            edges.next().unwrap().weight(),\n            &Grapheme::from(\"b\", false, false, false)\n        );\n        assert_eq!(\n            edges.next().unwrap().weight(),\n            &Grapheme::from(\"x\", false, false, false)\n        );\n        assert!(edges.next().is_none());\n\n        let third_state = states.get(2).unwrap();\n        edges = dfa.outgoing_edges(*third_state);\n        assert_eq!(\n            edges.next().unwrap().weight(),\n            &Grapheme::from(\"y\", false, false, false)\n        );\n        assert!(edges.next().is_none());\n\n        let fourth_state = states.get(3).unwrap();\n        edges = dfa.outgoing_edges(*fourth_state);\n        assert_eq!(\n            edges.next().unwrap().weight(),\n            &Grapheme::from(\"z\", false, false, false)\n        );\n        assert!(edges.next().is_none());\n\n        let fifth_state = states.get(4).unwrap();\n        edges = dfa.outgoing_edges(*fifth_state);\n        assert!(edges.next().is_none());\n\n        let sixth_state = states.get(5).unwrap();\n        edges = dfa.outgoing_edges(*sixth_state);\n        assert_eq!(\n            edges.next().unwrap().weight(),\n            &Grapheme::from(\"c\", false, false, false)\n        );\n        assert!(edges.next().is_none());\n\n        let seventh_state = states.get(6).unwrap();\n        edges = dfa.outgoing_edges(*seventh_state);\n        assert_eq!(\n            edges.next().unwrap().weight(),\n            &Grapheme::from(\"d\", false, false, false)\n        );\n        assert!(edges.next().is_none());\n    }\n\n    #[test]\n    fn test_minimization_algorithm() {\n        let config = RegExpConfig::new();\n        let mut dfa = Dfa::new(&config);\n        assert_eq!(dfa.graph.node_count(), 1);\n        assert_eq!(dfa.graph.edge_count(), 0);\n\n        dfa.insert(&GraphemeCluster::from(\"abcd\", &RegExpConfig::new()));\n        assert_eq!(dfa.graph.node_count(), 5);\n        assert_eq!(dfa.graph.edge_count(), 4);\n\n        dfa.insert(&GraphemeCluster::from(\"abxd\", &RegExpConfig::new()));\n        assert_eq!(dfa.graph.node_count(), 7);\n        assert_eq!(dfa.graph.edge_count(), 6);\n\n        dfa.minimize();\n        assert_eq!(dfa.graph.node_count(), 5);\n        assert_eq!(dfa.graph.edge_count(), 5);\n    }\n\n    #[test]\n    fn test_dfa_constructor() {\n        let config = RegExpConfig::new();\n        let dfa = Dfa::from(\n            &[\n                GraphemeCluster::from(\"abcd\", &RegExpConfig::new()),\n                GraphemeCluster::from(\"abxd\", &RegExpConfig::new()),\n            ],\n            true,\n            &config,\n        );\n        assert_eq!(dfa.graph.node_count(), 5);\n        assert_eq!(dfa.graph.edge_count(), 5);\n    }\n}\n"
  },
  {
    "path": "src/expression.rs",
    "content": "/*\n * Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse crate::cluster::GraphemeCluster;\nuse crate::config::RegExpConfig;\nuse crate::dfa::Dfa;\nuse crate::grapheme::Grapheme;\nuse crate::quantifier::Quantifier;\nuse crate::substring::Substring;\nuse itertools::EitherOrBoth::Both;\nuse itertools::Itertools;\nuse ndarray::{Array1, Array2};\nuse petgraph::prelude::EdgeRef;\nuse std::cmp::Reverse;\nuse std::collections::BTreeSet;\n\n#[derive(Clone, Debug, Eq, PartialEq)]\npub(crate) enum Expression<'a> {\n    Alternation(Vec<Expression<'a>>, bool, bool, bool),\n    CharacterClass(BTreeSet<char>, bool),\n    Concatenation(Box<Expression<'a>>, Box<Expression<'a>>, bool, bool, bool),\n    Literal(GraphemeCluster<'a>, bool, bool),\n    Repetition(Box<Expression<'a>>, Quantifier, bool, bool, bool),\n}\n\nimpl<'a> Expression<'a> {\n    pub(crate) fn from(dfa: Dfa, config: &'a RegExpConfig) -> Self {\n        let states = dfa.states_in_depth_first_order();\n        let state_count = dfa.state_count();\n\n        let mut a = Array2::<Option<Expression>>::default((state_count, state_count));\n        let mut b = Array1::<Option<Expression>>::default(state_count);\n\n        for (i, state) in states.iter().enumerate() {\n            if dfa.is_final_state(*state) {\n                b[i] = Some(Expression::new_literal(\n                    GraphemeCluster::from(\"\", config),\n                    config,\n                ));\n            }\n\n            for edge in dfa.outgoing_edges(*state) {\n                let literal = Expression::new_literal(\n                    GraphemeCluster::new(edge.weight().clone(), config),\n                    config,\n                );\n                let j = states.iter().position(|&it| it == edge.target()).unwrap();\n\n                a[(i, j)] = if a[(i, j)].is_some() {\n                    Self::union(&a[(i, j)], &Some(literal), config)\n                } else {\n                    Some(literal)\n                }\n            }\n        }\n\n        for n in (0..state_count).rev() {\n            if a[(n, n)].is_some() {\n                b[n] = Self::concatenate(\n                    &Self::repeat_zero_or_more_times(&a[(n, n)], config),\n                    &b[n],\n                    config,\n                );\n                for j in 0..n {\n                    a[(n, j)] = Self::concatenate(\n                        &Self::repeat_zero_or_more_times(&a[(n, n)], config),\n                        &a[(n, j)],\n                        config,\n                    );\n                }\n            }\n\n            for i in 0..n {\n                if a[(i, n)].is_some() {\n                    b[i] =\n                        Self::union(&b[i], &Self::concatenate(&a[(i, n)], &b[n], config), config);\n                    for j in 0..n {\n                        a[(i, j)] = Self::union(\n                            &a[(i, j)],\n                            &Self::concatenate(&a[(i, n)], &a[(n, j)], config),\n                            config,\n                        );\n                    }\n                }\n            }\n        }\n\n        if !b.is_empty() && b[0].is_some() {\n            b[0].as_ref().unwrap().clone()\n        } else {\n            Expression::new_literal(GraphemeCluster::from(\"\", config), config)\n        }\n    }\n\n    pub(crate) fn new_alternation(exprs: Vec<Expression<'a>>, config: &RegExpConfig) -> Self {\n        let mut options: Vec<Expression> = vec![];\n        Self::flatten_alternations(&mut options, exprs);\n        options.sort_by_key(|option| Reverse(option.len()));\n        Expression::Alternation(\n            options,\n            config.is_capturing_group_enabled,\n            config.is_output_colorized,\n            config.is_verbose_mode_enabled,\n        )\n    }\n\n    fn new_character_class(\n        first_char_set: BTreeSet<char>,\n        second_char_set: BTreeSet<char>,\n        config: &RegExpConfig,\n    ) -> Self {\n        let union_set = first_char_set.union(&second_char_set).copied().collect();\n        Expression::CharacterClass(union_set, config.is_output_colorized)\n    }\n\n    fn new_concatenation(\n        expr1: Expression<'a>,\n        expr2: Expression<'a>,\n        config: &RegExpConfig,\n    ) -> Self {\n        Expression::Concatenation(\n            Box::from(expr1),\n            Box::from(expr2),\n            config.is_capturing_group_enabled,\n            config.is_output_colorized,\n            config.is_verbose_mode_enabled,\n        )\n    }\n\n    pub(crate) fn new_literal(cluster: GraphemeCluster<'a>, config: &RegExpConfig) -> Self {\n        Expression::Literal(\n            cluster,\n            config.is_non_ascii_char_escaped,\n            config.is_astral_code_point_converted_to_surrogate,\n        )\n    }\n\n    fn new_repetition(expr: Expression<'a>, quantifier: Quantifier, config: &RegExpConfig) -> Self {\n        Expression::Repetition(\n            Box::from(expr),\n            quantifier,\n            config.is_capturing_group_enabled,\n            config.is_output_colorized,\n            config.is_verbose_mode_enabled,\n        )\n    }\n\n    fn is_empty(&self) -> bool {\n        match self {\n            Expression::Literal(cluster, _, _) => cluster.is_empty(),\n            _ => false,\n        }\n    }\n\n    pub(crate) fn is_single_codepoint(&self) -> bool {\n        match self {\n            Expression::CharacterClass(_, _) => true,\n            Expression::Literal(cluster, is_non_ascii_char_escaped, _) => {\n                cluster.char_count(*is_non_ascii_char_escaped) == 1\n                    && cluster.graphemes().first().unwrap().maximum() == 1\n            }\n            _ => false,\n        }\n    }\n\n    fn len(&self) -> usize {\n        match self {\n            Expression::Alternation(options, _, _, _) => options.first().unwrap().len(),\n            Expression::CharacterClass(_, _) => 1,\n            Expression::Concatenation(expr1, expr2, _, _, _) => expr1.len() + expr2.len(),\n            Expression::Literal(cluster, _, _) => cluster.size(),\n            Expression::Repetition(expr, _, _, _, _) => expr.len(),\n        }\n    }\n\n    pub(crate) fn precedence(&self) -> u8 {\n        match self {\n            Expression::Alternation(_, _, _, _) | Expression::CharacterClass(_, _) => 1,\n            Expression::Concatenation(_, _, _, _, _) | Expression::Literal(_, _, _) => 2,\n            Expression::Repetition(_, _, _, _, _) => 3,\n        }\n    }\n\n    pub(crate) fn remove_substring(&mut self, substring: &Substring, length: usize) {\n        match self {\n            Expression::Concatenation(expr1, expr2, _, _, _) => match substring {\n                Substring::Prefix => {\n                    if let Expression::Literal(_, _, _) = **expr1 {\n                        expr1.remove_substring(substring, length)\n                    }\n                }\n                Substring::Suffix => {\n                    if let Expression::Literal(_, _, _) = **expr2 {\n                        expr2.remove_substring(substring, length)\n                    }\n                }\n            },\n            Expression::Literal(cluster, _, _) => match substring {\n                Substring::Prefix => {\n                    cluster.graphemes_mut().drain(..length);\n                }\n                Substring::Suffix => {\n                    let graphemes = cluster.graphemes_mut();\n                    graphemes.drain(graphemes.len() - length..);\n                }\n            },\n            _ => (),\n        }\n    }\n\n    pub(crate) fn value(&self, substring: Option<&Substring>) -> Option<Vec<Grapheme>> {\n        match self {\n            Expression::Concatenation(expr1, expr2, _, _, _) => match substring {\n                Some(value) => match value {\n                    Substring::Prefix => expr1.value(None),\n                    Substring::Suffix => expr2.value(None),\n                },\n                None => None,\n            },\n            Expression::Literal(cluster, _, _) => Some(cluster.graphemes().clone()),\n            _ => None,\n        }\n    }\n\n    fn repeat_zero_or_more_times(\n        expr: &Option<Expression<'a>>,\n        config: &'a RegExpConfig,\n    ) -> Option<Expression<'a>> {\n        expr.as_ref()\n            .map(|value| Expression::new_repetition(value.clone(), Quantifier::KleeneStar, config))\n    }\n\n    fn concatenate(\n        a: &Option<Expression<'a>>,\n        b: &Option<Expression<'a>>,\n        config: &'a RegExpConfig,\n    ) -> Option<Expression<'a>> {\n        if a.is_none() || b.is_none() {\n            return None;\n        }\n\n        let expr1 = a.as_ref().unwrap();\n        let expr2 = b.as_ref().unwrap();\n\n        if expr1.is_empty() {\n            return b.clone();\n        }\n        if expr2.is_empty() {\n            return a.clone();\n        }\n\n        if let (Expression::Literal(graphemes_a, _, _), Expression::Literal(graphemes_b, _, _)) =\n            (&expr1, &expr2)\n        {\n            return Some(Expression::new_literal(\n                GraphemeCluster::merge(graphemes_a, graphemes_b, config),\n                config,\n            ));\n        }\n\n        if let (\n            Expression::Literal(graphemes_a, _, _),\n            Expression::Concatenation(first, second, _, _, _),\n        ) = (&expr1, &expr2)\n        {\n            if let Expression::Literal(graphemes_first, _, _) = &**first {\n                let literal = Expression::new_literal(\n                    GraphemeCluster::merge(graphemes_a, graphemes_first, config),\n                    config,\n                );\n                return Some(Expression::new_concatenation(\n                    literal,\n                    *second.clone(),\n                    config,\n                ));\n            }\n        }\n\n        if let (\n            Expression::Literal(graphemes_b, _, _),\n            Expression::Concatenation(first, second, _, _, _),\n        ) = (&expr2, &expr1)\n        {\n            if let Expression::Literal(graphemes_second, _, _) = &**second {\n                let literal = Expression::new_literal(\n                    GraphemeCluster::merge(graphemes_second, graphemes_b, config),\n                    config,\n                );\n                return Some(Expression::new_concatenation(\n                    *first.clone(),\n                    literal,\n                    config,\n                ));\n            }\n        }\n\n        Some(Expression::new_concatenation(\n            expr1.clone(),\n            expr2.clone(),\n            config,\n        ))\n    }\n\n    fn union(\n        a: &Option<Expression<'a>>,\n        b: &Option<Expression<'a>>,\n        config: &'a RegExpConfig,\n    ) -> Option<Expression<'a>> {\n        if let (Some(mut expr1), Some(mut expr2)) = (a.clone(), b.clone()) {\n            if expr1 != expr2 {\n                let common_prefix =\n                    Self::remove_common_substring(&mut expr1, &mut expr2, Substring::Prefix);\n                let common_suffix =\n                    Self::remove_common_substring(&mut expr1, &mut expr2, Substring::Suffix);\n\n                let mut result = if expr1.is_empty() {\n                    Some(Expression::new_repetition(\n                        expr2.clone(),\n                        Quantifier::QuestionMark,\n                        config,\n                    ))\n                } else if expr2.is_empty() {\n                    Some(Expression::new_repetition(\n                        expr1.clone(),\n                        Quantifier::QuestionMark,\n                        config,\n                    ))\n                } else {\n                    None\n                };\n\n                if result.is_none() {\n                    if let Expression::Repetition(expr, quantifier, _, _, _) = &expr1 {\n                        if quantifier == &Quantifier::QuestionMark {\n                            let alternation = Expression::new_alternation(\n                                vec![*expr.clone(), expr2.clone()],\n                                config,\n                            );\n                            result = Some(Expression::new_repetition(\n                                alternation,\n                                Quantifier::QuestionMark,\n                                config,\n                            ));\n                        }\n                    }\n                }\n\n                if result.is_none() {\n                    if let Expression::Repetition(expr, quantifier, _, _, _) = &expr2 {\n                        if quantifier == &Quantifier::QuestionMark {\n                            let alternation = Expression::new_alternation(\n                                vec![expr1.clone(), *expr.clone()],\n                                config,\n                            );\n                            result = Some(Expression::new_repetition(\n                                alternation,\n                                Quantifier::QuestionMark,\n                                config,\n                            ));\n                        }\n                    }\n                }\n\n                if result.is_none() && expr1.is_single_codepoint() && expr2.is_single_codepoint() {\n                    let first_char_set = Self::extract_character_set(expr1.clone());\n                    let second_char_set = Self::extract_character_set(expr2.clone());\n                    result = Some(Expression::new_character_class(\n                        first_char_set,\n                        second_char_set,\n                        config,\n                    ));\n                }\n\n                if result.is_none() {\n                    result = Some(Expression::new_alternation(vec![expr1, expr2], config));\n                }\n\n                if let Some(prefix) = common_prefix {\n                    result = Some(Expression::new_concatenation(\n                        Expression::new_literal(\n                            GraphemeCluster::from_graphemes(prefix, config),\n                            config,\n                        ),\n                        result.unwrap(),\n                        config,\n                    ));\n                }\n\n                if let Some(suffix) = common_suffix {\n                    result = Some(Expression::new_concatenation(\n                        result.unwrap(),\n                        Expression::new_literal(\n                            GraphemeCluster::from_graphemes(suffix, config),\n                            config,\n                        ),\n                        config,\n                    ));\n                }\n\n                result\n            } else if a.is_some() {\n                a.clone()\n            } else if b.is_some() {\n                b.clone()\n            } else {\n                None\n            }\n        } else if a.is_some() {\n            a.clone()\n        } else if b.is_some() {\n            b.clone()\n        } else {\n            None\n        }\n    }\n\n    fn flatten_alternations(\n        flattened_options: &mut Vec<Expression<'a>>,\n        current_options: Vec<Expression<'a>>,\n    ) {\n        for option in current_options {\n            if let Expression::Alternation(expr_options, _, _, _) = option {\n                Self::flatten_alternations(flattened_options, expr_options);\n            } else {\n                flattened_options.push(option);\n            }\n        }\n    }\n\n    fn extract_character_set(expr: Expression) -> BTreeSet<char> {\n        match expr {\n            Expression::Literal(cluster, _, _) => {\n                let single_char = cluster\n                    .graphemes()\n                    .first()\n                    .unwrap()\n                    .value()\n                    .chars()\n                    .next()\n                    .unwrap();\n                btreeset![single_char]\n            }\n            Expression::CharacterClass(char_set, _) => char_set,\n            _ => BTreeSet::new(),\n        }\n    }\n\n    fn remove_common_substring(\n        a: &mut Expression,\n        b: &mut Expression,\n        substring: Substring,\n    ) -> Option<Vec<Grapheme>> {\n        let common_substring = Self::find_common_substring(a, b, &substring);\n        if let Some(value) = &common_substring {\n            a.remove_substring(&substring, value.len());\n            b.remove_substring(&substring, value.len());\n        }\n        common_substring\n    }\n\n    fn find_common_substring(\n        a: &Expression,\n        b: &Expression,\n        substring: &Substring,\n    ) -> Option<Vec<Grapheme>> {\n        let mut graphemes_a = a.value(Some(substring)).unwrap_or_default();\n        let mut graphemes_b = b.value(Some(substring)).unwrap_or_default();\n        let mut common_graphemes = vec![];\n\n        if let Substring::Suffix = substring {\n            graphemes_a.reverse();\n            graphemes_b.reverse();\n        }\n\n        for pair in graphemes_a.iter().zip_longest(graphemes_b.iter()) {\n            match pair {\n                Both(grapheme_a, grapheme_b) => {\n                    if grapheme_a == grapheme_b {\n                        common_graphemes.push(grapheme_a.clone());\n                    } else {\n                        break;\n                    }\n                }\n                _ => break,\n            }\n        }\n\n        if let Substring::Suffix = substring {\n            common_graphemes.reverse();\n        }\n\n        if common_graphemes.is_empty() {\n            None\n        } else {\n            Some(common_graphemes)\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn ensure_correct_string_representation_of_alternation_1() {\n        let config = RegExpConfig::new();\n        let literal1 = Expression::new_literal(GraphemeCluster::from(\"abc\", &config), &config);\n        let literal2 = Expression::new_literal(GraphemeCluster::from(\"def\", &config), &config);\n        let alternation = Expression::new_alternation(vec![literal1, literal2], &config);\n        assert_eq!(alternation.to_string(), \"abc|def\");\n    }\n\n    #[test]\n    fn ensure_correct_string_representation_of_alternation_2() {\n        let config = RegExpConfig::new();\n        let literal1 = Expression::new_literal(GraphemeCluster::from(\"a\", &config), &config);\n        let literal2 = Expression::new_literal(GraphemeCluster::from(\"ab\", &config), &config);\n        let literal3 = Expression::new_literal(GraphemeCluster::from(\"abc\", &config), &config);\n        let alternation = Expression::new_alternation(vec![literal1, literal2, literal3], &config);\n        assert_eq!(alternation.to_string(), \"abc|ab|a\");\n    }\n\n    #[test]\n    fn ensure_correct_string_representation_of_character_class_1() {\n        let config = RegExpConfig::new();\n        let char_class = Expression::new_character_class(btreeset!['a'], btreeset!['b'], &config);\n        assert_eq!(char_class.to_string(), \"[ab]\");\n    }\n\n    #[test]\n    fn ensure_correct_string_representation_of_character_class_2() {\n        let config = RegExpConfig::new();\n        let char_class =\n            Expression::new_character_class(btreeset!['a', 'b'], btreeset!['c'], &config);\n        assert_eq!(char_class.to_string(), \"[a-c]\");\n    }\n\n    #[test]\n    fn ensure_correct_string_representation_of_concatenation_1() {\n        let config = RegExpConfig::new();\n        let literal1 = Expression::new_literal(GraphemeCluster::from(\"abc\", &config), &config);\n        let literal2 = Expression::new_literal(GraphemeCluster::from(\"def\", &config), &config);\n        let concatenation = Expression::new_concatenation(literal1, literal2, &config);\n        assert_eq!(concatenation.to_string(), \"abcdef\");\n    }\n\n    #[test]\n    fn ensure_correct_string_representation_of_concatenation_2() {\n        let config = RegExpConfig::new();\n        let literal1 = Expression::new_literal(GraphemeCluster::from(\"abc\", &config), &config);\n        let literal2 = Expression::new_literal(GraphemeCluster::from(\"def\", &config), &config);\n        let repetition = Expression::new_repetition(literal1, Quantifier::KleeneStar, &config);\n        let concatenation = Expression::new_concatenation(repetition, literal2, &config);\n        assert_eq!(concatenation.to_string(), \"(?:abc)*def\");\n    }\n\n    #[test]\n    fn ensure_correct_removal_of_prefix_in_literal() {\n        let config = RegExpConfig::new();\n        let mut literal =\n            Expression::new_literal(GraphemeCluster::from(\"abcdef\", &config), &config);\n        assert_eq!(\n            literal.value(None),\n            Some(\n                vec![\"a\", \"b\", \"c\", \"d\", \"e\", \"f\"]\n                    .iter()\n                    .map(|&it| Grapheme::from(\n                        it,\n                        config.is_capturing_group_enabled,\n                        config.is_output_colorized,\n                        config.is_verbose_mode_enabled\n                    ))\n                    .collect_vec()\n            )\n        );\n\n        literal.remove_substring(&Substring::Prefix, 2);\n        assert_eq!(\n            literal.value(None),\n            Some(\n                vec![\"c\", \"d\", \"e\", \"f\"]\n                    .iter()\n                    .map(|&it| Grapheme::from(\n                        it,\n                        config.is_capturing_group_enabled,\n                        config.is_output_colorized,\n                        config.is_verbose_mode_enabled\n                    ))\n                    .collect_vec()\n            )\n        );\n    }\n\n    #[test]\n    fn ensure_correct_removal_of_suffix_in_literal() {\n        let config = RegExpConfig::new();\n        let mut literal =\n            Expression::new_literal(GraphemeCluster::from(\"abcdef\", &config), &config);\n        assert_eq!(\n            literal.value(None),\n            Some(\n                vec![\"a\", \"b\", \"c\", \"d\", \"e\", \"f\"]\n                    .iter()\n                    .map(|&it| Grapheme::from(\n                        it,\n                        config.is_capturing_group_enabled,\n                        config.is_output_colorized,\n                        config.is_verbose_mode_enabled\n                    ))\n                    .collect_vec()\n            )\n        );\n\n        literal.remove_substring(&Substring::Suffix, 2);\n        assert_eq!(\n            literal.value(None),\n            Some(\n                vec![\"a\", \"b\", \"c\", \"d\"]\n                    .iter()\n                    .map(|&it| Grapheme::from(\n                        it,\n                        config.is_capturing_group_enabled,\n                        config.is_output_colorized,\n                        config.is_verbose_mode_enabled\n                    ))\n                    .collect_vec()\n            )\n        );\n    }\n\n    #[test]\n    fn ensure_correct_string_representation_of_repetition_1() {\n        let config = RegExpConfig::new();\n        let literal = Expression::new_literal(GraphemeCluster::from(\"abc\", &config), &config);\n        let repetition = Expression::new_repetition(literal, Quantifier::KleeneStar, &config);\n        assert_eq!(repetition.to_string(), \"(?:abc)*\");\n    }\n\n    #[test]\n    fn ensure_correct_string_representation_of_repetition_2() {\n        let config = RegExpConfig::new();\n        let literal = Expression::new_literal(GraphemeCluster::from(\"a\", &config), &config);\n        let repetition = Expression::new_repetition(literal, Quantifier::QuestionMark, &config);\n        assert_eq!(repetition.to_string(), \"a?\");\n    }\n}\n"
  },
  {
    "path": "src/format.rs",
    "content": "/*\n * Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse crate::char_range::CharRange;\nuse crate::cluster::GraphemeCluster;\nuse crate::component::Component;\nuse crate::expression::Expression;\nuse crate::quantifier::Quantifier;\nuse itertools::Itertools;\nuse std::collections::BTreeSet;\nuse std::fmt::{Display, Formatter, Result};\n\nimpl Display for Expression<'_> {\n    fn fmt(&self, f: &mut Formatter<'_>) -> Result {\n        match self {\n            Expression::Alternation(\n                options,\n                is_capturing_group_enabled,\n                is_output_colorized,\n                is_verbose_mode_enabled,\n            ) => format_alternation(\n                f,\n                self,\n                options,\n                *is_capturing_group_enabled,\n                *is_output_colorized,\n                *is_verbose_mode_enabled,\n            ),\n            Expression::CharacterClass(char_set, is_output_colorized) => {\n                format_character_class(f, char_set, *is_output_colorized)\n            }\n            Expression::Concatenation(\n                expr1,\n                expr2,\n                is_capturing_group_enabled,\n                is_output_colorized,\n                is_verbose_mode_enabled,\n            ) => format_concatenation(\n                f,\n                self,\n                expr1,\n                expr2,\n                *is_capturing_group_enabled,\n                *is_output_colorized,\n                *is_verbose_mode_enabled,\n            ),\n            Expression::Literal(\n                cluster,\n                is_non_ascii_char_escaped,\n                is_astral_code_point_converted_to_surrogate,\n            ) => format_literal(\n                f,\n                cluster,\n                *is_non_ascii_char_escaped,\n                *is_astral_code_point_converted_to_surrogate,\n            ),\n            Expression::Repetition(\n                expr,\n                quantifier,\n                is_capturing_group_enabled,\n                is_output_colorized,\n                is_verbose_mode_enabled,\n            ) => format_repetition(\n                f,\n                self,\n                expr,\n                quantifier,\n                *is_capturing_group_enabled,\n                *is_output_colorized,\n                *is_verbose_mode_enabled,\n            ),\n        }\n    }\n}\n\nfn get_codepoint_position(c: char) -> usize {\n    CharRange::all().position(|it| it == c).unwrap()\n}\n\nfn format_alternation(\n    f: &mut Formatter<'_>,\n    expr: &Expression,\n    options: &[Expression],\n    is_capturing_group_enabled: bool,\n    is_output_colorized: bool,\n    is_verbose_mode_enabled: bool,\n) -> Result {\n    let pipe_component = Component::Pipe.to_repr(is_output_colorized);\n    let disjunction_operator = if is_verbose_mode_enabled {\n        format!(\"\\n{}\\n\", pipe_component)\n    } else {\n        pipe_component\n    };\n    let alternation_str = options\n        .iter()\n        .map(|option| {\n            if option.precedence() < expr.precedence() && !option.is_single_codepoint() {\n                if is_capturing_group_enabled {\n                    Component::CapturedParenthesizedExpression(\n                        option.to_string(),\n                        is_verbose_mode_enabled,\n                        true,\n                    )\n                    .to_repr(is_output_colorized)\n                } else {\n                    Component::UncapturedParenthesizedExpression(\n                        option.to_string(),\n                        is_verbose_mode_enabled,\n                        true,\n                    )\n                    .to_repr(is_output_colorized)\n                }\n            } else {\n                format!(\"{}\", option)\n            }\n        })\n        .join(&disjunction_operator);\n\n    write!(f, \"{}\", alternation_str)\n}\n\nfn format_character_class(\n    f: &mut Formatter<'_>,\n    char_set: &BTreeSet<char>,\n    is_output_colorized: bool,\n) -> Result {\n    let chars_to_escape = ['[', ']', '\\\\', '-', '^', '$'];\n    let escaped_char_set = char_set\n        .iter()\n        .map(|c| {\n            if chars_to_escape.contains(c) {\n                format!(\"{}{}\", \"\\\\\", c)\n            } else if c == &'\\n' {\n                \"\\\\n\".to_string()\n            } else if c == &'\\r' {\n                \"\\\\r\".to_string()\n            } else if c == &'\\t' {\n                \"\\\\t\".to_string()\n            } else {\n                c.to_string()\n            }\n        })\n        .collect_vec();\n    let char_positions = char_set\n        .iter()\n        .map(|&it| get_codepoint_position(it))\n        .collect_vec();\n\n    let mut subsets = vec![];\n    let mut subset = vec![];\n\n    for ((first_c, first_pos), (second_c, second_pos)) in\n        escaped_char_set.iter().zip(char_positions).tuple_windows()\n    {\n        if subset.is_empty() {\n            subset.push(first_c);\n        }\n        if second_pos == first_pos + 1 {\n            subset.push(second_c);\n        } else {\n            subsets.push(subset);\n            subset = vec![second_c];\n        }\n    }\n\n    subsets.push(subset);\n\n    let mut char_class_strs = vec![];\n\n    for subset in subsets.iter() {\n        if subset.len() <= 2 {\n            for c in subset.iter() {\n                char_class_strs.push((*c).to_string());\n            }\n        } else {\n            char_class_strs.push(format!(\n                \"{}{}{}\",\n                subset.first().unwrap(),\n                Component::Hyphen.to_repr(is_output_colorized),\n                subset.last().unwrap()\n            ));\n        }\n    }\n\n    write!(\n        f,\n        \"{}{}{}\",\n        Component::LeftBracket.to_repr(is_output_colorized),\n        char_class_strs.join(\"\"),\n        Component::RightBracket.to_repr(is_output_colorized)\n    )\n}\n\nfn format_concatenation(\n    f: &mut Formatter<'_>,\n    expr: &Expression,\n    expr1: &Expression,\n    expr2: &Expression,\n    is_capturing_group_enabled: bool,\n    is_output_colorized: bool,\n    is_verbose_mode_enabled: bool,\n) -> Result {\n    let expr_strs = [expr1, expr2]\n        .iter()\n        .map(|&it| {\n            if it.precedence() < expr.precedence() && !it.is_single_codepoint() {\n                if is_capturing_group_enabled {\n                    Component::CapturedParenthesizedExpression(\n                        it.to_string(),\n                        is_verbose_mode_enabled,\n                        true,\n                    )\n                    .to_repr(is_output_colorized)\n                } else {\n                    Component::UncapturedParenthesizedExpression(\n                        it.to_string(),\n                        is_verbose_mode_enabled,\n                        true,\n                    )\n                    .to_repr(is_output_colorized)\n                }\n            } else {\n                format!(\"{}\", it)\n            }\n        })\n        .collect_vec();\n\n    write!(\n        f,\n        \"{}{}\",\n        expr_strs.first().unwrap(),\n        expr_strs.last().unwrap()\n    )\n}\n\nfn format_literal(\n    f: &mut Formatter<'_>,\n    cluster: &GraphemeCluster,\n    is_non_ascii_char_escaped: bool,\n    is_astral_code_point_converted_to_surrogate: bool,\n) -> Result {\n    let literal_str = cluster\n        .graphemes()\n        .iter()\n        .cloned()\n        .map(|mut grapheme| {\n            if grapheme.has_repetitions() {\n                grapheme\n                    .repetitions_mut()\n                    .iter_mut()\n                    .for_each(|repeated_grapheme| {\n                        repeated_grapheme.escape_regexp_symbols(\n                            is_non_ascii_char_escaped,\n                            is_astral_code_point_converted_to_surrogate,\n                        );\n                    });\n            } else {\n                grapheme.escape_regexp_symbols(\n                    is_non_ascii_char_escaped,\n                    is_astral_code_point_converted_to_surrogate,\n                );\n            }\n            grapheme.to_string()\n        })\n        .join(\"\");\n\n    write!(f, \"{}\", literal_str)\n}\n\nfn format_repetition(\n    f: &mut Formatter<'_>,\n    expr: &Expression,\n    expr1: &Expression,\n    quantifier: &Quantifier,\n    is_capturing_group_enabled: bool,\n    is_output_colorized: bool,\n    is_verbose_mode_enabled: bool,\n) -> Result {\n    if expr1.precedence() < expr.precedence() && !expr1.is_single_codepoint() {\n        if is_capturing_group_enabled {\n            write!(\n                f,\n                \"{}{}\",\n                Component::CapturedParenthesizedExpression(\n                    expr1.to_string(),\n                    is_verbose_mode_enabled,\n                    false\n                )\n                .to_repr(is_output_colorized),\n                Component::Quantifier(quantifier.clone(), is_verbose_mode_enabled)\n                    .to_repr(is_output_colorized)\n            )\n        } else {\n            write!(\n                f,\n                \"{}{}\",\n                Component::UncapturedParenthesizedExpression(\n                    expr1.to_string(),\n                    is_verbose_mode_enabled,\n                    false\n                )\n                .to_repr(is_output_colorized),\n                Component::Quantifier(quantifier.clone(), is_verbose_mode_enabled)\n                    .to_repr(is_output_colorized)\n            )\n        }\n    } else {\n        write!(\n            f,\n            \"{}{}\",\n            expr1,\n            Component::Quantifier(quantifier.clone(), is_verbose_mode_enabled)\n                .to_repr(is_output_colorized)\n        )\n    }\n}\n"
  },
  {
    "path": "src/grapheme.rs",
    "content": "/*\n * Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse crate::component::Component;\nuse itertools::Itertools;\nuse std::fmt::{Display, Formatter, Result};\n\nconst CHARS_TO_ESCAPE: [&str; 14] = [\n    \"(\", \")\", \"[\", \"]\", \"{\", \"}\", \"+\", \"*\", \"-\", \".\", \"?\", \"|\", \"^\", \"$\",\n];\n\nconst CHAR_CLASSES: [&str; 6] = [\"\\\\d\", \"\\\\s\", \"\\\\w\", \"\\\\D\", \"\\\\S\", \"\\\\W\"];\n\n#[derive(Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]\npub(crate) struct Grapheme {\n    pub(crate) chars: Vec<String>,\n    pub(crate) repetitions: Vec<Grapheme>,\n    min: u32,\n    max: u32,\n    is_capturing_group_enabled: bool,\n    is_output_colorized: bool,\n    is_verbose_mode_enabled: bool,\n}\n\nimpl Grapheme {\n    pub(crate) fn from(\n        s: &str,\n        is_capturing_group_enabled: bool,\n        is_output_colorized: bool,\n        is_verbose_mode_enabled: bool,\n    ) -> Self {\n        Self {\n            chars: vec![s.to_string()],\n            repetitions: vec![],\n            min: 1,\n            max: 1,\n            is_capturing_group_enabled,\n            is_output_colorized,\n            is_verbose_mode_enabled,\n        }\n    }\n\n    pub(crate) fn new(\n        chars: Vec<String>,\n        min: u32,\n        max: u32,\n        is_capturing_group_enabled: bool,\n        is_output_colorized: bool,\n        is_verbose_mode_enabled: bool,\n    ) -> Self {\n        Self {\n            chars,\n            repetitions: vec![],\n            min,\n            max,\n            is_capturing_group_enabled,\n            is_output_colorized,\n            is_verbose_mode_enabled,\n        }\n    }\n\n    pub(crate) fn value(&self) -> String {\n        self.chars.join(\"\")\n    }\n\n    pub(crate) fn chars(&self) -> &Vec<String> {\n        &self.chars\n    }\n\n    pub(crate) fn chars_mut(&mut self) -> &mut Vec<String> {\n        &mut self.chars\n    }\n\n    pub(crate) fn has_repetitions(&self) -> bool {\n        !self.repetitions.is_empty()\n    }\n\n    pub(crate) fn repetitions_mut(&mut self) -> &mut Vec<Grapheme> {\n        &mut self.repetitions\n    }\n\n    pub(crate) fn minimum(&self) -> u32 {\n        self.min\n    }\n\n    pub(crate) fn maximum(&self) -> u32 {\n        self.max\n    }\n\n    pub(crate) fn char_count(&self, is_non_ascii_char_escaped: bool) -> usize {\n        if is_non_ascii_char_escaped {\n            self.chars\n                .iter()\n                .map(|it| it.chars().map(|c| self.escape(c, false)).join(\"\"))\n                .join(\"\")\n                .chars()\n                .count()\n        } else {\n            self.chars.iter().map(|it| it.chars().count()).sum()\n        }\n    }\n\n    pub(crate) fn escape_non_ascii_chars(&mut self, use_surrogate_pairs: bool) {\n        self.chars = self\n            .chars\n            .iter()\n            .map(|it| {\n                it.chars()\n                    .map(|c| self.escape(c, use_surrogate_pairs))\n                    .join(\"\")\n            })\n            .collect_vec();\n    }\n\n    pub(crate) fn escape_regexp_symbols(\n        &mut self,\n        is_non_ascii_char_escaped: bool,\n        is_astral_code_point_converted_to_surrogate: bool,\n    ) {\n        let characters = self.chars_mut();\n\n        #[allow(clippy::needless_range_loop)]\n        for i in 0..characters.len() {\n            let mut character = characters[i].clone();\n\n            for char_to_escape in CHARS_TO_ESCAPE.iter() {\n                character =\n                    character.replace(char_to_escape, &format!(\"{}{}\", \"\\\\\", char_to_escape));\n            }\n\n            character = character\n                .replace('\\n', \"\\\\n\")\n                .replace('\\r', \"\\\\r\")\n                .replace('\\t', \"\\\\t\");\n\n            if character == \"\\\\\" {\n                character = \"\\\\\\\\\".to_string();\n            }\n\n            characters[i] = character;\n        }\n\n        if is_non_ascii_char_escaped {\n            self.escape_non_ascii_chars(is_astral_code_point_converted_to_surrogate);\n        }\n    }\n\n    fn escape(&self, c: char, use_surrogate_pairs: bool) -> String {\n        if c.is_ascii() {\n            c.to_string()\n        } else if use_surrogate_pairs && ('\\u{10000}'..'\\u{10ffff}').contains(&c) {\n            self.convert_to_surrogate_pair(c)\n        } else {\n            c.escape_unicode().to_string()\n        }\n    }\n\n    fn convert_to_surrogate_pair(&self, c: char) -> String {\n        c.encode_utf16(&mut [0; 2])\n            .iter()\n            .map(|it| format!(\"\\\\u{{{:x}}}\", it))\n            .join(\"\")\n    }\n}\n\nimpl Display for Grapheme {\n    fn fmt(&self, f: &mut Formatter<'_>) -> Result {\n        let is_single_char = self.char_count(false) == 1\n            || (self.chars.len() == 1 && self.chars[0].matches('\\\\').count() == 1);\n        let is_range = self.min < self.max;\n        let is_repetition = self.min > 1;\n        let mut value = if self.repetitions.is_empty() {\n            self.value()\n        } else {\n            self.repetitions.iter().map(|it| it.to_string()).join(\"\")\n        };\n        value = Component::CharClass(value.clone())\n            .to_repr(self.is_output_colorized && CHAR_CLASSES.contains(&&*value));\n\n        if !is_range && is_repetition && is_single_char {\n            write!(\n                f,\n                \"{}{}\",\n                value,\n                Component::Repetition(self.min, false).to_repr(self.is_output_colorized)\n            )\n        } else if !is_range && is_repetition && !is_single_char {\n            write!(\n                f,\n                \"{}{}\",\n                if self.is_capturing_group_enabled {\n                    Component::CapturedParenthesizedExpression(\n                        value,\n                        self.is_verbose_mode_enabled,\n                        false,\n                    )\n                    .to_repr(self.is_output_colorized)\n                } else {\n                    Component::UncapturedParenthesizedExpression(\n                        value,\n                        self.is_verbose_mode_enabled,\n                        false,\n                    )\n                    .to_repr(self.is_output_colorized)\n                },\n                Component::Repetition(self.min, self.is_verbose_mode_enabled)\n                    .to_repr(self.is_output_colorized)\n            )\n        } else if is_range && is_single_char {\n            write!(\n                f,\n                \"{}{}\",\n                value,\n                Component::RepetitionRange(self.min, self.max, false)\n                    .to_repr(self.is_output_colorized)\n            )\n        } else if is_range && !is_single_char {\n            write!(\n                f,\n                \"{}{}\",\n                if self.is_capturing_group_enabled {\n                    Component::CapturedParenthesizedExpression(\n                        value,\n                        self.is_verbose_mode_enabled,\n                        false,\n                    )\n                    .to_repr(self.is_output_colorized)\n                } else {\n                    Component::UncapturedParenthesizedExpression(\n                        value,\n                        self.is_verbose_mode_enabled,\n                        false,\n                    )\n                    .to_repr(self.is_output_colorized)\n                },\n                Component::RepetitionRange(self.min, self.max, self.is_verbose_mode_enabled)\n                    .to_repr(self.is_output_colorized)\n            )\n        } else {\n            write!(f, \"{}\", value)\n        }\n    }\n}\n"
  },
  {
    "path": "src/lib.rs",
    "content": "/*\n * Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n//! ## 1. What does this tool do?\n//!\n//! *grex* is a library as well as a command-line utility that is meant to simplify the often\n//! complicated and tedious task of creating regular expressions. It does so by automatically\n//! generating a single regular expression from user-provided test cases. The resulting\n//! expression is guaranteed to match the test cases which it was generated from.\n//!\n//! This project has started as a Rust port of the JavaScript tool\n//! [*regexgen*](https://github.com/devongovett/regexgen) written by\n//! [Devon Govett](https://github.com/devongovett). Although a lot of further useful features\n//! could be added to it, its development was apparently ceased several years ago. The plan\n//! is now to add these new features to *grex* as Rust really shines when it comes to\n//! command-line tools. *grex* offers all features that *regexgen* provides, and more.\n//!\n//! The philosophy of this project is to generate the most specific regular expression\n//! possible by default which exactly matches the given input only and nothing else.\n//! With the use of command-line flags (in the CLI tool) or preprocessing methods\n//! (in the library), more generalized expressions can be created.\n//!\n//! The produced expressions are [Perl-compatible regular expressions](https://www.pcre.org)\n//! which are also compatible with the regular expression parser in Rust's\n//! [*regex crate*](https://crates.io/crates/regex).\n//! Other regular expression parsers or respective libraries from other programming languages\n//! have not been tested so far, but they ought to be mostly compatible as well.\n//!\n//! ## 2. Do I still need to learn to write regexes then?\n//!\n//! **Definitely, yes!** Using the standard settings, *grex* produces a regular expression that\n//! is guaranteed to match only the test cases given as input and nothing else. This has been\n//! verified by [property tests](https://github.com/pemistahl/grex/blob/main/tests/property_tests.rs).\n//! However, if the conversion to shorthand character classes such as `\\w` is enabled, the\n//! resulting regex matches a much wider scope of test cases. Knowledge about the consequences of\n//! this conversion is essential for finding a correct regular expression for your business domain.\n//!\n//! *grex* uses an algorithm that tries to find the shortest possible regex for the given test cases.\n//! Very often though, the resulting expression is still longer or more complex than it needs to be.\n//! In such cases, a more compact or elegant regex can be created only by hand.\n//! Also, every regular expression engine has different built-in optimizations.\n//! *grex* does not know anything about those and therefore cannot optimize its regexes\n//! for a specific engine.\n//!\n//! **So, please learn how to write regular expressions!** The currently best use case for *grex*\n//! is to find an initial correct regex which should be inspected by hand if further optimizations\n//! are possible.\n//!\n//! ## 3. Current features\n//!\n//! - literals\n//! - character classes\n//! - detection of common prefixes and suffixes\n//! - detection of repeated substrings and conversion to `{min,max}` quantifier notation\n//! - alternation using `|` operator\n//! - optionality using `?` quantifier\n//! - escaping of non-ascii characters, with optional conversion of astral code points to surrogate pairs\n//! - case-sensitive or case-insensitive matching\n//! - capturing or non-capturing groups\n//! - optional anchors `^` and `$`\n//! - fully compliant to [Unicode Standard 15.0](https://unicode.org/versions/Unicode15.0.0)\n//! - fully compatible with [*regex* crate 1.9.0+](https://crates.io/crates/regex)\n//! - correctly handles graphemes consisting of multiple Unicode symbols\n//! - reads input strings from the command-line or from a file\n//! - produces more readable expressions indented on multiple using optional verbose mode\n//!\n//! ## 4. How to use?\n//!\n//! The code snippets below show how to use the public api.\n//!\n//! For [more detailed examples](https://github.com/pemistahl/grex/tree/main#53-examples), please\n//! take a look at the project's readme file on GitHub.\n//!\n//! ### 4.1 Default settings\n//!\n//! Test cases are passed either from a collection via [`RegExpBuilder::from()`]\n//! or from a file via [`RegExpBuilder::from_file()`].\n//!\n//! ```\n//! use grex::RegExpBuilder;\n//!\n//! let regexp = RegExpBuilder::from(&[\"a\", \"aa\", \"aaa\"]).build();\n//! assert_eq!(regexp, \"^a(?:aa?)?$\");\n//! ```\n//!\n//! ### 4.2 Convert to character classes\n//!\n//! ```\n//! use grex::RegExpBuilder;\n//!\n//! let regexp = RegExpBuilder::from(&[\"a\", \"aa\", \"123\"])\n//!     .with_conversion_of_digits()\n//!     .with_conversion_of_words()\n//!     .build();\n//! assert_eq!(regexp, \"^(?:\\\\d\\\\d\\\\d|\\\\w(?:\\\\w)?)$\");\n//! ```\n//!\n//! ### 4.3 Convert repeated substrings\n//!\n//! ```\n//! use grex::RegExpBuilder;\n//!\n//! let regexp = RegExpBuilder::from(&[\"aa\", \"bcbc\", \"defdefdef\"])\n//!     .with_conversion_of_repetitions()\n//!     .build();\n//! assert_eq!(regexp, \"^(?:a{2}|(?:bc){2}|(?:def){3})$\");\n//! ```\n//!\n//! By default, *grex* converts each substring this way which is at least a single character long\n//! and which is subsequently repeated at least once. You can customize these two parameters\n//! if you like.\n//!\n//! In the following example, the test case `aa` is not converted to `a{2}` because the repeated\n//! substring `a` has a length of 1, but the minimum substring length has been set to 2.\n//!\n//! ```\n//! use grex::RegExpBuilder;\n//!\n//! let regexp = RegExpBuilder::from(&[\"aa\", \"bcbc\", \"defdefdef\"])\n//!     .with_conversion_of_repetitions()\n//!     .with_minimum_substring_length(2)\n//!     .build();\n//! assert_eq!(regexp, \"^(?:aa|(?:bc){2}|(?:def){3})$\");\n//! ```\n//!\n//! Setting a minimum number of 2 repetitions in the next example, only the test case `defdefdef`\n//! will be converted because it is the only one that is repeated twice.\n//!\n//! ```\n//! use grex::RegExpBuilder;\n//!\n//! let regexp = RegExpBuilder::from(&[\"aa\", \"bcbc\", \"defdefdef\"])\n//!     .with_conversion_of_repetitions()\n//!     .with_minimum_repetitions(2)\n//!     .build();\n//! assert_eq!(regexp, \"^(?:bcbc|aa|(?:def){3})$\");\n//! ```\n//!\n//! ### 4.4 Escape non-ascii characters\n//!\n//! ```\n//! use grex::RegExpBuilder;\n//!\n//! let regexp = RegExpBuilder::from(&[\"You smell like 💩.\"])\n//!     .with_escaping_of_non_ascii_chars(false)\n//!     .build();\n//! assert_eq!(regexp, \"^You smell like \\\\u{1f4a9}\\\\.$\");\n//! ```\n//!\n//! Old versions of JavaScript do not support unicode escape sequences for\n//! the astral code planes (range `U+010000` to `U+10FFFF`). In order to\n//! support these symbols in JavaScript regular expressions, the conversion\n//! to surrogate pairs is necessary. More information on that matter can be\n//! found [here](https://mathiasbynens.be/notes/javascript-unicode).\n//!\n//! ```\n//! use grex::RegExpBuilder;\n//!\n//! let regexp = RegExpBuilder::from(&[\"You smell like 💩.\"])\n//!     .with_escaping_of_non_ascii_chars(true)\n//!     .build();\n//! assert_eq!(regexp, \"^You smell like \\\\u{d83d}\\\\u{dca9}\\\\.$\");\n//! ```\n//!\n//! ### 4.5 Case-insensitive matching\n//!\n//! The regular expressions that *grex* generates are case-sensitive by default.\n//! Case-insensitive matching can be enabled like so:\n//!\n//! ```\n//! use grex::RegExpBuilder;\n//!\n//! let regexp = RegExpBuilder::from(&[\"big\", \"BIGGER\"])\n//!     .with_case_insensitive_matching()\n//!     .build();\n//! assert_eq!(regexp, \"(?i)^big(?:ger)?$\");\n//! ```\n//!\n//! ### 4.6 Capturing Groups\n//!\n//! Non-capturing groups are used by default.\n//! Extending the previous example, you can switch to capturing groups instead.\n//!\n//! ```\n//! use grex::RegExpBuilder;\n//!\n//! let regexp = RegExpBuilder::from(&[\"big\", \"BIGGER\"])\n//!     .with_case_insensitive_matching()\n//!     .with_capturing_groups()\n//!     .build();\n//! assert_eq!(regexp, \"(?i)^big(ger)?$\");\n//! ```\n//!\n//! ### 4.7 Verbose mode\n//!\n//! If you find the generated regular expression hard to read, you can enable verbose mode.\n//! The expression is then put on multiple lines and indented to make it more pleasant to the eyes.\n//!\n//! ```\n//! use grex::RegExpBuilder;\n//! use indoc::indoc;\n//!\n//! let regexp = RegExpBuilder::from(&[\"a\", \"b\", \"bcd\"])\n//!     .with_verbose_mode()\n//!     .build();\n//!\n//! assert_eq!(regexp, indoc!(\n//!     r#\"\n//!     (?x)\n//!     ^\n//!       (?:\n//!         b\n//!         (?:\n//!           cd\n//!         )?\n//!         |\n//!         a\n//!       )\n//!     $\"#\n//! ));\n//! ```\n//!\n//! ### 4.8 Disable anchors\n//!\n//! By default, the anchors `^` and `$` are put around every generated regular expression in order\n//! to ensure that it matches only the test cases given as input. Often enough, however, it is\n//! desired to use the generated pattern as part of a larger one. For this purpose, the anchors\n//! can be disabled, either separately or both of them.\n//!\n//! ```\n//! use grex::RegExpBuilder;\n//!\n//! let regexp = RegExpBuilder::from(&[\"a\", \"aa\", \"aaa\"])\n//!     .without_anchors()\n//!     .build();\n//! assert_eq!(regexp, \"a(?:aa?)?\");\n//! ```\n//!\n//! ### 5. How does it work?\n//!\n//! 1. A [deterministic finite automaton](https://en.wikipedia.org/wiki/Deterministic_finite_automaton) (DFA)\n//!    is created from the input strings.\n//!\n//! 2. The number of states and transitions between states in the DFA is reduced by applying\n//!    [Hopcroft's DFA minimization algorithm](https://en.wikipedia.org/wiki/DFA_minimization#Hopcroft.27s_algorithm).\n//!\n//! 3. The minimized DFA is expressed as a system of linear equations which are solved with\n//!    [Brzozowski's algebraic method](http://cs.stackexchange.com/questions/2016/how-to-convert-finite-automata-to-regular-expressions#2392),\n//!    resulting in the final regular expression.\n\n#[macro_use]\nmod macros;\n\nmod builder;\nmod char_range;\nmod cluster;\nmod component;\nmod config;\nmod dfa;\nmod expression;\nmod format;\nmod grapheme;\nmod quantifier;\nmod regexp;\nmod substring;\nmod unicode_tables;\n\n#[cfg(feature = \"python\")]\nmod python;\n\n#[cfg(target_family = \"wasm\")]\nmod wasm;\n\npub use builder::RegExpBuilder;\n\n#[cfg(target_family = \"wasm\")]\npub use wasm::RegExpBuilder as WasmRegExpBuilder;\n"
  },
  {
    "path": "src/macros.rs",
    "content": "/*\n * Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nmacro_rules! btreeset {\n    ( $( $value: expr ),* ) => {{\n        let mut set = std::collections::BTreeSet::new();\n        $( set.insert($value); )*\n        set\n    }};\n}\n"
  },
  {
    "path": "src/main.rs",
    "content": "/*\n * Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#[cfg(not(target_family = \"wasm\"))]\nmod cli {\n    use clap::ArgAction;\n    use clap::Parser;\n    use grex::RegExpBuilder;\n    use itertools::Itertools;\n    use std::io::{stdin, BufRead, Error, ErrorKind, IsTerminal, Read};\n    use std::path::PathBuf;\n\n    #[derive(Parser)]\n    #[command(\n        author = \"© 2019-today Peter M. Stahl <pemistahl@gmail.com>\",\n        about = \"Licensed under the Apache License, Version 2.0\\n\\\n                 Downloadable from https://crates.io/crates/grex\\n\\\n                 Source code at https://github.com/pemistahl/grex\\n\\n\\\n                 grex generates regular expressions from user-provided test cases.\",\n        version,\n        override_usage = \"grex [OPTIONS] {INPUT...|--file <FILE>}\",\n        help_template = \"{name} {version}\\n{author}\\n{about}\\n\\n{usage-heading} {usage}\\n\\n{all-args}\",\n        disable_help_flag = true,\n        disable_version_flag = true\n    )]\n    pub(crate) struct Cli {\n        // --------------------\n        // INPUT\n        // --------------------\n        /// One or more test cases separated by blank space\n        ///\n        /// Use a hyphen `-` to read test cases from standard input.\n        ///\n        /// Conflicts with --file.\n        #[arg(\n            value_name = \"INPUT\",\n            allow_hyphen_values = true,\n            required_unless_present = \"file\",\n            conflicts_with = \"file\",\n            help_heading = \"Input\",\n            display_order = 1\n        )]\n        input: Vec<String>,\n\n        /// Reads test cases on separate lines from a file.\n        ///\n        /// Lines may be ended with either a newline `\\n` or a carriage return with a line feed `\\r\\n`.\n        /// The final line ending is optional.\n        ///\n        /// Use a hyphen `-` to read the filename from standard input.\n        ///\n        /// Conflicts with INPUT...\n        #[arg(\n            name = \"file\",\n            value_name = \"FILE\",\n            short,\n            long,\n            required_unless_present = \"input\",\n            help_heading = \"Input\",\n            display_order = 2\n        )]\n        file_path: Option<PathBuf>,\n\n        // --------------------\n        // DIGIT OPTIONS\n        // --------------------\n        /// Converts any Unicode decimal digit to \\d.\n        ///\n        /// Takes precedence over --words if both are set.\n        /// Decimal digits are converted to \\d, remaining word characters to \\w.\n        ///\n        /// Takes precedence over --non-spaces if both are set.\n        /// Decimal digits are converted to \\d, remaining non-space characters to \\S.\n        #[arg(name = \"digits\", short, long, help_heading = \"Digit Options\")]\n        is_digit_converted: bool,\n\n        /// Converts any character which is not a Unicode decimal digit to \\D.\n        ///\n        /// Takes precedence over --non-words if both are set.\n        /// Non-digits which are also non-word characters are converted to \\D.\n        ///\n        /// Takes precedence over --non-spaces if both are set.\n        /// Non-digits which are also non-space characters are converted to \\D.\n        #[arg(name = \"non-digits\", short = 'D', long, help_heading = \"Digit Options\")]\n        is_non_digit_converted: bool,\n\n        // --------------------\n        // WHITESPACE OPTIONS\n        // --------------------\n        /// Converts any Unicode whitespace character to \\s.\n        ///\n        /// Takes precedence over --non-digits if both are set.\n        /// Whitespace is converted to \\s, remaining non-digits to \\D.\n        ///\n        /// Takes precedence over --non-words if both are set.\n        /// Whitespace is converted to \\s, remaining non-word characters to \\W.\n        #[arg(name = \"spaces\", short, long, help_heading = \"Whitespace Options\")]\n        is_space_converted: bool,\n\n        /// Converts any character which is not a Unicode whitespace character to \\S\n        #[arg(\n            name = \"non-spaces\",\n            short = 'S',\n            long,\n            help_heading = \"Whitespace Options\"\n        )]\n        is_non_space_converted: bool,\n\n        // --------------------\n        // WORD OPTIONS\n        // --------------------\n        /// Converts any Unicode word character to \\w.\n        ///\n        /// Takes precedence over --non-digits if both are set.\n        /// Word characters are converted to \\w, remaining non-digits to \\D.\n        ///\n        /// Takes precedence over --non-spaces if both are set.\n        /// Word characters are converted to \\w, remaining non-whitespace to \\S.\n        #[arg(name = \"words\", short, long, help_heading = \"Word Options\")]\n        is_word_converted: bool,\n\n        /// Converts any character which is not a Unicode word character to \\W.\n        ///\n        /// Takes precedence over --non-spaces if both are set.\n        /// Non-word characters which are also non-whitespace are converted to \\W.\n        #[arg(name = \"non-words\", short = 'W', long, help_heading = \"Word Options\")]\n        is_non_word_converted: bool,\n\n        // --------------------\n        // ESCAPING OPTIONS\n        // --------------------\n        /// Replaces all non-ASCII characters with unicode escape sequences.\n        #[arg(name = \"escape\", short, long, help_heading = \"Escaping Options\")]\n        is_non_ascii_char_escaped: bool,\n\n        /// Converts astral code points to surrogate pairs if --escape is set.\n        #[arg(\n            name = \"with-surrogates\",\n            long,\n            requires = \"escape\",\n            help_heading = \"Escaping Options\"\n        )]\n        is_astral_code_point_converted_to_surrogate: bool,\n\n        // --------------------\n        // REPETITION OPTIONS\n        // --------------------\n        /// Detects repeated non-overlapping substrings and converts them to {min,max} quantifier notation.\n        #[arg(\n            name = \"repetitions\",\n            short,\n            long,\n            help_heading = \"Repetition Options\",\n            display_order = 1\n        )]\n        is_repetition_converted: bool,\n\n        /// Specifies the minimum quantity of substring repetitions to be converted if --repetitions is set.\n        #[arg(\n            name = \"min-repetitions\",\n            value_name = \"QUANTITY\",\n            long,\n            default_value_t = 1,\n            value_parser = repetition_options_parser,\n            help_heading = \"Repetition Options\"\n        )]\n        minimum_repetitions: u32,\n\n        /// Specifies the minimum length a repeated substring must have\n        /// in order to be converted if --repetitions is set.\n        #[arg(\n            name = \"min-substring-length\",\n            value_name = \"LENGTH\",\n            long,\n            default_value_t = 1,\n            value_parser = repetition_options_parser,\n            help_heading = \"Repetition Options\"\n        )]\n        minimum_substring_length: u32,\n\n        // --------------------\n        // ANCHOR OPTIONS\n        // --------------------\n        /// Removes the caret anchor `^` from the resulting regular expression.\n        ///\n        /// By default, the caret anchor is added to every generated regular expression\n        /// which guarantees that the expression matches the test cases\n        /// given as input only at the start of a string.\n        ///\n        /// This flag removes the anchor, thereby allowing to match the test cases\n        /// also when they do not occur at the start of a string.\n        #[arg(name = \"no-start-anchor\", long, help_heading = \"Anchor Options\")]\n        is_caret_anchor_disabled: bool,\n\n        /// Removes the dollar sign anchor `$` from the resulting regular expression.\n        ///\n        /// By default, the dollar sign anchor is added to every generated regular expression\n        /// which guarantees that the expression matches the test cases given as input\n        /// only at the end of a string.\n        ///\n        /// This flag removes the anchor, thereby allowing to match the test cases\n        /// also when they do not occur at the end of a string.\n        #[arg(name = \"no-end-anchor\", long, help_heading = \"Anchor Options\")]\n        is_dollar_sign_anchor_disabled: bool,\n\n        /// Removes the caret and dollar sign anchors from the resulting regular expression.\n        ///\n        /// By default, anchors are added to every generated regular expression\n        /// which guarantees that the expression exactly matches only the test cases given as input\n        /// and nothing else.\n        ///\n        /// This flag removes the anchors, thereby allowing to match the test cases\n        /// also when they occur within a larger string that contains other content as well.\n        #[arg(name = \"no-anchors\", long, help_heading = \"Anchor Options\")]\n        are_anchors_disabled: bool,\n\n        // --------------------\n        // DISPLAY OPTIONS\n        // --------------------\n        /// Produces a nicer-looking regular expression in verbose mode.\n        #[arg(\n            name = \"verbose\",\n            short = 'x',\n            long,\n            help_heading = \"Display Options\",\n            display_order = 1\n        )]\n        is_verbose_mode_enabled: bool,\n\n        /// Provides syntax highlighting for the resulting regular expression.\n        #[arg(name = \"colorize\", short, long, help_heading = \"Display Options\")]\n        is_output_colorized: bool,\n\n        // ---------------------\n        // MISCELLANEOUS OPTIONS\n        // ---------------------\n        /// Performs case-insensitive matching, letters match both upper and lower case.\n        #[arg(\n            name = \"ignore-case\",\n            short,\n            long,\n            help_heading = \"Miscellaneous Options\",\n            display_order = 1\n        )]\n        is_case_ignored: bool,\n\n        /// Replaces non-capturing groups with capturing ones.\n        #[arg(\n            name = \"capture-groups\",\n            short = 'g',\n            long,\n            help_heading = \"Miscellaneous Options\",\n            display_order = 2\n        )]\n        is_group_captured: bool,\n\n        /// Prints help information\n        #[arg(\n            name = \"help\",\n            short = 'h',\n            long,\n            action = ArgAction::Help,\n            help_heading = \"Miscellaneous Options\",\n            display_order = 3\n        )]\n        help: Option<String>,\n\n        /// Prints version information\n        #[arg(\n            name = \"version\",\n            short = 'v',\n            long,\n            action = ArgAction::Version,\n            help_heading = \"Miscellaneous Options\",\n            display_order = 4\n        )]\n        version: Option<String>,\n    }\n\n    pub(crate) fn obtain_input(cli: &Cli) -> Result<Vec<String>, Error> {\n        let is_stdin_available = !stdin().is_terminal();\n\n        if !cli.input.is_empty() {\n            let is_single_item = cli.input.len() == 1;\n            let is_hyphen = cli.input.first().unwrap() == \"-\";\n\n            if is_single_item && is_hyphen && is_stdin_available {\n                Ok(stdin()\n                    .lock()\n                    .lines()\n                    .map(|line| line.unwrap())\n                    .collect_vec())\n            } else {\n                Ok(cli.input.clone())\n            }\n        } else if let Some(file_path) = &cli.file_path {\n            let is_hyphen = file_path.as_os_str() == \"-\";\n            let path = if is_hyphen && is_stdin_available {\n                let mut stdin_file_path = String::new();\n                stdin().read_to_string(&mut stdin_file_path)?;\n                PathBuf::from(stdin_file_path.trim())\n            } else {\n                file_path.to_path_buf()\n            };\n            match std::fs::read_to_string(path) {\n                Ok(file_content) => Ok(file_content.lines().map(|it| it.to_string()).collect_vec()),\n                Err(error) => Err(error),\n            }\n        } else {\n            Err(Error::new(\n                ErrorKind::InvalidInput,\n                \"error: no valid input could be found whatsoever\",\n            ))\n        }\n    }\n\n    pub(crate) fn handle_input(\n        cli: &Cli,\n        input: Result<Vec<String>, Error>,\n    ) -> Result<(), Box<dyn std::error::Error>> {\n        match input {\n            Ok(test_cases) => {\n                let mut builder = RegExpBuilder::from(&test_cases);\n\n                if cli.is_digit_converted {\n                    builder.with_conversion_of_digits();\n                }\n\n                if cli.is_non_digit_converted {\n                    builder.with_conversion_of_non_digits();\n                }\n\n                if cli.is_space_converted {\n                    builder.with_conversion_of_whitespace();\n                }\n\n                if cli.is_non_space_converted {\n                    builder.with_conversion_of_non_whitespace();\n                }\n\n                if cli.is_word_converted {\n                    builder.with_conversion_of_words();\n                }\n\n                if cli.is_non_word_converted {\n                    builder.with_conversion_of_non_words();\n                }\n\n                if cli.is_repetition_converted {\n                    builder.with_conversion_of_repetitions();\n                }\n\n                if cli.is_case_ignored {\n                    builder.with_case_insensitive_matching();\n                }\n\n                if cli.is_group_captured {\n                    builder.with_capturing_groups();\n                }\n\n                if cli.is_non_ascii_char_escaped {\n                    builder.with_escaping_of_non_ascii_chars(\n                        cli.is_astral_code_point_converted_to_surrogate,\n                    );\n                }\n\n                if cli.is_verbose_mode_enabled {\n                    builder.with_verbose_mode();\n                }\n\n                if cli.is_caret_anchor_disabled {\n                    builder.without_start_anchor();\n                }\n\n                if cli.is_dollar_sign_anchor_disabled {\n                    builder.without_end_anchor();\n                }\n\n                if cli.are_anchors_disabled {\n                    builder.without_anchors();\n                }\n\n                if cli.is_output_colorized {\n                    builder.with_syntax_highlighting();\n                }\n\n                builder\n                    .with_minimum_repetitions(cli.minimum_repetitions)\n                    .with_minimum_substring_length(cli.minimum_substring_length);\n\n                let regexp = builder.build();\n\n                println!(\"{}\", regexp);\n                Ok(())\n            }\n            Err(error) => match error.kind() {\n                ErrorKind::NotFound => Err(\"error: the specified file could not be found\".into()),\n                ErrorKind::InvalidData => {\n                    Err(\"error: the specified file's encoding is not valid UTF-8\".into())\n                }\n                ErrorKind::PermissionDenied => {\n                    Err(\"permission denied: the specified file could not be opened\".into())\n                }\n                _ => Err(format!(\"error: {}\", error).into()),\n            },\n        }\n    }\n\n    fn repetition_options_parser(value: &str) -> Result<u32, String> {\n        match value.parse::<u32>() {\n            Ok(parsed_value) => {\n                if parsed_value > 0 {\n                    Ok(parsed_value)\n                } else {\n                    Err(String::from(\"Value must not be zero\"))\n                }\n            }\n            Err(_) => Err(String::from(\"Value is not a valid unsigned integer\")),\n        }\n    }\n}\n\n#[cfg(not(target_family = \"wasm\"))]\nfn main() {\n    use clap::Parser;\n    let cli = cli::Cli::parse();\n    if let Err(e) = cli::handle_input(&cli, cli::obtain_input(&cli)) {\n        eprintln!(\"{}\", e);\n        std::process::exit(1);\n    }\n}\n\n#[cfg(target_family = \"wasm\")]\nfn main() {}\n"
  },
  {
    "path": "src/python.rs",
    "content": "/*\n * Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse crate::builder::{\n    RegExpBuilder, MINIMUM_REPETITIONS_MESSAGE, MINIMUM_SUBSTRING_LENGTH_MESSAGE,\n    MISSING_TEST_CASES_MESSAGE,\n};\nuse crate::config::RegExpConfig;\nuse pyo3::exceptions::PyValueError;\nuse pyo3::prelude::*;\nuse pyo3::types::PyType;\nuse regex::{Captures, Regex};\nuse std::sync::LazyLock;\n\n#[pymodule]\nfn grex(m: &Bound<'_, PyModule>) -> PyResult<()> {\n    m.add_class::<RegExpBuilder>()?;\n    Ok(())\n}\n\n#[pymethods]\nimpl RegExpBuilder {\n    #[new]\n    fn new(test_cases: Vec<String>) -> PyResult<Self> {\n        if test_cases.is_empty() {\n            Err(PyValueError::new_err(MISSING_TEST_CASES_MESSAGE))\n        } else {\n            Ok(Self {\n                test_cases,\n                config: RegExpConfig::new(),\n            })\n        }\n    }\n\n    /// Specify the test cases to build the regular expression from.\n    ///\n    /// The test cases need not be sorted because `RegExpBuilder` sorts them internally.\n    ///\n    /// Args:\n    ///     test_cases (list[str]): The list of test cases\n    ///\n    /// Raises:\n    ///     ValueError: if `test_cases` is empty\n    #[classmethod]\n    fn from_test_cases(_cls: &Bound<PyType>, test_cases: Vec<String>) -> PyResult<Self> {\n        Self::new(test_cases)\n    }\n\n    /// Convert any Unicode decimal digit to character class `\\d`.\n    ///\n    /// This method takes precedence over `with_conversion_of_words` if both are set.\n    /// Decimal digits are converted to `\\d`, the remaining word characters to `\\w`.\n    ///\n    /// This method takes precedence over `with_conversion_of_non_whitespace` if both are set.\n    /// Decimal digits are converted to `\\d`, the remaining non-whitespace characters to `\\S`.\n    #[pyo3(name = \"with_conversion_of_digits\")]\n    fn py_with_conversion_of_digits(mut self_: PyRefMut<Self>) -> PyRefMut<Self> {\n        self_.config.is_digit_converted = true;\n        self_\n    }\n\n    /// Convert any character which is not a Unicode decimal digit to character class `\\D`.\n    ///\n    /// This method takes precedence over `with_conversion_of_non_words` if both are set.\n    /// Non-digits which are also non-word characters are converted to `\\D`.\n    ///\n    /// This method takes precedence over `with_conversion_of_non_whitespace` if both are set.\n    /// Non-digits which are also non-space characters are converted to `\\D`.\n    #[pyo3(name = \"with_conversion_of_non_digits\")]\n    fn py_with_conversion_of_non_digits(mut self_: PyRefMut<Self>) -> PyRefMut<Self> {\n        self_.config.is_non_digit_converted = true;\n        self_\n    }\n\n    /// Convert any Unicode whitespace character to character class `\\s`.\n    ///\n    /// This method takes precedence over `with_conversion_of_non_digits` if both are set.\n    /// Whitespace characters are converted to `\\s`, the remaining non-digit characters to `\\D`.\n    ///\n    /// This method takes precedence over `with_conversion_of_non_words` if both are set.\n    /// Whitespace characters are converted to `\\s`, the remaining non-word characters to `\\W`.\n    #[pyo3(name = \"with_conversion_of_whitespace\")]\n    fn py_with_conversion_of_whitespace(mut self_: PyRefMut<Self>) -> PyRefMut<Self> {\n        self_.config.is_space_converted = true;\n        self_\n    }\n\n    /// Convert any character which is not a Unicode whitespace character to character class `\\S`.\n    #[pyo3(name = \"with_conversion_of_non_whitespace\")]\n    fn py_with_conversion_of_non_whitespace(mut self_: PyRefMut<Self>) -> PyRefMut<Self> {\n        self_.config.is_non_space_converted = true;\n        self_\n    }\n\n    /// Convert any Unicode word character to character class `\\w`.\n    ///\n    /// This method takes precedence over `with_conversion_of_non_digits` if both are set.\n    /// Word characters are converted to `\\w`, the remaining non-digit characters to `\\D`.\n    ///\n    /// This method takes precedence over `with_conversion_of_non_whitespace` if both are set.\n    /// Word characters are converted to `\\w`, the remaining non-space characters to `\\S`.\n    #[pyo3(name = \"with_conversion_of_words\")]\n    fn py_with_conversion_of_words(mut self_: PyRefMut<Self>) -> PyRefMut<Self> {\n        self_.config.is_word_converted = true;\n        self_\n    }\n\n    /// Convert any character which is not a Unicode word character to character class `\\W`.\n    ///\n    /// This method takes precedence over `with_conversion_of_non_whitespace` if both are set.\n    /// Non-words which are also non-space characters are converted to `\\W`.\n    #[pyo3(name = \"with_conversion_of_non_words\")]\n    fn py_with_conversion_of_non_words(mut self_: PyRefMut<Self>) -> PyRefMut<Self> {\n        self_.config.is_non_word_converted = true;\n        self_\n    }\n\n    /// Detect repeated non-overlapping substrings and convert them to `{min,max}` quantifier notation.\n    #[pyo3(name = \"with_conversion_of_repetitions\")]\n    fn py_with_conversion_of_repetitions(mut self_: PyRefMut<Self>) -> PyRefMut<Self> {\n        self_.config.is_repetition_converted = true;\n        self_\n    }\n\n    /// Enable case-insensitive matching of test cases so that letters match both upper and lower case.\n    #[pyo3(name = \"with_case_insensitive_matching\")]\n    fn py_with_case_insensitive_matching(mut self_: PyRefMut<Self>) -> PyRefMut<Self> {\n        self_.config.is_case_insensitive_matching = true;\n        self_\n    }\n\n    /// Replace non-capturing groups by capturing ones.\n    #[pyo3(name = \"with_capturing_groups\")]\n    fn py_with_capturing_groups(mut self_: PyRefMut<Self>) -> PyRefMut<Self> {\n        self_.config.is_capturing_group_enabled = true;\n        self_\n    }\n\n    /// Specify the minimum quantity of substring repetitions to be converted if `with_conversion_of_repetitions` is set.\n    ///\n    /// If the quantity is not explicitly set with this method, a default value of 1 will be used.\n    ///\n    /// Args:\n    ///     quantity (int): The minimum quantity of substring repetitions\n    ///\n    /// Raises:\n    ///     ValueError: if `quantity` is zero\n    #[pyo3(name = \"with_minimum_repetitions\")]\n    fn py_with_minimum_repetitions(\n        mut self_: PyRefMut<Self>,\n        quantity: i32,\n    ) -> PyResult<PyRefMut<Self>> {\n        if quantity <= 0 {\n            Err(PyValueError::new_err(MINIMUM_REPETITIONS_MESSAGE))\n        } else {\n            self_.config.minimum_repetitions = quantity as u32;\n            Ok(self_)\n        }\n    }\n\n    /// Specify the minimum length a repeated substring must have in order to be converted if `with_conversion_of_repetitions` is set.\n    ///\n    /// If the length is not explicitly set with this method, a default value of 1 will be used.\n    ///\n    /// Args:\n    ///     length (int): The minimum substring length\n    ///\n    /// Raises:\n    ///     ValueError: if `length` is zero\n    #[pyo3(name = \"with_minimum_substring_length\")]\n    fn py_with_minimum_substring_length(\n        mut self_: PyRefMut<Self>,\n        length: i32,\n    ) -> PyResult<PyRefMut<Self>> {\n        if length <= 0 {\n            Err(PyValueError::new_err(MINIMUM_SUBSTRING_LENGTH_MESSAGE))\n        } else {\n            self_.config.minimum_substring_length = length as u32;\n            Ok(self_)\n        }\n    }\n\n    /// Convert non-ASCII characters to unicode escape sequences.\n    ///\n    /// The parameter `use_surrogate_pairs` specifies whether to convert astral code planes\n    /// (range `U+010000` to `U+10FFFF`) to surrogate pairs.\n    ///\n    /// Args:\n    ///     use_surrogate_pairs (bool): Whether to convert astral code planes to surrogate pairs\n    #[pyo3(name = \"with_escaping_of_non_ascii_chars\")]\n    fn py_with_escaping_of_non_ascii_chars(\n        mut self_: PyRefMut<Self>,\n        use_surrogate_pairs: bool,\n    ) -> PyRefMut<Self> {\n        self_.config.is_non_ascii_char_escaped = true;\n        self_.config.is_astral_code_point_converted_to_surrogate = use_surrogate_pairs;\n        self_\n    }\n\n    /// Produce a nicer looking regular expression in verbose mode.\n    #[pyo3(name = \"with_verbose_mode\")]\n    fn py_with_verbose_mode(mut self_: PyRefMut<Self>) -> PyRefMut<Self> {\n        self_.config.is_verbose_mode_enabled = true;\n        self_\n    }\n\n    /// Remove the caret anchor '^' from the resulting regular expression, thereby allowing to\n    /// match the test cases also when they do not occur at the start of a string.\n    #[pyo3(name = \"without_start_anchor\")]\n    fn py_without_start_anchor(mut self_: PyRefMut<Self>) -> PyRefMut<Self> {\n        self_.config.is_start_anchor_disabled = true;\n        self_\n    }\n\n    /// Remove the dollar sign anchor '$' from the resulting regular expression, thereby allowing\n    /// to match the test cases also when they do not occur at the end of a string.\n    #[pyo3(name = \"without_end_anchor\")]\n    fn py_without_end_anchor(mut self_: PyRefMut<Self>) -> PyRefMut<Self> {\n        self_.config.is_end_anchor_disabled = true;\n        self_\n    }\n\n    /// Remove the caret and dollar sign anchors from the resulting regular expression, thereby\n    /// allowing to match the test cases also when they occur within a larger string that contains\n    /// other content as well.\n    #[pyo3(name = \"without_anchors\")]\n    fn py_without_anchors(mut self_: PyRefMut<Self>) -> PyRefMut<Self> {\n        self_.config.is_start_anchor_disabled = true;\n        self_.config.is_end_anchor_disabled = true;\n        self_\n    }\n\n    /// Build the actual regular expression using the previously given settings.\n    #[pyo3(name = \"build\")]\n    fn py_build(&mut self) -> String {\n        let regexp = self.build();\n        if self.config.is_non_ascii_char_escaped {\n            replace_unicode_escape_sequences(regexp)\n        } else {\n            regexp\n        }\n    }\n}\n\n/// Replaces Rust Unicode escape sequences with Python Unicode escape sequences.\nfn replace_unicode_escape_sequences(regexp: String) -> String {\n    static FOUR_CHARS_ESCAPE_SEQUENCE: LazyLock<Regex> =\n        LazyLock::new(|| Regex::new(r\"\\\\u\\{([0-9a-f]{4})\\}\").unwrap());\n\n    static FIVE_CHARS_ESCAPE_SEQUENCE: LazyLock<Regex> =\n        LazyLock::new(|| Regex::new(r\"\\\\u\\{([0-9a-f]{5})\\}\").unwrap());\n\n    let mut replacement = FOUR_CHARS_ESCAPE_SEQUENCE\n        .replace_all(&regexp, |caps: &Captures| format!(\"\\\\u{}\", &caps[1]))\n        .to_string();\n\n    replacement = FIVE_CHARS_ESCAPE_SEQUENCE\n        .replace_all(&replacement, |caps: &Captures| {\n            format!(\"\\\\U000{}\", &caps[1])\n        })\n        .to_string();\n\n    replacement\n}\n"
  },
  {
    "path": "src/quantifier.rs",
    "content": "/*\n * Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse std::fmt::{Display, Formatter, Result};\n\n#[derive(Clone, Debug, Eq, PartialEq)]\npub(crate) enum Quantifier {\n    KleeneStar,\n    QuestionMark,\n}\n\nimpl Display for Quantifier {\n    fn fmt(&self, f: &mut Formatter<'_>) -> Result {\n        write!(\n            f,\n            \"{}\",\n            match self {\n                Quantifier::KleeneStar => '*',\n                Quantifier::QuestionMark => '?',\n            }\n        )\n    }\n}\n"
  },
  {
    "path": "src/regexp.rs",
    "content": "/*\n * Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse crate::cluster::GraphemeCluster;\nuse crate::component::Component;\nuse crate::config::RegExpConfig;\nuse crate::dfa::Dfa;\nuse crate::expression::Expression;\nuse itertools::Itertools;\nuse regex::Regex;\nuse std::cmp::Ordering;\nuse std::fmt::{Display, Formatter, Result};\n\npub(crate) struct RegExp<'a> {\n    ast: Expression<'a>,\n    config: &'a RegExpConfig,\n}\n\nimpl<'a> RegExp<'a> {\n    pub(crate) fn from(test_cases: &'a mut Vec<String>, config: &'a RegExpConfig) -> Self {\n        if config.is_case_insensitive_matching {\n            Self::convert_for_case_insensitive_matching(test_cases);\n        }\n        Self::sort(test_cases);\n        let grapheme_clusters = Self::grapheme_clusters(test_cases, config);\n        let mut dfa = Dfa::from(&grapheme_clusters, true, config);\n        let mut ast = Expression::from(dfa, config);\n\n        if config.is_start_anchor_disabled && config.is_end_anchor_disabled {\n            let mut regex = Self::convert_expr_to_regex(&ast, config);\n\n            if config.is_verbose_mode_enabled {\n                // Remove line breaks before checking matches, otherwise check will be incorrect.\n                regex = Regex::new(&regex.to_string().replace('\\n', \"\")).unwrap();\n            }\n\n            if !Self::is_each_test_case_matched_after_rotating_alternations(\n                &regex, &mut ast, test_cases,\n            ) {\n                dfa = Dfa::from(&grapheme_clusters, false, config);\n                ast = Expression::from(dfa, config);\n                regex = Self::convert_expr_to_regex(&ast, config);\n\n                if !Self::regex_matches_all_test_cases(&regex, test_cases) {\n                    let mut exprs = vec![];\n                    for cluster in grapheme_clusters {\n                        let literal = Expression::new_literal(cluster, config);\n                        exprs.push(literal);\n                    }\n                    ast = Expression::new_alternation(exprs, config);\n                }\n            }\n        }\n\n        Self { ast, config }\n    }\n\n    fn convert_for_case_insensitive_matching(test_cases: &mut Vec<String>) {\n        // Convert only those test cases to lowercase if\n        // they keep their original number of characters.\n        // Otherwise, \"İ\" -> \"i\\u{307}\" would not match \"İ\".\n        *test_cases = test_cases\n            .iter()\n            .map(|it| {\n                let lower_test_case = it.to_lowercase();\n                if lower_test_case.chars().count() == it.chars().count() {\n                    lower_test_case\n                } else {\n                    it.to_string()\n                }\n            })\n            .collect_vec();\n    }\n\n    fn convert_expr_to_regex(expr: &Expression, config: &RegExpConfig) -> Regex {\n        if config.is_output_colorized {\n            let color_replace_regex = Regex::new(\"\\u{1b}\\\\[(?:\\\\d+;\\\\d+|0)m\").unwrap();\n            Regex::new(&color_replace_regex.replace_all(&expr.to_string(), \"\")).unwrap()\n        } else {\n            Regex::new(&expr.to_string()).unwrap()\n        }\n    }\n\n    fn regex_matches_all_test_cases(regex: &Regex, test_cases: &[String]) -> bool {\n        test_cases\n            .iter()\n            .all(|test_case| regex.find_iter(test_case).count() == 1)\n    }\n\n    fn sort(test_cases: &mut Vec<String>) {\n        test_cases.sort();\n        test_cases.dedup();\n        test_cases.sort_by(|a, b| match a.len().cmp(&b.len()) {\n            Ordering::Equal => a.cmp(b),\n            other => other,\n        });\n    }\n\n    fn grapheme_clusters(\n        test_cases: &'a [String],\n        config: &'a RegExpConfig,\n    ) -> Vec<GraphemeCluster<'a>> {\n        let mut clusters = test_cases\n            .iter()\n            .map(|it| GraphemeCluster::from(it, config))\n            .collect_vec();\n\n        if config.is_char_class_feature_enabled() {\n            for cluster in clusters.iter_mut() {\n                cluster.convert_to_char_classes();\n            }\n        }\n\n        if config.is_repetition_converted {\n            for cluster in clusters.iter_mut() {\n                cluster.convert_repetitions();\n            }\n        }\n\n        clusters\n    }\n\n    fn is_each_test_case_matched_after_rotating_alternations(\n        regex: &Regex,\n        expr: &mut Expression,\n        test_cases: &[String],\n    ) -> bool {\n        for _ in 1..test_cases.len() {\n            if Self::regex_matches_all_test_cases(regex, test_cases) {\n                return true;\n            } else if let Expression::Alternation(options, _, _, _) = expr {\n                options.rotate_right(1);\n            } else if let Expression::Concatenation(first, second, _, _, _) = expr {\n                let a: &mut Expression = first;\n                let b: &mut Expression = second;\n\n                if let Expression::Alternation(options, _, _, _) = a {\n                    options.rotate_right(1);\n                } else if let Expression::Alternation(options, _, _, _) = b {\n                    options.rotate_right(1);\n                }\n            }\n        }\n        false\n    }\n}\n\nimpl Display for RegExp<'_> {\n    fn fmt(&self, f: &mut Formatter<'_>) -> Result {\n        let flag =\n            if self.config.is_case_insensitive_matching && self.config.is_verbose_mode_enabled {\n                Component::IgnoreCaseAndVerboseModeFlag.to_repr(self.config.is_output_colorized)\n            } else if self.config.is_case_insensitive_matching {\n                Component::IgnoreCaseFlag.to_repr(self.config.is_output_colorized)\n            } else if self.config.is_verbose_mode_enabled {\n                Component::VerboseModeFlag.to_repr(self.config.is_output_colorized)\n            } else {\n                String::new()\n            };\n\n        let caret = if self.config.is_start_anchor_disabled {\n            String::new()\n        } else {\n            Component::Caret(self.config.is_verbose_mode_enabled)\n                .to_repr(self.config.is_output_colorized)\n        };\n\n        let dollar_sign = if self.config.is_end_anchor_disabled {\n            String::new()\n        } else {\n            Component::DollarSign(self.config.is_verbose_mode_enabled)\n                .to_repr(self.config.is_output_colorized)\n        };\n\n        let mut regexp = match self.ast {\n            Expression::Alternation(_, _, _, _) => {\n                format!(\n                    \"{}{}{}{}\",\n                    flag,\n                    caret,\n                    if self.config.is_capturing_group_enabled {\n                        Component::CapturedParenthesizedExpression(\n                            self.ast.to_string(),\n                            self.config.is_verbose_mode_enabled,\n                            false,\n                        )\n                        .to_repr(self.config.is_output_colorized)\n                    } else {\n                        Component::UncapturedParenthesizedExpression(\n                            self.ast.to_string(),\n                            self.config.is_verbose_mode_enabled,\n                            false,\n                        )\n                        .to_repr(self.config.is_output_colorized)\n                    },\n                    dollar_sign\n                )\n            }\n            _ => {\n                format!(\"{}{}{}{}\", flag, caret, self.ast, dollar_sign)\n            }\n        };\n\n        regexp = regexp\n            .replace('\\u{b}', \"\\\\v\") // U+000B Line Tabulation\n            .replace('\\u{c}', \"\\\\f\"); // U+000C Form Feed\n\n        if self.config.is_verbose_mode_enabled {\n            regexp = regexp\n                .replace('#', \"\\\\#\")\n                .replace(\n                    [\n                        ' ', ' ', ' ', ' ', ' ', ' ', ' ', '\\u{85}', '\\u{a0}', '\\u{1680}',\n                        '\\u{2000}', '\\u{2001}', '\\u{2002}', '\\u{2003}', '\\u{2004}', '\\u{2005}',\n                        '\\u{2006}', '\\u{2007}', '\\u{2008}', '\\u{2009}', '\\u{200a}', '\\u{2028}',\n                        '\\u{2029}', '\\u{202f}', '\\u{205f}', '\\u{3000}',\n                    ],\n                    \"\\\\s\",\n                )\n                .replace(' ', \"\\\\ \");\n        }\n\n        write!(\n            f,\n            \"{}\",\n            if self.config.is_verbose_mode_enabled {\n                indent_regexp(regexp, self.config)\n            } else {\n                regexp\n            }\n        )\n    }\n}\n\nfn indent_regexp(regexp: String, config: &RegExpConfig) -> String {\n    let mut indented_regexp = vec![];\n    let mut nesting_level = 0;\n\n    for (i, line) in regexp.lines().enumerate() {\n        if i == 1 && config.is_start_anchor_disabled {\n            nesting_level += 1;\n        }\n        if line.is_empty() {\n            continue;\n        }\n\n        let is_colored_line = line.starts_with(\"\\u{1b}[\");\n\n        if nesting_level > 0\n            && ((is_colored_line && (line.contains('$') || line.contains(')')))\n                || (line == \"$\" || line.starts_with(')')))\n        {\n            nesting_level -= 1;\n        }\n\n        let indentation = \"  \".repeat(nesting_level);\n        indented_regexp.push(format!(\"{indentation}{line}\"));\n\n        if (is_colored_line && (line.contains('^') || (i > 0 && line.contains('('))))\n            || (line == \"^\" || (i > 0 && line.starts_with('(')))\n        {\n            nesting_level += 1;\n        }\n    }\n\n    indented_regexp.join(\"\\n\")\n}\n"
  },
  {
    "path": "src/substring.rs",
    "content": "/*\n * Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npub(crate) enum Substring {\n    Prefix,\n    Suffix,\n}\n"
  },
  {
    "path": "src/unicode_tables/decimal.rs",
    "content": "/*\n * Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// DO NOT EDIT THIS FILE. IT WAS AUTOMATICALLY GENERATED BY:\n//\n//   ucd-generate general-category ucd-16.0.0 --chars --include decimalnumber\n//\n// Unicode version: 16.0.0.\n//\n// ucd-generate 0.3.1 is available on crates.io.\n\npub const DECIMAL_NUMBER: &[(char, char)] = &[\n    ('0', '9'),\n    ('٠', '٩'),\n    ('۰', '۹'),\n    ('߀', '߉'),\n    ('०', '९'),\n    ('০', '৯'),\n    ('੦', '੯'),\n    ('૦', '૯'),\n    ('୦', '୯'),\n    ('௦', '௯'),\n    ('౦', '౯'),\n    ('೦', '೯'),\n    ('൦', '൯'),\n    ('෦', '෯'),\n    ('๐', '๙'),\n    ('໐', '໙'),\n    ('༠', '༩'),\n    ('၀', '၉'),\n    ('႐', '႙'),\n    ('០', '៩'),\n    ('᠐', '᠙'),\n    ('᥆', '᥏'),\n    ('᧐', '᧙'),\n    ('᪀', '᪉'),\n    ('᪐', '᪙'),\n    ('᭐', '᭙'),\n    ('᮰', '᮹'),\n    ('᱀', '᱉'),\n    ('᱐', '᱙'),\n    ('꘠', '꘩'),\n    ('꣐', '꣙'),\n    ('꤀', '꤉'),\n    ('꧐', '꧙'),\n    ('꧰', '꧹'),\n    ('꩐', '꩙'),\n    ('꯰', '꯹'),\n    ('０', '９'),\n    ('𐒠', '𐒩'),\n    ('𐴰', '𐴹'),\n    ('𐵀', '𐵉'),\n    ('𑁦', '𑁯'),\n    ('𑃰', '𑃹'),\n    ('𑄶', '𑄿'),\n    ('𑇐', '𑇙'),\n    ('𑋰', '𑋹'),\n    ('𑑐', '𑑙'),\n    ('𑓐', '𑓙'),\n    ('𑙐', '𑙙'),\n    ('𑛀', '𑛉'),\n    ('𑛐', '𑛣'),\n    ('𑜰', '𑜹'),\n    ('𑣠', '𑣩'),\n    ('𑥐', '𑥙'),\n    ('𑯰', '𑯹'),\n    ('𑱐', '𑱙'),\n    ('𑵐', '𑵙'),\n    ('𑶠', '𑶩'),\n    ('𑽐', '𑽙'),\n    ('𖄰', '𖄹'),\n    ('𖩠', '𖩩'),\n    ('𖫀', '𖫉'),\n    ('𖭐', '𖭙'),\n    ('𖵰', '𖵹'),\n    ('𜳰', '𜳹'),\n    ('𝟎', '𝟿'),\n    ('𞅀', '𞅉'),\n    ('𞋰', '𞋹'),\n    ('𞓰', '𞓹'),\n    ('𞗱', '𞗺'),\n    ('𞥐', '𞥙'),\n    ('🯰', '🯹'),\n];\n"
  },
  {
    "path": "src/unicode_tables/mod.rs",
    "content": "/*\n * Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nmod decimal;\nmod space;\nmod word;\n\npub use decimal::DECIMAL_NUMBER;\npub use space::WHITE_SPACE;\npub use word::WORD;\n"
  },
  {
    "path": "src/unicode_tables/space.rs",
    "content": "/*\n * Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// DO NOT EDIT THIS FILE. IT WAS AUTOMATICALLY GENERATED BY:\n//\n//   ucd-generate property-bool ucd-16.0.0 --chars --include whitespace\n//\n// Unicode version: 16.0.0.\n//\n// ucd-generate 0.3.1 is available on crates.io.\n\npub const WHITE_SPACE: &[(char, char)] = &[\n    ('\\t', '\\r'),\n    (' ', ' '),\n    ('\\u{85}', '\\u{85}'),\n    ('\\u{a0}', '\\u{a0}'),\n    ('\\u{1680}', '\\u{1680}'),\n    ('\\u{2000}', '\\u{200a}'),\n    ('\\u{2028}', '\\u{2029}'),\n    ('\\u{202f}', '\\u{202f}'),\n    ('\\u{205f}', '\\u{205f}'),\n    ('\\u{3000}', '\\u{3000}'),\n];\n"
  },
  {
    "path": "src/unicode_tables/word.rs",
    "content": "/*\n * Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// DO NOT EDIT THIS FILE. IT WAS AUTOMATICALLY GENERATED BY:\n//\n//   ucd-generate perl-word ucd-16.0.0 --chars\n//\n// Unicode version: 16.0.0.\n//\n// ucd-generate 0.3.1 is available on crates.io.\n\npub const WORD: &[(char, char)] = &[\n    ('0', '9'),\n    ('A', 'Z'),\n    ('_', '_'),\n    ('a', 'z'),\n    ('ª', 'ª'),\n    ('µ', 'µ'),\n    ('º', 'º'),\n    ('À', 'Ö'),\n    ('Ø', 'ö'),\n    ('ø', 'ˁ'),\n    ('ˆ', 'ˑ'),\n    ('ˠ', 'ˤ'),\n    ('ˬ', 'ˬ'),\n    ('ˮ', 'ˮ'),\n    ('\\u{300}', 'ʹ'),\n    ('Ͷ', 'ͷ'),\n    ('ͺ', 'ͽ'),\n    ('Ϳ', 'Ϳ'),\n    ('Ά', 'Ά'),\n    ('Έ', 'Ί'),\n    ('Ό', 'Ό'),\n    ('Ύ', 'Ρ'),\n    ('Σ', 'ϵ'),\n    ('Ϸ', 'ҁ'),\n    ('\\u{483}', 'ԯ'),\n    ('Ա', 'Ֆ'),\n    ('ՙ', 'ՙ'),\n    ('ՠ', 'ֈ'),\n    ('\\u{591}', '\\u{5bd}'),\n    ('\\u{5bf}', '\\u{5bf}'),\n    ('\\u{5c1}', '\\u{5c2}'),\n    ('\\u{5c4}', '\\u{5c5}'),\n    ('\\u{5c7}', '\\u{5c7}'),\n    ('א', 'ת'),\n    ('ׯ', 'ײ'),\n    ('\\u{610}', '\\u{61a}'),\n    ('ؠ', '٩'),\n    ('ٮ', 'ۓ'),\n    ('ە', '\\u{6dc}'),\n    ('\\u{6df}', '\\u{6e8}'),\n    ('\\u{6ea}', 'ۼ'),\n    ('ۿ', 'ۿ'),\n    ('ܐ', '\\u{74a}'),\n    ('ݍ', 'ޱ'),\n    ('߀', 'ߵ'),\n    ('ߺ', 'ߺ'),\n    ('\\u{7fd}', '\\u{7fd}'),\n    ('ࠀ', '\\u{82d}'),\n    ('ࡀ', '\\u{85b}'),\n    ('ࡠ', 'ࡪ'),\n    ('ࡰ', 'ࢇ'),\n    ('ࢉ', 'ࢎ'),\n    ('\\u{897}', '\\u{8e1}'),\n    ('\\u{8e3}', '\\u{963}'),\n    ('०', '९'),\n    ('ॱ', 'ঃ'),\n    ('অ', 'ঌ'),\n    ('এ', 'ঐ'),\n    ('ও', 'ন'),\n    ('প', 'র'),\n    ('ল', 'ল'),\n    ('শ', 'হ'),\n    ('\\u{9bc}', '\\u{9c4}'),\n    ('ে', 'ৈ'),\n    ('ো', 'ৎ'),\n    ('\\u{9d7}', '\\u{9d7}'),\n    ('ড়', 'ঢ়'),\n    ('য়', '\\u{9e3}'),\n    ('০', 'ৱ'),\n    ('ৼ', 'ৼ'),\n    ('\\u{9fe}', '\\u{9fe}'),\n    ('\\u{a01}', 'ਃ'),\n    ('ਅ', 'ਊ'),\n    ('ਏ', 'ਐ'),\n    ('ਓ', 'ਨ'),\n    ('ਪ', 'ਰ'),\n    ('ਲ', 'ਲ਼'),\n    ('ਵ', 'ਸ਼'),\n    ('ਸ', 'ਹ'),\n    ('\\u{a3c}', '\\u{a3c}'),\n    ('ਾ', '\\u{a42}'),\n    ('\\u{a47}', '\\u{a48}'),\n    ('\\u{a4b}', '\\u{a4d}'),\n    ('\\u{a51}', '\\u{a51}'),\n    ('ਖ਼', 'ੜ'),\n    ('ਫ਼', 'ਫ਼'),\n    ('੦', '\\u{a75}'),\n    ('\\u{a81}', 'ઃ'),\n    ('અ', 'ઍ'),\n    ('એ', 'ઑ'),\n    ('ઓ', 'ન'),\n    ('પ', 'ર'),\n    ('લ', 'ળ'),\n    ('વ', 'હ'),\n    ('\\u{abc}', '\\u{ac5}'),\n    ('\\u{ac7}', 'ૉ'),\n    ('ો', '\\u{acd}'),\n    ('ૐ', 'ૐ'),\n    ('ૠ', '\\u{ae3}'),\n    ('૦', '૯'),\n    ('ૹ', '\\u{aff}'),\n    ('\\u{b01}', 'ଃ'),\n    ('ଅ', 'ଌ'),\n    ('ଏ', 'ଐ'),\n    ('ଓ', 'ନ'),\n    ('ପ', 'ର'),\n    ('ଲ', 'ଳ'),\n    ('ଵ', 'ହ'),\n    ('\\u{b3c}', '\\u{b44}'),\n    ('େ', 'ୈ'),\n    ('ୋ', '\\u{b4d}'),\n    ('\\u{b55}', '\\u{b57}'),\n    ('ଡ଼', 'ଢ଼'),\n    ('ୟ', '\\u{b63}'),\n    ('୦', '୯'),\n    ('ୱ', 'ୱ'),\n    ('\\u{b82}', 'ஃ'),\n    ('அ', 'ஊ'),\n    ('எ', 'ஐ'),\n    ('ஒ', 'க'),\n    ('ங', 'ச'),\n    ('ஜ', 'ஜ'),\n    ('ஞ', 'ட'),\n    ('ண', 'த'),\n    ('ந', 'ப'),\n    ('ம', 'ஹ'),\n    ('\\u{bbe}', 'ூ'),\n    ('ெ', 'ை'),\n    ('ொ', '\\u{bcd}'),\n    ('ௐ', 'ௐ'),\n    ('\\u{bd7}', '\\u{bd7}'),\n    ('௦', '௯'),\n    ('\\u{c00}', 'ఌ'),\n    ('ఎ', 'ఐ'),\n    ('ఒ', 'న'),\n    ('ప', 'హ'),\n    ('\\u{c3c}', 'ౄ'),\n    ('\\u{c46}', '\\u{c48}'),\n    ('\\u{c4a}', '\\u{c4d}'),\n    ('\\u{c55}', '\\u{c56}'),\n    ('ౘ', 'ౚ'),\n    ('ౝ', 'ౝ'),\n    ('ౠ', '\\u{c63}'),\n    ('౦', '౯'),\n    ('ಀ', 'ಃ'),\n    ('ಅ', 'ಌ'),\n    ('ಎ', 'ಐ'),\n    ('ಒ', 'ನ'),\n    ('ಪ', 'ಳ'),\n    ('ವ', 'ಹ'),\n    ('\\u{cbc}', 'ೄ'),\n    ('\\u{cc6}', '\\u{cc8}'),\n    ('\\u{cca}', '\\u{ccd}'),\n    ('\\u{cd5}', '\\u{cd6}'),\n    ('ೝ', 'ೞ'),\n    ('ೠ', '\\u{ce3}'),\n    ('೦', '೯'),\n    ('ೱ', 'ೳ'),\n    ('\\u{d00}', 'ഌ'),\n    ('എ', 'ഐ'),\n    ('ഒ', '\\u{d44}'),\n    ('െ', 'ൈ'),\n    ('ൊ', 'ൎ'),\n    ('ൔ', '\\u{d57}'),\n    ('ൟ', '\\u{d63}'),\n    ('൦', '൯'),\n    ('ൺ', 'ൿ'),\n    ('\\u{d81}', 'ඃ'),\n    ('අ', 'ඖ'),\n    ('ක', 'න'),\n    ('ඳ', 'ර'),\n    ('ල', 'ල'),\n    ('ව', 'ෆ'),\n    ('\\u{dca}', '\\u{dca}'),\n    ('\\u{dcf}', '\\u{dd4}'),\n    ('\\u{dd6}', '\\u{dd6}'),\n    ('ෘ', '\\u{ddf}'),\n    ('෦', '෯'),\n    ('ෲ', 'ෳ'),\n    ('ก', '\\u{e3a}'),\n    ('เ', '\\u{e4e}'),\n    ('๐', '๙'),\n    ('ກ', 'ຂ'),\n    ('ຄ', 'ຄ'),\n    ('ຆ', 'ຊ'),\n    ('ຌ', 'ຣ'),\n    ('ລ', 'ລ'),\n    ('ວ', 'ຽ'),\n    ('ເ', 'ໄ'),\n    ('ໆ', 'ໆ'),\n    ('\\u{ec8}', '\\u{ece}'),\n    ('໐', '໙'),\n    ('ໜ', 'ໟ'),\n    ('ༀ', 'ༀ'),\n    ('\\u{f18}', '\\u{f19}'),\n    ('༠', '༩'),\n    ('\\u{f35}', '\\u{f35}'),\n    ('\\u{f37}', '\\u{f37}'),\n    ('\\u{f39}', '\\u{f39}'),\n    ('༾', 'ཇ'),\n    ('ཉ', 'ཬ'),\n    ('\\u{f71}', '\\u{f84}'),\n    ('\\u{f86}', '\\u{f97}'),\n    ('\\u{f99}', '\\u{fbc}'),\n    ('\\u{fc6}', '\\u{fc6}'),\n    ('က', '၉'),\n    ('ၐ', '\\u{109d}'),\n    ('Ⴀ', 'Ⴥ'),\n    ('Ⴧ', 'Ⴧ'),\n    ('Ⴭ', 'Ⴭ'),\n    ('ა', 'ჺ'),\n    ('ჼ', 'ቈ'),\n    ('ቊ', 'ቍ'),\n    ('ቐ', 'ቖ'),\n    ('ቘ', 'ቘ'),\n    ('ቚ', 'ቝ'),\n    ('በ', 'ኈ'),\n    ('ኊ', 'ኍ'),\n    ('ነ', 'ኰ'),\n    ('ኲ', 'ኵ'),\n    ('ኸ', 'ኾ'),\n    ('ዀ', 'ዀ'),\n    ('ዂ', 'ዅ'),\n    ('ወ', 'ዖ'),\n    ('ዘ', 'ጐ'),\n    ('ጒ', 'ጕ'),\n    ('ጘ', 'ፚ'),\n    ('\\u{135d}', '\\u{135f}'),\n    ('ᎀ', 'ᎏ'),\n    ('Ꭰ', 'Ᏽ'),\n    ('ᏸ', 'ᏽ'),\n    ('ᐁ', 'ᙬ'),\n    ('ᙯ', 'ᙿ'),\n    ('ᚁ', 'ᚚ'),\n    ('ᚠ', 'ᛪ'),\n    ('ᛮ', 'ᛸ'),\n    ('ᜀ', '\\u{1715}'),\n    ('ᜟ', '\\u{1734}'),\n    ('ᝀ', '\\u{1753}'),\n    ('ᝠ', 'ᝬ'),\n    ('ᝮ', 'ᝰ'),\n    ('\\u{1772}', '\\u{1773}'),\n    ('ក', '\\u{17d3}'),\n    ('ៗ', 'ៗ'),\n    ('ៜ', '\\u{17dd}'),\n    ('០', '៩'),\n    ('\\u{180b}', '\\u{180d}'),\n    ('\\u{180f}', '᠙'),\n    ('ᠠ', 'ᡸ'),\n    ('ᢀ', 'ᢪ'),\n    ('ᢰ', 'ᣵ'),\n    ('ᤀ', 'ᤞ'),\n    ('\\u{1920}', 'ᤫ'),\n    ('ᤰ', '\\u{193b}'),\n    ('᥆', 'ᥭ'),\n    ('ᥰ', 'ᥴ'),\n    ('ᦀ', 'ᦫ'),\n    ('ᦰ', 'ᧉ'),\n    ('᧐', '᧙'),\n    ('ᨀ', '\\u{1a1b}'),\n    ('ᨠ', '\\u{1a5e}'),\n    ('\\u{1a60}', '\\u{1a7c}'),\n    ('\\u{1a7f}', '᪉'),\n    ('᪐', '᪙'),\n    ('ᪧ', 'ᪧ'),\n    ('\\u{1ab0}', '\\u{1ace}'),\n    ('\\u{1b00}', 'ᭌ'),\n    ('᭐', '᭙'),\n    ('\\u{1b6b}', '\\u{1b73}'),\n    ('\\u{1b80}', '\\u{1bf3}'),\n    ('ᰀ', '\\u{1c37}'),\n    ('᱀', '᱉'),\n    ('ᱍ', 'ᱽ'),\n    ('ᲀ', 'ᲊ'),\n    ('Ა', 'Ჺ'),\n    ('Ჽ', 'Ჿ'),\n    ('\\u{1cd0}', '\\u{1cd2}'),\n    ('\\u{1cd4}', 'ᳺ'),\n    ('ᴀ', 'ἕ'),\n    ('Ἐ', 'Ἕ'),\n    ('ἠ', 'ὅ'),\n    ('Ὀ', 'Ὅ'),\n    ('ὐ', 'ὗ'),\n    ('Ὑ', 'Ὑ'),\n    ('Ὓ', 'Ὓ'),\n    ('Ὕ', 'Ὕ'),\n    ('Ὗ', 'ώ'),\n    ('ᾀ', 'ᾴ'),\n    ('ᾶ', 'ᾼ'),\n    ('ι', 'ι'),\n    ('ῂ', 'ῄ'),\n    ('ῆ', 'ῌ'),\n    ('ῐ', 'ΐ'),\n    ('ῖ', 'Ί'),\n    ('ῠ', 'Ῥ'),\n    ('ῲ', 'ῴ'),\n    ('ῶ', 'ῼ'),\n    ('\\u{200c}', '\\u{200d}'),\n    ('‿', '⁀'),\n    ('⁔', '⁔'),\n    ('ⁱ', 'ⁱ'),\n    ('ⁿ', 'ⁿ'),\n    ('ₐ', 'ₜ'),\n    ('\\u{20d0}', '\\u{20f0}'),\n    ('ℂ', 'ℂ'),\n    ('ℇ', 'ℇ'),\n    ('ℊ', 'ℓ'),\n    ('ℕ', 'ℕ'),\n    ('ℙ', 'ℝ'),\n    ('ℤ', 'ℤ'),\n    ('Ω', 'Ω'),\n    ('ℨ', 'ℨ'),\n    ('K', 'ℭ'),\n    ('ℯ', 'ℹ'),\n    ('ℼ', 'ℿ'),\n    ('ⅅ', 'ⅉ'),\n    ('ⅎ', 'ⅎ'),\n    ('Ⅰ', 'ↈ'),\n    ('Ⓐ', 'ⓩ'),\n    ('Ⰰ', 'ⳤ'),\n    ('Ⳬ', 'ⳳ'),\n    ('ⴀ', 'ⴥ'),\n    ('ⴧ', 'ⴧ'),\n    ('ⴭ', 'ⴭ'),\n    ('ⴰ', 'ⵧ'),\n    ('ⵯ', 'ⵯ'),\n    ('\\u{2d7f}', 'ⶖ'),\n    ('ⶠ', 'ⶦ'),\n    ('ⶨ', 'ⶮ'),\n    ('ⶰ', 'ⶶ'),\n    ('ⶸ', 'ⶾ'),\n    ('ⷀ', 'ⷆ'),\n    ('ⷈ', 'ⷎ'),\n    ('ⷐ', 'ⷖ'),\n    ('ⷘ', 'ⷞ'),\n    ('\\u{2de0}', '\\u{2dff}'),\n    ('ⸯ', 'ⸯ'),\n    ('々', '〇'),\n    ('〡', '\\u{302f}'),\n    ('〱', '〵'),\n    ('〸', '〼'),\n    ('ぁ', 'ゖ'),\n    ('\\u{3099}', '\\u{309a}'),\n    ('ゝ', 'ゟ'),\n    ('ァ', 'ヺ'),\n    ('ー', 'ヿ'),\n    ('ㄅ', 'ㄯ'),\n    ('ㄱ', 'ㆎ'),\n    ('ㆠ', 'ㆿ'),\n    ('ㇰ', 'ㇿ'),\n    ('㐀', '䶿'),\n    ('一', 'ꒌ'),\n    ('ꓐ', 'ꓽ'),\n    ('ꔀ', 'ꘌ'),\n    ('ꘐ', 'ꘫ'),\n    ('Ꙁ', '\\u{a672}'),\n    ('\\u{a674}', '\\u{a67d}'),\n    ('ꙿ', '\\u{a6f1}'),\n    ('ꜗ', 'ꜟ'),\n    ('Ꜣ', 'ꞈ'),\n    ('Ꞌ', 'ꟍ'),\n    ('Ꟑ', 'ꟑ'),\n    ('ꟓ', 'ꟓ'),\n    ('ꟕ', 'Ƛ'),\n    ('ꟲ', 'ꠧ'),\n    ('\\u{a82c}', '\\u{a82c}'),\n    ('ꡀ', 'ꡳ'),\n    ('ꢀ', '\\u{a8c5}'),\n    ('꣐', '꣙'),\n    ('\\u{a8e0}', 'ꣷ'),\n    ('ꣻ', 'ꣻ'),\n    ('ꣽ', '\\u{a92d}'),\n    ('ꤰ', '\\u{a953}'),\n    ('ꥠ', 'ꥼ'),\n    ('\\u{a980}', '\\u{a9c0}'),\n    ('ꧏ', '꧙'),\n    ('ꧠ', 'ꧾ'),\n    ('ꨀ', '\\u{aa36}'),\n    ('ꩀ', 'ꩍ'),\n    ('꩐', '꩙'),\n    ('ꩠ', 'ꩶ'),\n    ('ꩺ', 'ꫂ'),\n    ('ꫛ', 'ꫝ'),\n    ('ꫠ', 'ꫯ'),\n    ('ꫲ', '\\u{aaf6}'),\n    ('ꬁ', 'ꬆ'),\n    ('ꬉ', 'ꬎ'),\n    ('ꬑ', 'ꬖ'),\n    ('ꬠ', 'ꬦ'),\n    ('ꬨ', 'ꬮ'),\n    ('ꬰ', 'ꭚ'),\n    ('ꭜ', 'ꭩ'),\n    ('ꭰ', 'ꯪ'),\n    ('꯬', '\\u{abed}'),\n    ('꯰', '꯹'),\n    ('가', '힣'),\n    ('ힰ', 'ퟆ'),\n    ('ퟋ', 'ퟻ'),\n    ('豈', '舘'),\n    ('並', '龎'),\n    ('ﬀ', 'ﬆ'),\n    ('ﬓ', 'ﬗ'),\n    ('יִ', 'ﬨ'),\n    ('שׁ', 'זּ'),\n    ('טּ', 'לּ'),\n    ('מּ', 'מּ'),\n    ('נּ', 'סּ'),\n    ('ףּ', 'פּ'),\n    ('צּ', 'ﮱ'),\n    ('ﯓ', 'ﴽ'),\n    ('ﵐ', 'ﶏ'),\n    ('ﶒ', 'ﷇ'),\n    ('ﷰ', 'ﷻ'),\n    ('\\u{fe00}', '\\u{fe0f}'),\n    ('\\u{fe20}', '\\u{fe2f}'),\n    ('︳', '︴'),\n    ('﹍', '﹏'),\n    ('ﹰ', 'ﹴ'),\n    ('ﹶ', 'ﻼ'),\n    ('０', '９'),\n    ('Ａ', 'Ｚ'),\n    ('＿', '＿'),\n    ('ａ', 'ｚ'),\n    ('ｦ', 'ﾾ'),\n    ('ￂ', 'ￇ'),\n    ('ￊ', 'ￏ'),\n    ('ￒ', 'ￗ'),\n    ('ￚ', 'ￜ'),\n    ('𐀀', '𐀋'),\n    ('𐀍', '𐀦'),\n    ('𐀨', '𐀺'),\n    ('𐀼', '𐀽'),\n    ('𐀿', '𐁍'),\n    ('𐁐', '𐁝'),\n    ('𐂀', '𐃺'),\n    ('𐅀', '𐅴'),\n    ('\\u{101fd}', '\\u{101fd}'),\n    ('𐊀', '𐊜'),\n    ('𐊠', '𐋐'),\n    ('\\u{102e0}', '\\u{102e0}'),\n    ('𐌀', '𐌟'),\n    ('𐌭', '𐍊'),\n    ('𐍐', '\\u{1037a}'),\n    ('𐎀', '𐎝'),\n    ('𐎠', '𐏃'),\n    ('𐏈', '𐏏'),\n    ('𐏑', '𐏕'),\n    ('𐐀', '𐒝'),\n    ('𐒠', '𐒩'),\n    ('𐒰', '𐓓'),\n    ('𐓘', '𐓻'),\n    ('𐔀', '𐔧'),\n    ('𐔰', '𐕣'),\n    ('𐕰', '𐕺'),\n    ('𐕼', '𐖊'),\n    ('𐖌', '𐖒'),\n    ('𐖔', '𐖕'),\n    ('𐖗', '𐖡'),\n    ('𐖣', '𐖱'),\n    ('𐖳', '𐖹'),\n    ('𐖻', '𐖼'),\n    ('𐗀', '𐗳'),\n    ('𐘀', '𐜶'),\n    ('𐝀', '𐝕'),\n    ('𐝠', '𐝧'),\n    ('𐞀', '𐞅'),\n    ('𐞇', '𐞰'),\n    ('𐞲', '𐞺'),\n    ('𐠀', '𐠅'),\n    ('𐠈', '𐠈'),\n    ('𐠊', '𐠵'),\n    ('𐠷', '𐠸'),\n    ('𐠼', '𐠼'),\n    ('𐠿', '𐡕'),\n    ('𐡠', '𐡶'),\n    ('𐢀', '𐢞'),\n    ('𐣠', '𐣲'),\n    ('𐣴', '𐣵'),\n    ('𐤀', '𐤕'),\n    ('𐤠', '𐤹'),\n    ('𐦀', '𐦷'),\n    ('𐦾', '𐦿'),\n    ('𐨀', '\\u{10a03}'),\n    ('\\u{10a05}', '\\u{10a06}'),\n    ('\\u{10a0c}', '𐨓'),\n    ('𐨕', '𐨗'),\n    ('𐨙', '𐨵'),\n    ('\\u{10a38}', '\\u{10a3a}'),\n    ('\\u{10a3f}', '\\u{10a3f}'),\n    ('𐩠', '𐩼'),\n    ('𐪀', '𐪜'),\n    ('𐫀', '𐫇'),\n    ('𐫉', '\\u{10ae6}'),\n    ('𐬀', '𐬵'),\n    ('𐭀', '𐭕'),\n    ('𐭠', '𐭲'),\n    ('𐮀', '𐮑'),\n    ('𐰀', '𐱈'),\n    ('𐲀', '𐲲'),\n    ('𐳀', '𐳲'),\n    ('𐴀', '\\u{10d27}'),\n    ('𐴰', '𐴹'),\n    ('𐵀', '𐵥'),\n    ('\\u{10d69}', '\\u{10d6d}'),\n    ('𐵯', '𐶅'),\n    ('𐺀', '𐺩'),\n    ('\\u{10eab}', '\\u{10eac}'),\n    ('𐺰', '𐺱'),\n    ('𐻂', '𐻄'),\n    ('\\u{10efc}', '𐼜'),\n    ('𐼧', '𐼧'),\n    ('𐼰', '\\u{10f50}'),\n    ('𐽰', '\\u{10f85}'),\n    ('𐾰', '𐿄'),\n    ('𐿠', '𐿶'),\n    ('𑀀', '\\u{11046}'),\n    ('𑁦', '𑁵'),\n    ('\\u{1107f}', '\\u{110ba}'),\n    ('\\u{110c2}', '\\u{110c2}'),\n    ('𑃐', '𑃨'),\n    ('𑃰', '𑃹'),\n    ('\\u{11100}', '\\u{11134}'),\n    ('𑄶', '𑄿'),\n    ('𑅄', '𑅇'),\n    ('𑅐', '\\u{11173}'),\n    ('𑅶', '𑅶'),\n    ('\\u{11180}', '𑇄'),\n    ('\\u{111c9}', '\\u{111cc}'),\n    ('𑇎', '𑇚'),\n    ('𑇜', '𑇜'),\n    ('𑈀', '𑈑'),\n    ('𑈓', '\\u{11237}'),\n    ('\\u{1123e}', '\\u{11241}'),\n    ('𑊀', '𑊆'),\n    ('𑊈', '𑊈'),\n    ('𑊊', '𑊍'),\n    ('𑊏', '𑊝'),\n    ('𑊟', '𑊨'),\n    ('𑊰', '\\u{112ea}'),\n    ('𑋰', '𑋹'),\n    ('\\u{11300}', '𑌃'),\n    ('𑌅', '𑌌'),\n    ('𑌏', '𑌐'),\n    ('𑌓', '𑌨'),\n    ('𑌪', '𑌰'),\n    ('𑌲', '𑌳'),\n    ('𑌵', '𑌹'),\n    ('\\u{1133b}', '𑍄'),\n    ('𑍇', '𑍈'),\n    ('𑍋', '\\u{1134d}'),\n    ('𑍐', '𑍐'),\n    ('\\u{11357}', '\\u{11357}'),\n    ('𑍝', '𑍣'),\n    ('\\u{11366}', '\\u{1136c}'),\n    ('\\u{11370}', '\\u{11374}'),\n    ('𑎀', '𑎉'),\n    ('𑎋', '𑎋'),\n    ('𑎎', '𑎎'),\n    ('𑎐', '𑎵'),\n    ('𑎷', '\\u{113c0}'),\n    ('\\u{113c2}', '\\u{113c2}'),\n    ('\\u{113c5}', '\\u{113c5}'),\n    ('\\u{113c7}', '𑏊'),\n    ('𑏌', '𑏓'),\n    ('\\u{113e1}', '\\u{113e2}'),\n    ('𑐀', '𑑊'),\n    ('𑑐', '𑑙'),\n    ('\\u{1145e}', '𑑡'),\n    ('𑒀', '𑓅'),\n    ('𑓇', '𑓇'),\n    ('𑓐', '𑓙'),\n    ('𑖀', '\\u{115b5}'),\n    ('𑖸', '\\u{115c0}'),\n    ('𑗘', '\\u{115dd}'),\n    ('𑘀', '\\u{11640}'),\n    ('𑙄', '𑙄'),\n    ('𑙐', '𑙙'),\n    ('𑚀', '𑚸'),\n    ('𑛀', '𑛉'),\n    ('𑛐', '𑛣'),\n    ('𑜀', '𑜚'),\n    ('\\u{1171d}', '\\u{1172b}'),\n    ('𑜰', '𑜹'),\n    ('𑝀', '𑝆'),\n    ('𑠀', '\\u{1183a}'),\n    ('𑢠', '𑣩'),\n    ('𑣿', '𑤆'),\n    ('𑤉', '𑤉'),\n    ('𑤌', '𑤓'),\n    ('𑤕', '𑤖'),\n    ('𑤘', '𑤵'),\n    ('𑤷', '𑤸'),\n    ('\\u{1193b}', '\\u{11943}'),\n    ('𑥐', '𑥙'),\n    ('𑦠', '𑦧'),\n    ('𑦪', '\\u{119d7}'),\n    ('\\u{119da}', '𑧡'),\n    ('𑧣', '𑧤'),\n    ('𑨀', '\\u{11a3e}'),\n    ('\\u{11a47}', '\\u{11a47}'),\n    ('𑩐', '\\u{11a99}'),\n    ('𑪝', '𑪝'),\n    ('𑪰', '𑫸'),\n    ('𑯀', '𑯠'),\n    ('𑯰', '𑯹'),\n    ('𑰀', '𑰈'),\n    ('𑰊', '\\u{11c36}'),\n    ('\\u{11c38}', '𑱀'),\n    ('𑱐', '𑱙'),\n    ('𑱲', '𑲏'),\n    ('\\u{11c92}', '\\u{11ca7}'),\n    ('𑲩', '\\u{11cb6}'),\n    ('𑴀', '𑴆'),\n    ('𑴈', '𑴉'),\n    ('𑴋', '\\u{11d36}'),\n    ('\\u{11d3a}', '\\u{11d3a}'),\n    ('\\u{11d3c}', '\\u{11d3d}'),\n    ('\\u{11d3f}', '\\u{11d47}'),\n    ('𑵐', '𑵙'),\n    ('𑵠', '𑵥'),\n    ('𑵧', '𑵨'),\n    ('𑵪', '𑶎'),\n    ('\\u{11d90}', '\\u{11d91}'),\n    ('𑶓', '𑶘'),\n    ('𑶠', '𑶩'),\n    ('𑻠', '𑻶'),\n    ('\\u{11f00}', '𑼐'),\n    ('𑼒', '\\u{11f3a}'),\n    ('𑼾', '\\u{11f42}'),\n    ('𑽐', '\\u{11f5a}'),\n    ('𑾰', '𑾰'),\n    ('𒀀', '𒎙'),\n    ('𒐀', '𒑮'),\n    ('𒒀', '𒕃'),\n    ('𒾐', '𒿰'),\n    ('𓀀', '𓐯'),\n    ('\\u{13440}', '\\u{13455}'),\n    ('𓑠', '𔏺'),\n    ('𔐀', '𔙆'),\n    ('𖄀', '𖄹'),\n    ('𖠀', '𖨸'),\n    ('𖩀', '𖩞'),\n    ('𖩠', '𖩩'),\n    ('𖩰', '𖪾'),\n    ('𖫀', '𖫉'),\n    ('𖫐', '𖫭'),\n    ('\\u{16af0}', '\\u{16af4}'),\n    ('𖬀', '\\u{16b36}'),\n    ('𖭀', '𖭃'),\n    ('𖭐', '𖭙'),\n    ('𖭣', '𖭷'),\n    ('𖭽', '𖮏'),\n    ('𖵀', '𖵬'),\n    ('𖵰', '𖵹'),\n    ('𖹀', '𖹿'),\n    ('𖼀', '𖽊'),\n    ('\\u{16f4f}', '𖾇'),\n    ('\\u{16f8f}', '𖾟'),\n    ('𖿠', '𖿡'),\n    ('𖿣', '\\u{16fe4}'),\n    ('\\u{16ff0}', '\\u{16ff1}'),\n    ('𗀀', '𘟷'),\n    ('𘠀', '𘳕'),\n    ('𘳿', '𘴈'),\n    ('𚿰', '𚿳'),\n    ('𚿵', '𚿻'),\n    ('𚿽', '𚿾'),\n    ('𛀀', '𛄢'),\n    ('𛄲', '𛄲'),\n    ('𛅐', '𛅒'),\n    ('𛅕', '𛅕'),\n    ('𛅤', '𛅧'),\n    ('𛅰', '𛋻'),\n    ('𛰀', '𛱪'),\n    ('𛱰', '𛱼'),\n    ('𛲀', '𛲈'),\n    ('𛲐', '𛲙'),\n    ('\\u{1bc9d}', '\\u{1bc9e}'),\n    ('𜳰', '𜳹'),\n    ('\\u{1cf00}', '\\u{1cf2d}'),\n    ('\\u{1cf30}', '\\u{1cf46}'),\n    ('\\u{1d165}', '\\u{1d169}'),\n    ('\\u{1d16d}', '\\u{1d172}'),\n    ('\\u{1d17b}', '\\u{1d182}'),\n    ('\\u{1d185}', '\\u{1d18b}'),\n    ('\\u{1d1aa}', '\\u{1d1ad}'),\n    ('\\u{1d242}', '\\u{1d244}'),\n    ('𝐀', '𝑔'),\n    ('𝑖', '𝒜'),\n    ('𝒞', '𝒟'),\n    ('𝒢', '𝒢'),\n    ('𝒥', '𝒦'),\n    ('𝒩', '𝒬'),\n    ('𝒮', '𝒹'),\n    ('𝒻', '𝒻'),\n    ('𝒽', '𝓃'),\n    ('𝓅', '𝔅'),\n    ('𝔇', '𝔊'),\n    ('𝔍', '𝔔'),\n    ('𝔖', '𝔜'),\n    ('𝔞', '𝔹'),\n    ('𝔻', '𝔾'),\n    ('𝕀', '𝕄'),\n    ('𝕆', '𝕆'),\n    ('𝕊', '𝕐'),\n    ('𝕒', '𝚥'),\n    ('𝚨', '𝛀'),\n    ('𝛂', '𝛚'),\n    ('𝛜', '𝛺'),\n    ('𝛼', '𝜔'),\n    ('𝜖', '𝜴'),\n    ('𝜶', '𝝎'),\n    ('𝝐', '𝝮'),\n    ('𝝰', '𝞈'),\n    ('𝞊', '𝞨'),\n    ('𝞪', '𝟂'),\n    ('𝟄', '𝟋'),\n    ('𝟎', '𝟿'),\n    ('\\u{1da00}', '\\u{1da36}'),\n    ('\\u{1da3b}', '\\u{1da6c}'),\n    ('\\u{1da75}', '\\u{1da75}'),\n    ('\\u{1da84}', '\\u{1da84}'),\n    ('\\u{1da9b}', '\\u{1da9f}'),\n    ('\\u{1daa1}', '\\u{1daaf}'),\n    ('𝼀', '𝼞'),\n    ('𝼥', '𝼪'),\n    ('\\u{1e000}', '\\u{1e006}'),\n    ('\\u{1e008}', '\\u{1e018}'),\n    ('\\u{1e01b}', '\\u{1e021}'),\n    ('\\u{1e023}', '\\u{1e024}'),\n    ('\\u{1e026}', '\\u{1e02a}'),\n    ('𞀰', '𞁭'),\n    ('\\u{1e08f}', '\\u{1e08f}'),\n    ('𞄀', '𞄬'),\n    ('\\u{1e130}', '𞄽'),\n    ('𞅀', '𞅉'),\n    ('𞅎', '𞅎'),\n    ('𞊐', '\\u{1e2ae}'),\n    ('𞋀', '𞋹'),\n    ('𞓐', '𞓹'),\n    ('𞗐', '𞗺'),\n    ('𞟠', '𞟦'),\n    ('𞟨', '𞟫'),\n    ('𞟭', '𞟮'),\n    ('𞟰', '𞟾'),\n    ('𞠀', '𞣄'),\n    ('\\u{1e8d0}', '\\u{1e8d6}'),\n    ('𞤀', '𞥋'),\n    ('𞥐', '𞥙'),\n    ('𞸀', '𞸃'),\n    ('𞸅', '𞸟'),\n    ('𞸡', '𞸢'),\n    ('𞸤', '𞸤'),\n    ('𞸧', '𞸧'),\n    ('𞸩', '𞸲'),\n    ('𞸴', '𞸷'),\n    ('𞸹', '𞸹'),\n    ('𞸻', '𞸻'),\n    ('𞹂', '𞹂'),\n    ('𞹇', '𞹇'),\n    ('𞹉', '𞹉'),\n    ('𞹋', '𞹋'),\n    ('𞹍', '𞹏'),\n    ('𞹑', '𞹒'),\n    ('𞹔', '𞹔'),\n    ('𞹗', '𞹗'),\n    ('𞹙', '𞹙'),\n    ('𞹛', '𞹛'),\n    ('𞹝', '𞹝'),\n    ('𞹟', '𞹟'),\n    ('𞹡', '𞹢'),\n    ('𞹤', '𞹤'),\n    ('𞹧', '𞹪'),\n    ('𞹬', '𞹲'),\n    ('𞹴', '𞹷'),\n    ('𞹹', '𞹼'),\n    ('𞹾', '𞹾'),\n    ('𞺀', '𞺉'),\n    ('𞺋', '𞺛'),\n    ('𞺡', '𞺣'),\n    ('𞺥', '𞺩'),\n    ('𞺫', '𞺻'),\n    ('🄰', '🅉'),\n    ('🅐', '🅩'),\n    ('🅰', '🆉'),\n    ('🯰', '🯹'),\n    ('𠀀', '𪛟'),\n    ('𪜀', '𫜹'),\n    ('𫝀', '𫠝'),\n    ('𫠠', '𬺡'),\n    ('𬺰', '𮯠'),\n    ('𮯰', '𮹝'),\n    ('丽', '𪘀'),\n    ('𰀀', '𱍊'),\n    ('𱍐', '𲎯'),\n    ('\\u{e0100}', '\\u{e01ef}'),\n];\n"
  },
  {
    "path": "src/wasm.rs",
    "content": "/*\n * Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#![allow(non_snake_case)]\n\nuse crate::builder::{\n    RegExpBuilder as Builder, MINIMUM_REPETITIONS_MESSAGE, MINIMUM_SUBSTRING_LENGTH_MESSAGE,\n    MISSING_TEST_CASES_MESSAGE,\n};\nuse itertools::Itertools;\nuse wasm_bindgen::prelude::*;\n\n/// This class builds regular expressions from user-provided test cases.\n#[wasm_bindgen]\n#[derive(Clone)]\npub struct RegExpBuilder {\n    builder: Builder,\n}\n\n#[wasm_bindgen]\nimpl RegExpBuilder {\n    /// Specifies the test cases to build the regular expression from.\n    ///\n    /// The test cases need not be sorted because `RegExpBuilder` sorts them internally.\n    ///\n    /// ⚠ Throws an error if `testCases` is empty.\n    pub fn from(testCases: Box<[JsValue]>) -> Result<RegExpBuilder, JsValue> {\n        let strs = testCases\n            .iter()\n            .filter_map(|it| it.as_string())\n            .collect_vec();\n\n        if strs.is_empty() {\n            return Err(JsValue::from(MISSING_TEST_CASES_MESSAGE));\n        }\n        Ok(RegExpBuilder {\n            builder: Builder::from(&strs),\n        })\n    }\n\n    /// Tells `RegExpBuilder` to convert any Unicode decimal digit to character class `\\d`.\n    ///\n    /// This method takes precedence over `withConversionOfWords` if both are set.\n    /// Decimal digits are converted to `\\d`, the remaining word characters to `\\w`.\n    ///\n    /// This method takes precedence over `withConversionOfWhitespace` if both are set.\n    /// Decimal digits are converted to `\\d`, the remaining non-whitespace characters to `\\S`.\n    pub fn withConversionOfDigits(&mut self) -> RegExpBuilder {\n        self.builder.config.is_digit_converted = true;\n        self.clone()\n    }\n\n    /// Tells `RegExpBuilder` to convert any character which is not\n    /// a Unicode decimal digit to character class `\\D`.\n    ///\n    /// This method takes precedence over `withConversionOfNonWords` if both are set.\n    /// Non-digits which are also non-word characters are converted to `\\D`.\n    ///\n    /// This method takes precedence over `withConversionOfNonWhitespace` if both are set.\n    /// Non-digits which are also non-space characters are converted to `\\D`.\n    pub fn withConversionOfNonDigits(&mut self) -> RegExpBuilder {\n        self.builder.config.is_non_digit_converted = true;\n        self.clone()\n    }\n\n    /// Tells `RegExpBuilder` to convert any Unicode whitespace character to character class `\\s`.\n    ///\n    /// This method takes precedence over `withConversionOfNonDigits` if both are set.\n    /// Whitespace characters are converted to `\\s`, the remaining non-digit characters to `\\D`.\n    ///\n    /// This method takes precedence over `withConversionOfNonWords` if both are set.\n    /// Whitespace characters are converted to `\\s`, the remaining non-word characters to `\\W`.\n    pub fn withConversionOfWhitespace(&mut self) -> RegExpBuilder {\n        self.builder.config.is_space_converted = true;\n        self.clone()\n    }\n\n    /// Tells `RegExpBuilder` to convert any character which is not\n    /// a Unicode whitespace character to character class `\\S`.\n    pub fn withConversionOfNonWhitespace(&mut self) -> RegExpBuilder {\n        self.builder.config.is_non_space_converted = true;\n        self.clone()\n    }\n\n    /// Tells `RegExpBuilder` to convert any Unicode word character to character class `\\w`.\n    ///\n    /// This method takes precedence over `withConversionOfNonDigits` if both are set.\n    /// Word characters are converted to `\\w`, the remaining non-digit characters to `\\D`.\n    ///\n    /// This method takes precedence over `withConversionOfNonWhitespace` if both are set.\n    /// Word characters are converted to `\\w`, the remaining non-space characters to `\\S`.\n    pub fn withConversionOfWords(&mut self) -> RegExpBuilder {\n        self.builder.config.is_word_converted = true;\n        self.clone()\n    }\n\n    /// Tells `RegExpBuilder` to convert any character which is not\n    /// a Unicode word character to character class `\\W`.\n    ///\n    /// This method takes precedence over `withConversionOfNonWhitespace` if both are set.\n    /// Non-words which are also non-space characters are converted to `\\W`.\n    pub fn withConversionOfNonWords(&mut self) -> RegExpBuilder {\n        self.builder.config.is_non_word_converted = true;\n        self.clone()\n    }\n\n    /// Tells `RegExpBuilder` to detect repeated non-overlapping substrings and\n    /// to convert them to `{min,max}` quantifier notation.\n    pub fn withConversionOfRepetitions(&mut self) -> RegExpBuilder {\n        self.builder.config.is_repetition_converted = true;\n        self.clone()\n    }\n\n    /// Tells `RegExpBuilder` to enable case-insensitive matching of test cases\n    /// so that letters match both upper and lower case.\n    pub fn withCaseInsensitiveMatching(&mut self) -> RegExpBuilder {\n        self.builder.config.is_case_insensitive_matching = true;\n        self.clone()\n    }\n\n    /// Tells `RegExpBuilder` to replace non-capturing groups by capturing ones.\n    pub fn withCapturingGroups(&mut self) -> RegExpBuilder {\n        self.builder.config.is_capturing_group_enabled = true;\n        self.clone()\n    }\n\n    /// Tells `RegExpBuilder` to convert non-ASCII characters to unicode escape sequences.\n    /// The parameter `useSurrogatePairs` specifies whether to convert astral code planes\n    /// (range `U+010000` to `U+10FFFF`) to surrogate pairs.\n    pub fn withEscapingOfNonAsciiChars(&mut self, useSurrogatePairs: bool) -> RegExpBuilder {\n        self.builder.config.is_non_ascii_char_escaped = true;\n        self.builder\n            .config\n            .is_astral_code_point_converted_to_surrogate = useSurrogatePairs;\n        self.clone()\n    }\n\n    /// Tells `RegExpBuilder` to produce a nicer looking regular expression in verbose mode.\n    pub fn withVerboseMode(&mut self) -> RegExpBuilder {\n        self.builder.config.is_verbose_mode_enabled = true;\n        self.clone()\n    }\n\n    /// Tells `RegExpBuilder` to remove the caret anchor '^' from the resulting regular\n    /// expression, thereby allowing to match the test cases also when they do not occur\n    /// at the start of a string.\n    pub fn withoutStartAnchor(&mut self) -> RegExpBuilder {\n        self.builder.config.is_start_anchor_disabled = true;\n        self.clone()\n    }\n\n    /// Tells `RegExpBuilder` to remove the dollar sign anchor '$' from the resulting regular\n    /// expression, thereby allowing to match the test cases also when they do not occur\n    /// at the end of a string.\n    pub fn withoutEndAnchor(&mut self) -> RegExpBuilder {\n        self.builder.config.is_end_anchor_disabled = true;\n        self.clone()\n    }\n\n    /// Tells `RegExpBuilder` to remove the caret and dollar sign anchors from the resulting\n    /// regular expression, thereby allowing to match the test cases also when they occur\n    /// within a larger string that contains other content as well.\n    pub fn withoutAnchors(&mut self) -> RegExpBuilder {\n        self.builder.config.is_start_anchor_disabled = true;\n        self.builder.config.is_end_anchor_disabled = true;\n        self.clone()\n    }\n\n    /// Specifies the minimum quantity of substring repetitions to be converted\n    /// if `withConversionOfRepetitions` is set.\n    ///\n    /// If the quantity is not explicitly set with this method, a default value of 1 will be used.\n    ///\n    /// ⚠ Throws an error if `quantity` is zero.\n    pub fn withMinimumRepetitions(&mut self, quantity: u32) -> Result<RegExpBuilder, JsValue> {\n        if quantity < 1 {\n            return Err(JsValue::from(MINIMUM_REPETITIONS_MESSAGE));\n        }\n        self.builder.config.minimum_repetitions = quantity;\n        Ok(self.clone())\n    }\n\n    /// Specifies the minimum length a repeated substring must have in order to be converted\n    /// if `withConversionOfRepetitions` is set.\n    ///\n    /// If the length is not explicitly set with this method, a default value of 1 will be used.\n    ///\n    /// ⚠ Throws an error if `length` is zero.\n    pub fn withMinimumSubstringLength(&mut self, length: u32) -> Result<RegExpBuilder, JsValue> {\n        if length < 1 {\n            return Err(JsValue::from(MINIMUM_SUBSTRING_LENGTH_MESSAGE));\n        }\n        self.builder.config.minimum_substring_length = length;\n        Ok(self.clone())\n    }\n\n    /// Builds the actual regular expression using the previously given settings.\n    pub fn build(&mut self) -> String {\n        self.builder.build()\n    }\n}\n"
  },
  {
    "path": "tests/cli_integration_tests.rs",
    "content": "/*\n * Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#![cfg(not(target_family = \"wasm\"))]\n\nuse assert_cmd::{cargo_bin, Command};\nuse indoc::indoc;\nuse predicates::prelude::*;\nuse std::io::Write;\nuse tempfile::NamedTempFile;\n\nconst TEST_CASE: &str = \"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\";\n\nmod no_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[TEST_CASE]);\n            grex.assert()\n                .success()\n                .stdout(predicate::eq(\"^I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩\\\\.$\\n\"));\n        }\n\n        #[test]\n        fn succeeds_with_ignore_case_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--ignore-case\", \"Ä@Ö€Ü\", \"ä@ö€ü\", \"Ä@ö€Ü\", \"ä@Ö€ü\"]);\n            grex.assert()\n                .success()\n                .stdout(predicate::eq(\"(?i)^ä@ö€ü$\\n\"));\n        }\n\n        #[test]\n        fn succeeds_with_leading_hyphen() {\n            let mut grex = init_command();\n            grex.args(&[\"-a\", \"b\", \"c\"]);\n            grex.assert()\n                .success()\n                .stdout(predicate::eq(\"^(?:\\\\-a|[bc])$\\n\"));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--escape\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^I   \\\\u{2665}\\\\u{2665}\\\\u{2665} 36 and \\\\u{663} and y\\\\u{306}y\\\\u{306} and \\\\u{1f4a9}\\\\u{1f4a9}\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--escape\", \"--with-surrogates\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^I   \\\\u{2665}\\\\u{2665}\\\\u{2665} 36 and \\\\u{663} and y\\\\u{306}y\\\\u{306} and \\\\u{d83d}\\\\u{dca9}\\\\u{d83d}\\\\u{dca9}\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  I\\ \\ \\ ♥♥♥\\ 36\\ and\\ ٣\\ and\\ y̆y̆\\ and\\ 💩💩\\.\n                $\n                \"#,\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--escape\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  I\\ \\ \\ \\u{2665}\\u{2665}\\u{2665}\\ 36\\ and\\ \\u{663}\\ and\\ y\\u{306}y\\u{306}\\ and\\ \\u{1f4a9}\\u{1f4a9}\\.\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--escape\", \"--with-surrogates\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  I\\ \\ \\ \\u{2665}\\u{2665}\\u{2665}\\ 36\\ and\\ \\u{663}\\ and\\ y\\u{306}y\\u{306}\\ and\\ \\u{d83d}\\u{dca9}\\u{d83d}\\u{dca9}\\.\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_file_input() {\n            let mut file = NamedTempFile::new().unwrap();\n            writeln!(file, \"a\\nb\\\\n\\n\\nc\\näöü\\n♥\").unwrap();\n\n            let mut grex = init_command();\n            grex.args(&[\"-f\", file.path().to_str().unwrap()]);\n            grex.assert()\n                .success()\n                .stdout(predicate::eq(\"^(?:b\\\\\\\\n|äöü|[ac♥])$\\n\"));\n        }\n\n        #[test]\n        fn succeeds_with_test_cases_from_stdin() {\n            let mut grex = init_command();\n            grex.write_stdin(\"a\\nb\\\\n\\n\\nc\\näöü\\n♥\")\n                .arg(\"-\")\n                .assert()\n                .stdout(predicate::eq(\"^(?:b\\\\\\\\n|äöü|[ac♥])$\\n\"));\n        }\n\n        #[test]\n        fn succeeds_with_file_from_stdin() {\n            let mut file = NamedTempFile::new().unwrap();\n            writeln!(file, \"a\\nb\\\\n\\n\\nc\\näöü\\n♥\").unwrap();\n\n            let mut grex = init_command();\n            grex.write_stdin(file.path().to_str().unwrap())\n                .args(&[\"-f\", \"-\"])\n                .assert()\n                .stdout(predicate::eq(\"^(?:b\\\\\\\\n|äöü|[ac♥])$\\n\"));\n        }\n\n        #[test]\n        fn fails_with_surrogate_but_without_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--with-surrogates\", TEST_CASE]);\n            grex.assert().failure().stderr(predicate::str::contains(\n                \"required arguments were not provided\",\n            ));\n        }\n\n        #[test]\n        fn fails_without_arguments() {\n            let mut grex = init_command();\n            grex.assert().failure().stderr(predicate::str::contains(\n                \"required arguments were not provided\",\n            ));\n        }\n\n        #[test]\n        fn fails_when_file_name_is_not_provided() {\n            let mut grex = init_command();\n            grex.arg(\"-f\");\n            grex.assert().failure().stderr(predicate::str::contains(\n                \"a value is required for '--file <FILE>' but none was supplied\",\n            ));\n        }\n\n        #[test]\n        fn fails_when_file_does_not_exist() {\n            let mut grex = init_command();\n            grex.args(&[\"-f\", \"/path/to/non-existing/file\"]);\n            grex.assert()\n                .failure()\n                .stdout(predicate::str::is_empty())\n                .stderr(predicate::eq(\n                    \"error: the specified file could not be found\\n\",\n                ));\n        }\n\n        #[test]\n        fn fails_with_first_file_input_and_then_direct_input() {\n            let mut grex = init_command();\n            grex.args(&[\"-f\", \"/path/to/some/file\", TEST_CASE]);\n            grex.assert().failure().stderr(predicate::str::contains(\n                \"the argument '--file <FILE>' cannot be used with '[INPUT]...'\",\n            ));\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[\"--repetitions\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^I {3}♥{3} 36 and ٣ and (?:y̆){2} and 💩{2}\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_ignore_case_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--repetitions\", \"--ignore-case\", \"ÄÖÜäöü@Ö€\", \"äöüÄöÜ@ö€\"]);\n            grex.assert()\n                .success()\n                .stdout(predicate::eq(\"(?i)^(?:äöü){2}@ö€$\\n\"));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--repetitions\", \"--escape\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^I {3}\\\\u{2665}{3} 36 and \\\\u{663} and (?:y\\\\u{306}){2} and \\\\u{1f4a9}{2}\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--repetitions\", \"--escape\", \"--with-surrogates\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^I {3}\\\\u{2665}{3} 36 and \\\\u{663} and (?:y\\\\u{306}){2} and (?:\\\\u{d83d}\\\\u{dca9}){2}\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--repetitions\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  I\\ {3}♥{3}\\ 36\\ and\\ ٣\\ and\\ \n                  (?:\n                    y̆\n                  ){2}\n                  \\ and\\ 💩{2}\\.\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--repetitions\", \"--escape\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  I\\ {3}\\u{2665}{3}\\ 36\\ and\\ \\u{663}\\ and\\ \n                  (?:\n                    y\\u{306}\n                  ){2}\n                  \\ and\\ \\u{1f4a9}{2}\\.\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--escape\",\n                \"--with-surrogates\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  I\\ {3}\\u{2665}{3}\\ 36\\ and\\ \\u{663}\\ and\\ \n                  (?:\n                    y\\u{306}\n                  ){2}\n                  \\ and\\ \n                  (?:\n                    \\u{d83d}\\u{dca9}\n                  ){2}\n                  \\.\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_increased_minimum_repetitions() {\n            let mut grex = init_command();\n            grex.args(&[\"--repetitions\", \"--min-repetitions\", \"2\", TEST_CASE]);\n            grex.assert()\n                .success()\n                .stdout(predicate::eq(\"^I {3}♥{3} 36 and ٣ and y̆y̆ and 💩💩\\\\.$\\n\"));\n        }\n\n        #[test]\n        fn succeeds_with_increased_minimum_substring_length() {\n            let mut grex = init_command();\n            grex.args(&[\"--repetitions\", \"--min-substring-length\", \"2\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^I   ♥♥♥ 36 and ٣ and (?:y̆){2} and 💩💩\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn fails_with_minimum_repetitions_equal_to_zero() {\n            let mut grex = init_command();\n            grex.args(&[\"--min-repetitions\", \"0\", TEST_CASE]);\n            grex.assert()\n                .failure()\n                .stderr(predicate::str::contains(\"Value must not be zero\"));\n        }\n\n        #[test]\n        fn fails_with_minimum_repetitions_equal_to_invalid_value() {\n            let mut grex = init_command();\n            grex.args(&[\"--min-repetitions\", \"§!$\", TEST_CASE]);\n            grex.assert().failure().stderr(predicate::str::contains(\n                \"Value is not a valid unsigned integer\",\n            ));\n        }\n\n        #[test]\n        fn fails_with_minimum_substring_length_equal_to_zero() {\n            let mut grex = init_command();\n            grex.args(&[\"--min-substring-length\", \"0\", TEST_CASE]);\n            grex.assert()\n                .failure()\n                .stderr(predicate::str::contains(\"Value must not be zero\"));\n        }\n\n        #[test]\n        fn fails_with_minimum_substring_length_equal_to_invalid_value() {\n            let mut grex = init_command();\n            grex.args(&[\"--min-substring-length\", \"§!$\", TEST_CASE]);\n            grex.assert().failure().stderr(predicate::str::contains(\n                \"Value is not a valid unsigned integer\",\n            ));\n        }\n    }\n}\n\nmod digit_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[\"--digits\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^I   ♥♥♥ \\\\d\\\\d and \\\\d and y̆y̆ and 💩💩\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--digits\", \"--escape\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^I   \\\\u{2665}\\\\u{2665}\\\\u{2665} \\\\d\\\\d and \\\\d and y\\\\u{306}y\\\\u{306} and \\\\u{1f4a9}\\\\u{1f4a9}\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--digits\", \"--escape\", \"--with-surrogates\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^I   \\\\u{2665}\\\\u{2665}\\\\u{2665} \\\\d\\\\d and \\\\d and y\\\\u{306}y\\\\u{306} and \\\\u{d83d}\\\\u{dca9}\\\\u{d83d}\\\\u{dca9}\\\\.$\\n\"\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--digits\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  I\\ \\ \\ ♥♥♥\\ \\d\\d\\ and\\ \\d\\ and\\ y̆y̆\\ and\\ 💩💩\\.\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--digits\", \"--escape\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  I\\ \\ \\ \\u{2665}\\u{2665}\\u{2665}\\ \\d\\d\\ and\\ \\d\\ and\\ y\\u{306}y\\u{306}\\ and\\ \\u{1f4a9}\\u{1f4a9}\\.\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--digits\",\n                \"--escape\",\n                \"--with-surrogates\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  I\\ \\ \\ \\u{2665}\\u{2665}\\u{2665}\\ \\d\\d\\ and\\ \\d\\ and\\ y\\u{306}y\\u{306}\\ and\\ \\u{d83d}\\u{dca9}\\u{d83d}\\u{dca9}\\.\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_capturing_groups_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--capture-groups\", \"abc\", \"def\"]);\n            grex.assert()\n                .success()\n                .stdout(predicate::eq(\"^(abc|def)$\\n\"));\n        }\n\n        #[test]\n        fn succeeds_with_syntax_highlighting() {\n            let mut grex = init_command();\n            grex.args(&[\"--colorize\", \"abc\", \"def\"]);\n            grex.assert()\n                .success()\n                .stdout(predicate::eq(\"\\u{1b}[1;33m^\\u{1b}[0m\\u{1b}[1;32m(?:\\u{1b}[0mabc\\u{1b}[1;31m|\\u{1b}[0mdef\\u{1b}[1;32m)\\u{1b}[0m\\u{1b}[1;33m$\\u{1b}[0m\\n\"));\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[\"--repetitions\", \"--digits\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^I {3}♥{3} \\\\d(?:\\\\d and ){2}(?:y̆){2} and 💩{2}\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--repetitions\", \"--digits\", \"--escape\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^I {3}\\\\u{2665}{3} \\\\d(?:\\\\d and ){2}(?:y\\\\u{306}){2} and \\\\u{1f4a9}{2}\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--digits\",\n                \"--escape\",\n                \"--with-surrogates\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^I {3}\\\\u{2665}{3} \\\\d(?:\\\\d and ){2}(?:y\\\\u{306}){2} and (?:\\\\u{d83d}\\\\u{dca9}){2}\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--repetitions\", \"--digits\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  I\\ {3}♥{3}\\ \\d\n                  (?:\n                    \\d\\ and\\ \n                  ){2}\n                  (?:\n                    y̆\n                  ){2}\n                  \\ and\\ 💩{2}\\.\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--digits\",\n                \"--escape\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  I\\ {3}\\u{2665}{3}\\ \\d\n                  (?:\n                    \\d\\ and\\ \n                  ){2}\n                  (?:\n                    y\\u{306}\n                  ){2}\n                  \\ and\\ \\u{1f4a9}{2}\\.\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--digits\",\n                \"--escape\",\n                \"--with-surrogates\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  I\\ {3}\\u{2665}{3}\\ \\d\n                  (?:\n                    \\d\\ and\\ \n                  ){2}\n                  (?:\n                    y\\u{306}\n                  ){2}\n                  \\ and\\ \n                  (?:\n                    \\u{d83d}\\u{dca9}\n                  ){2}\n                  \\.\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_increased_minimum_repetitions() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--min-repetitions\",\n                \"2\",\n                \"--digits\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^I {3}♥{3} \\\\d\\\\d and \\\\d and y̆y̆ and 💩💩\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_increased_minimum_substring_length() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--min-substring-length\",\n                \"2\",\n                \"--digits\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^I   ♥♥♥ \\\\d(?:\\\\d and ){2}(?:y̆){2} and 💩💩\\\\.$\\n\",\n            ));\n        }\n    }\n}\n\nmod space_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[\"--spaces\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^I\\\\s\\\\s\\\\s♥♥♥\\\\s36\\\\sand\\\\s٣\\\\sand\\\\sy̆y̆\\\\sand\\\\s💩💩\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--spaces\", \"--escape\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^I\\\\s\\\\s\\\\s\\\\u{2665}\\\\u{2665}\\\\u{2665}\\\\s36\\\\sand\\\\s\\\\u{663}\\\\sand\\\\sy\\\\u{306}y\\\\u{306}\\\\sand\\\\s\\\\u{1f4a9}\\\\u{1f4a9}\\\\.$\\n\"\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--spaces\", \"--escape\", \"--with-surrogates\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^I\\\\s\\\\s\\\\s\\\\u{2665}\\\\u{2665}\\\\u{2665}\\\\s36\\\\sand\\\\s\\\\u{663}\\\\sand\\\\sy\\\\u{306}y\\\\u{306}\\\\sand\\\\s\\\\u{d83d}\\\\u{dca9}\\\\u{d83d}\\\\u{dca9}\\\\.$\\n\"\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--spaces\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  I\\s\\s\\s♥♥♥\\s36\\sand\\s٣\\sand\\sy̆y̆\\sand\\s💩💩\\.\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--spaces\", \"--escape\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  I\\s\\s\\s\\u{2665}\\u{2665}\\u{2665}\\s36\\sand\\s\\u{663}\\sand\\sy\\u{306}y\\u{306}\\sand\\s\\u{1f4a9}\\u{1f4a9}\\.\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--spaces\",\n                \"--escape\",\n                \"--with-surrogates\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  I\\s\\s\\s\\u{2665}\\u{2665}\\u{2665}\\s36\\sand\\s\\u{663}\\sand\\sy\\u{306}y\\u{306}\\sand\\s\\u{d83d}\\u{dca9}\\u{d83d}\\u{dca9}\\.\n                $\n                \"#\n            )));\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[\"--repetitions\", \"--spaces\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^I\\\\s{3}♥{3}\\\\s36\\\\sand\\\\s٣\\\\sand\\\\s(?:y̆){2}\\\\sand\\\\s💩{2}\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--repetitions\", \"--spaces\", \"--escape\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^I\\\\s{3}\\\\u{2665}{3}\\\\s36\\\\sand\\\\s\\\\u{663}\\\\sand\\\\s(?:y\\\\u{306}){2}\\\\sand\\\\s\\\\u{1f4a9}{2}\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--spaces\",\n                \"--escape\",\n                \"--with-surrogates\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^I\\\\s{3}\\\\u{2665}{3}\\\\s36\\\\sand\\\\s\\\\u{663}\\\\sand\\\\s(?:y\\\\u{306}){2}\\\\sand\\\\s(?:\\\\u{d83d}\\\\u{dca9}){2}\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--repetitions\", \"--spaces\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  I\\s{3}♥{3}\\s36\\sand\\s٣\\sand\\s\n                  (?:\n                    y̆\n                  ){2}\n                  \\sand\\s💩{2}\\.\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--spaces\",\n                \"--escape\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  I\\s{3}\\u{2665}{3}\\s36\\sand\\s\\u{663}\\sand\\s\n                  (?:\n                    y\\u{306}\n                  ){2}\n                  \\sand\\s\\u{1f4a9}{2}\\.\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--spaces\",\n                \"--escape\",\n                \"--with-surrogates\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  I\\s{3}\\u{2665}{3}\\s36\\sand\\s\\u{663}\\sand\\s\n                  (?:\n                    y\\u{306}\n                  ){2}\n                  \\sand\\s\n                  (?:\n                    \\u{d83d}\\u{dca9}\n                  ){2}\n                  \\.\n                $\n                \"#\n            )));\n        }\n    }\n}\n\nmod word_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[\"--words\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\w   ♥♥♥ \\\\w\\\\w \\\\w\\\\w\\\\w \\\\w \\\\w\\\\w\\\\w \\\\w\\\\w\\\\w\\\\w \\\\w\\\\w\\\\w 💩💩\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--words\", \"--escape\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\w   \\\\u{2665}\\\\u{2665}\\\\u{2665} \\\\w\\\\w \\\\w\\\\w\\\\w \\\\w \\\\w\\\\w\\\\w \\\\w\\\\w\\\\w\\\\w \\\\w\\\\w\\\\w \\\\u{1f4a9}\\\\u{1f4a9}\\\\.$\\n\"\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--words\", \"--escape\", \"--with-surrogates\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\w   \\\\u{2665}\\\\u{2665}\\\\u{2665} \\\\w\\\\w \\\\w\\\\w\\\\w \\\\w \\\\w\\\\w\\\\w \\\\w\\\\w\\\\w\\\\w \\\\w\\\\w\\\\w \\\\u{d83d}\\\\u{dca9}\\\\u{d83d}\\\\u{dca9}\\\\.$\\n\"\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--words\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\w\\ \\ \\ ♥♥♥\\ \\w\\w\\ \\w\\w\\w\\ \\w\\ \\w\\w\\w\\ \\w\\w\\w\\w\\ \\w\\w\\w\\ 💩💩\\.\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--words\", \"--escape\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\w\\ \\ \\ \\u{2665}\\u{2665}\\u{2665}\\ \\w\\w\\ \\w\\w\\w\\ \\w\\ \\w\\w\\w\\ \\w\\w\\w\\w\\ \\w\\w\\w\\ \\u{1f4a9}\\u{1f4a9}\\.\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--words\",\n                \"--escape\",\n                \"--with-surrogates\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\w\\ \\ \\ \\u{2665}\\u{2665}\\u{2665}\\ \\w\\w\\ \\w\\w\\w\\ \\w\\ \\w\\w\\w\\ \\w\\w\\w\\w\\ \\w\\w\\w\\ \\u{d83d}\\u{dca9}\\u{d83d}\\u{dca9}\\.\n                $\n                \"#\n            )));\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[\"--repetitions\", \"--words\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\w {3}♥{3} \\\\w{2}(?: \\\\w{3} \\\\w){2}(?:\\\\w{3} ){2}💩{2}\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--repetitions\", \"--words\", \"--escape\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\w {3}\\\\u{2665}{3} \\\\w{2}(?: \\\\w{3} \\\\w){2}(?:\\\\w{3} ){2}\\\\u{1f4a9}{2}\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--words\",\n                \"--escape\",\n                \"--with-surrogates\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\w {3}\\\\u{2665}{3} \\\\w{2}(?: \\\\w{3} \\\\w){2}(?:\\\\w{3} ){2}(?:\\\\u{d83d}\\\\u{dca9}){2}\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--repetitions\", \"--words\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\w\\ {3}♥{3}\\ \\w{2}\n                  (?:\n                    \\ \\w{3}\\ \\w\n                  ){2}\n                  (?:\n                    \\w{3}\\ \n                  ){2}\n                  💩{2}\\.\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--words\",\n                \"--escape\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\w\\ {3}\\u{2665}{3}\\ \\w{2}\n                  (?:\n                    \\ \\w{3}\\ \\w\n                  ){2}\n                  (?:\n                    \\w{3}\\ \n                  ){2}\n                  \\u{1f4a9}{2}\\.\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--words\",\n                \"--escape\",\n                \"--with-surrogates\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\w\\ {3}\\u{2665}{3}\\ \\w{2}\n                  (?:\n                    \\ \\w{3}\\ \\w\n                  ){2}\n                  (?:\n                    \\w{3}\\ \n                  ){2}\n                  (?:\n                    \\u{d83d}\\u{dca9}\n                  ){2}\n                  \\.\n                $\n                \"#\n            )));\n        }\n    }\n}\n\nmod digit_space_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[\"--digits\", \"--spaces\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^I\\\\s\\\\s\\\\s♥♥♥\\\\s\\\\d\\\\d\\\\sand\\\\s\\\\d\\\\sand\\\\sy̆y̆\\\\sand\\\\s💩💩\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--digits\", \"--spaces\", \"--escape\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^I\\\\s\\\\s\\\\s\\\\u{2665}\\\\u{2665}\\\\u{2665}\\\\s\\\\d\\\\d\\\\sand\\\\s\\\\d\\\\sand\\\\sy\\\\u{306}y\\\\u{306}\\\\sand\\\\s\\\\u{1f4a9}\\\\u{1f4a9}\\\\.$\\n\"\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--digits\",\n                \"--spaces\",\n                \"--escape\",\n                \"--with-surrogates\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^I\\\\s\\\\s\\\\s\\\\u{2665}\\\\u{2665}\\\\u{2665}\\\\s\\\\d\\\\d\\\\sand\\\\s\\\\d\\\\sand\\\\sy\\\\u{306}y\\\\u{306}\\\\sand\\\\s\\\\u{d83d}\\\\u{dca9}\\\\u{d83d}\\\\u{dca9}\\\\.$\\n\"\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--digits\", \"--spaces\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  I\\s\\s\\s♥♥♥\\s\\d\\d\\sand\\s\\d\\sand\\sy̆y̆\\sand\\s💩💩\\.\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--digits\", \"--spaces\", \"--escape\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  I\\s\\s\\s\\u{2665}\\u{2665}\\u{2665}\\s\\d\\d\\sand\\s\\d\\sand\\sy\\u{306}y\\u{306}\\sand\\s\\u{1f4a9}\\u{1f4a9}\\.\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--digits\",\n                \"--spaces\",\n                \"--escape\",\n                \"--with-surrogates\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  I\\s\\s\\s\\u{2665}\\u{2665}\\u{2665}\\s\\d\\d\\sand\\s\\d\\sand\\sy\\u{306}y\\u{306}\\sand\\s\\u{d83d}\\u{dca9}\\u{d83d}\\u{dca9}\\.\n                $\n                \"#\n            )));\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[\"--repetitions\", \"--digits\", \"--spaces\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^I\\\\s{3}♥{3}\\\\s\\\\d(?:\\\\d\\\\sand\\\\s){2}(?:y̆){2}\\\\sand\\\\s💩{2}\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--digits\",\n                \"--spaces\",\n                \"--escape\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^I\\\\s{3}\\\\u{2665}{3}\\\\s\\\\d(?:\\\\d\\\\sand\\\\s){2}(?:y\\\\u{306}){2}\\\\sand\\\\s\\\\u{1f4a9}{2}\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--digits\",\n                \"--spaces\",\n                \"--escape\",\n                \"--with-surrogates\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^I\\\\s{3}\\\\u{2665}{3}\\\\s\\\\d(?:\\\\d\\\\sand\\\\s){2}(?:y\\\\u{306}){2}\\\\sand\\\\s(?:\\\\u{d83d}\\\\u{dca9}){2}\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--digits\",\n                \"--spaces\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  I\\s{3}♥{3}\\s\\d\n                  (?:\n                    \\d\\sand\\s\n                  ){2}\n                  (?:\n                    y̆\n                  ){2}\n                  \\sand\\s💩{2}\\.\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--digits\",\n                \"--spaces\",\n                \"--escape\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  I\\s{3}\\u{2665}{3}\\s\\d\n                  (?:\n                    \\d\\sand\\s\n                  ){2}\n                  (?:\n                    y\\u{306}\n                  ){2}\n                  \\sand\\s\\u{1f4a9}{2}\\.\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--digits\",\n                \"--spaces\",\n                \"--escape\",\n                \"--with-surrogates\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  I\\s{3}\\u{2665}{3}\\s\\d\n                  (?:\n                    \\d\\sand\\s\n                  ){2}\n                  (?:\n                    y\\u{306}\n                  ){2}\n                  \\sand\\s\n                  (?:\n                    \\u{d83d}\\u{dca9}\n                  ){2}\n                  \\.\n                $\n                \"#\n            )));\n        }\n    }\n}\n\nmod digit_word_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[\"--digits\", \"--words\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\w   ♥♥♥ \\\\d\\\\d \\\\w\\\\w\\\\w \\\\d \\\\w\\\\w\\\\w \\\\w\\\\w\\\\w\\\\w \\\\w\\\\w\\\\w 💩💩\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--digits\", \"--words\", \"--escape\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\w   \\\\u{2665}\\\\u{2665}\\\\u{2665} \\\\d\\\\d \\\\w\\\\w\\\\w \\\\d \\\\w\\\\w\\\\w \\\\w\\\\w\\\\w\\\\w \\\\w\\\\w\\\\w \\\\u{1f4a9}\\\\u{1f4a9}\\\\.$\\n\"\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--digits\",\n                \"--words\",\n                \"--escape\",\n                \"--with-surrogates\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\w   \\\\u{2665}\\\\u{2665}\\\\u{2665} \\\\d\\\\d \\\\w\\\\w\\\\w \\\\d \\\\w\\\\w\\\\w \\\\w\\\\w\\\\w\\\\w \\\\w\\\\w\\\\w \\\\u{d83d}\\\\u{dca9}\\\\u{d83d}\\\\u{dca9}\\\\.$\\n\"\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--digits\", \"--words\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\w\\ \\ \\ ♥♥♥\\ \\d\\d\\ \\w\\w\\w\\ \\d\\ \\w\\w\\w\\ \\w\\w\\w\\w\\ \\w\\w\\w\\ 💩💩\\.\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--digits\", \"--words\", \"--escape\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\w\\ \\ \\ \\u{2665}\\u{2665}\\u{2665}\\ \\d\\d\\ \\w\\w\\w\\ \\d\\ \\w\\w\\w\\ \\w\\w\\w\\w\\ \\w\\w\\w\\ \\u{1f4a9}\\u{1f4a9}\\.\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--digits\",\n                \"--words\",\n                \"--escape\",\n                \"--with-surrogates\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\w\\ \\ \\ \\u{2665}\\u{2665}\\u{2665}\\ \\d\\d\\ \\w\\w\\w\\ \\d\\ \\w\\w\\w\\ \\w\\w\\w\\w\\ \\w\\w\\w\\ \\u{d83d}\\u{dca9}\\u{d83d}\\u{dca9}\\.\n                $\n                \"#\n            )));\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[\"--repetitions\", \"--digits\", \"--words\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\w {3}♥{3} \\\\d(?:\\\\d \\\\w{3} ){2}\\\\w(?:\\\\w{3} ){2}💩{2}\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--digits\",\n                \"--words\",\n                \"--escape\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\w {3}\\\\u{2665}{3} \\\\d(?:\\\\d \\\\w{3} ){2}\\\\w(?:\\\\w{3} ){2}\\\\u{1f4a9}{2}\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--digits\",\n                \"--words\",\n                \"--escape\",\n                \"--with-surrogates\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\w {3}\\\\u{2665}{3} \\\\d(?:\\\\d \\\\w{3} ){2}\\\\w(?:\\\\w{3} ){2}(?:\\\\u{d83d}\\\\u{dca9}){2}\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--digits\",\n                \"--words\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\w\\ {3}♥{3}\\ \\d\n                  (?:\n                    \\d\\ \\w{3}\\ \n                  ){2}\n                  \\w\n                  (?:\n                    \\w{3}\\ \n                  ){2}\n                  💩{2}\\.\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--digits\",\n                \"--words\",\n                \"--escape\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\w\\ {3}\\u{2665}{3}\\ \\d\n                  (?:\n                    \\d\\ \\w{3}\\ \n                  ){2}\n                  \\w\n                  (?:\n                    \\w{3}\\ \n                  ){2}\n                  \\u{1f4a9}{2}\\.\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--digits\",\n                \"--words\",\n                \"--escape\",\n                \"--with-surrogates\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\w\\ {3}\\u{2665}{3}\\ \\d\n                  (?:\n                    \\d\\ \\w{3}\\ \n                  ){2}\n                  \\w\n                  (?:\n                    \\w{3}\\ \n                  ){2}\n                  (?:\n                    \\u{d83d}\\u{dca9}\n                  ){2}\n                  \\.\n                $\n                \"#\n            )));\n        }\n    }\n}\n\nmod space_word_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[\"--words\", \"--spaces\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\w\\\\s\\\\s\\\\s♥♥♥\\\\s\\\\w\\\\w\\\\s\\\\w\\\\w\\\\w\\\\s\\\\w\\\\s\\\\w\\\\w\\\\w\\\\s\\\\w\\\\w\\\\w\\\\w\\\\s\\\\w\\\\w\\\\w\\\\s💩💩\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--words\", \"--spaces\", \"--escape\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\w\\\\s\\\\s\\\\s\\\\u{2665}\\\\u{2665}\\\\u{2665}\\\\s\\\\w\\\\w\\\\s\\\\w\\\\w\\\\w\\\\s\\\\w\\\\s\\\\w\\\\w\\\\w\\\\s\\\\w\\\\w\\\\w\\\\w\\\\s\\\\w\\\\w\\\\w\\\\s\\\\u{1f4a9}\\\\u{1f4a9}\\\\.$\\n\"\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--words\",\n                \"--spaces\",\n                \"--escape\",\n                \"--with-surrogates\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\w\\\\s\\\\s\\\\s\\\\u{2665}\\\\u{2665}\\\\u{2665}\\\\s\\\\w\\\\w\\\\s\\\\w\\\\w\\\\w\\\\s\\\\w\\\\s\\\\w\\\\w\\\\w\\\\s\\\\w\\\\w\\\\w\\\\w\\\\s\\\\w\\\\w\\\\w\\\\s\\\\u{d83d}\\\\u{dca9}\\\\u{d83d}\\\\u{dca9}\\\\.$\\n\"\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--words\", \"--spaces\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\w\\s\\s\\s♥♥♥\\s\\w\\w\\s\\w\\w\\w\\s\\w\\s\\w\\w\\w\\s\\w\\w\\w\\w\\s\\w\\w\\w\\s💩💩\\.\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--words\", \"--spaces\", \"--escape\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\w\\s\\s\\s\\u{2665}\\u{2665}\\u{2665}\\s\\w\\w\\s\\w\\w\\w\\s\\w\\s\\w\\w\\w\\s\\w\\w\\w\\w\\s\\w\\w\\w\\s\\u{1f4a9}\\u{1f4a9}\\.\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--words\",\n                \"--spaces\",\n                \"--escape\",\n                \"--with-surrogates\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\w\\s\\s\\s\\u{2665}\\u{2665}\\u{2665}\\s\\w\\w\\s\\w\\w\\w\\s\\w\\s\\w\\w\\w\\s\\w\\w\\w\\w\\s\\w\\w\\w\\s\\u{d83d}\\u{dca9}\\u{d83d}\\u{dca9}\\.\n                $\n                \"#\n            )));\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[\"--repetitions\", \"--words\", \"--spaces\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\w\\\\s{3}♥{3}\\\\s\\\\w{2}(?:\\\\s\\\\w{3}\\\\s\\\\w){2}(?:\\\\w{3}\\\\s){2}💩{2}\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--words\",\n                \"--spaces\",\n                \"--escape\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\w\\\\s{3}\\\\u{2665}{3}\\\\s\\\\w{2}(?:\\\\s\\\\w{3}\\\\s\\\\w){2}(?:\\\\w{3}\\\\s){2}\\\\u{1f4a9}{2}\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--words\",\n                \"--spaces\",\n                \"--escape\",\n                \"--with-surrogates\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\w\\\\s{3}\\\\u{2665}{3}\\\\s\\\\w{2}(?:\\\\s\\\\w{3}\\\\s\\\\w){2}(?:\\\\w{3}\\\\s){2}(?:\\\\u{d83d}\\\\u{dca9}){2}\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--words\",\n                \"--spaces\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\w\\s{3}♥{3}\\s\\w{2}\n                  (?:\n                    \\s\\w{3}\\s\\w\n                  ){2}\n                  (?:\n                    \\w{3}\\s\n                  ){2}\n                  💩{2}\\.\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--words\",\n                \"--spaces\",\n                \"--escape\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\w\\s{3}\\u{2665}{3}\\s\\w{2}\n                  (?:\n                    \\s\\w{3}\\s\\w\n                  ){2}\n                  (?:\n                    \\w{3}\\s\n                  ){2}\n                  \\u{1f4a9}{2}\\.\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--words\",\n                \"--spaces\",\n                \"--escape\",\n                \"--with-surrogates\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\w\\s{3}\\u{2665}{3}\\s\\w{2}\n                  (?:\n                    \\s\\w{3}\\s\\w\n                  ){2}\n                  (?:\n                    \\w{3}\\s\n                  ){2}\n                  (?:\n                    \\u{d83d}\\u{dca9}\n                  ){2}\n                  \\.\n                $\n                \"#\n            )));\n        }\n    }\n}\n\nmod digit_space_word_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[\"--digits\", \"--words\", \"--spaces\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\w\\\\s\\\\s\\\\s♥♥♥\\\\s\\\\d\\\\d\\\\s\\\\w\\\\w\\\\w\\\\s\\\\d\\\\s\\\\w\\\\w\\\\w\\\\s\\\\w\\\\w\\\\w\\\\w\\\\s\\\\w\\\\w\\\\w\\\\s💩💩\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--digits\", \"--words\", \"--spaces\", \"--escape\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\w\\\\s\\\\s\\\\s\\\\u{2665}\\\\u{2665}\\\\u{2665}\\\\s\\\\d\\\\d\\\\s\\\\w\\\\w\\\\w\\\\s\\\\d\\\\s\\\\w\\\\w\\\\w\\\\s\\\\w\\\\w\\\\w\\\\w\\\\s\\\\w\\\\w\\\\w\\\\s\\\\u{1f4a9}\\\\u{1f4a9}\\\\.$\\n\"\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--digits\",\n                \"--words\",\n                \"--spaces\",\n                \"--escape\",\n                \"--with-surrogates\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\w\\\\s\\\\s\\\\s\\\\u{2665}\\\\u{2665}\\\\u{2665}\\\\s\\\\d\\\\d\\\\s\\\\w\\\\w\\\\w\\\\s\\\\d\\\\s\\\\w\\\\w\\\\w\\\\s\\\\w\\\\w\\\\w\\\\w\\\\s\\\\w\\\\w\\\\w\\\\s\\\\u{d83d}\\\\u{dca9}\\\\u{d83d}\\\\u{dca9}\\\\.$\\n\"\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--digits\", \"--words\", \"--spaces\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\w\\s\\s\\s♥♥♥\\s\\d\\d\\s\\w\\w\\w\\s\\d\\s\\w\\w\\w\\s\\w\\w\\w\\w\\s\\w\\w\\w\\s💩💩\\.\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--digits\",\n                \"--words\",\n                \"--spaces\",\n                \"--escape\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\w\\s\\s\\s\\u{2665}\\u{2665}\\u{2665}\\s\\d\\d\\s\\w\\w\\w\\s\\d\\s\\w\\w\\w\\s\\w\\w\\w\\w\\s\\w\\w\\w\\s\\u{1f4a9}\\u{1f4a9}\\.\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--digits\",\n                \"--words\",\n                \"--spaces\",\n                \"--escape\",\n                \"--with-surrogates\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\w\\s\\s\\s\\u{2665}\\u{2665}\\u{2665}\\s\\d\\d\\s\\w\\w\\w\\s\\d\\s\\w\\w\\w\\s\\w\\w\\w\\w\\s\\w\\w\\w\\s\\u{d83d}\\u{dca9}\\u{d83d}\\u{dca9}\\.\n                $\n                \"#\n            )));\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--digits\",\n                \"--words\",\n                \"--spaces\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\w\\\\s{3}♥{3}\\\\s\\\\d(?:\\\\d\\\\s\\\\w{3}\\\\s){2}\\\\w(?:\\\\w{3}\\\\s){2}💩{2}\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--digits\",\n                \"--words\",\n                \"--spaces\",\n                \"--escape\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\w\\\\s{3}\\\\u{2665}{3}\\\\s\\\\d(?:\\\\d\\\\s\\\\w{3}\\\\s){2}\\\\w(?:\\\\w{3}\\\\s){2}\\\\u{1f4a9}{2}\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--digits\",\n                \"--words\",\n                \"--spaces\",\n                \"--escape\",\n                \"--with-surrogates\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\w\\\\s{3}\\\\u{2665}{3}\\\\s\\\\d(?:\\\\d\\\\s\\\\w{3}\\\\s){2}\\\\w(?:\\\\w{3}\\\\s){2}(?:\\\\u{d83d}\\\\u{dca9}){2}\\\\.$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--digits\",\n                \"--words\",\n                \"--spaces\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\w\\s{3}♥{3}\\s\\d\n                  (?:\n                    \\d\\s\\w{3}\\s\n                  ){2}\n                  \\w\n                  (?:\n                    \\w{3}\\s\n                  ){2}\n                  💩{2}\\.\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--digits\",\n                \"--words\",\n                \"--spaces\",\n                \"--escape\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\w\\s{3}\\u{2665}{3}\\s\\d\n                  (?:\n                    \\d\\s\\w{3}\\s\n                  ){2}\n                  \\w\n                  (?:\n                    \\w{3}\\s\n                  ){2}\n                  \\u{1f4a9}{2}\\.\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--digits\",\n                \"--words\",\n                \"--spaces\",\n                \"--escape\",\n                \"--with-surrogates\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\w\\s{3}\\u{2665}{3}\\s\\d\n                  (?:\n                    \\d\\s\\w{3}\\s\n                  ){2}\n                  \\w\n                  (?:\n                    \\w{3}\\s\n                  ){2}\n                  (?:\n                    \\u{d83d}\\u{dca9}\n                  ){2}\n                  \\.\n                $\n                \"#\n            )));\n        }\n    }\n}\n\nmod non_digit_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[\"--non-digits\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D36\\\\D\\\\D\\\\D\\\\D\\\\D٣\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--non-digits\", \"--escape\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D36\\\\D\\\\D\\\\D\\\\D\\\\D\\\\u{663}\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--non-digits\", \"--escape\", \"--with-surrogates\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D36\\\\D\\\\D\\\\D\\\\D\\\\D\\\\u{663}\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--non-digits\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\D\\D\\D\\D\\D\\D\\D\\D36\\D\\D\\D\\D\\D٣\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--non-digits\", \"--escape\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\D\\D\\D\\D\\D\\D\\D\\D36\\D\\D\\D\\D\\D\\u{663}\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--non-digits\",\n                \"--escape\",\n                \"--with-surrogates\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\D\\D\\D\\D\\D\\D\\D\\D36\\D\\D\\D\\D\\D\\u{663}\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\n                $\n                \"#\n            )));\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[\"--repetitions\", \"--non-digits\", TEST_CASE]);\n            grex.assert()\n                .success()\n                .stdout(predicate::eq(\"^\\\\D{8}36\\\\D{5}٣\\\\D{17}$\\n\"));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--repetitions\", \"--non-digits\", \"--escape\", TEST_CASE]);\n            grex.assert()\n                .success()\n                .stdout(predicate::eq(\"^\\\\D{8}36\\\\D{5}\\\\u{663}\\\\D{17}$\\n\"));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--non-digits\",\n                \"--escape\",\n                \"--with-surrogates\",\n                TEST_CASE,\n            ]);\n            grex.assert()\n                .success()\n                .stdout(predicate::eq(\"^\\\\D{8}36\\\\D{5}\\\\u{663}\\\\D{17}$\\n\"));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--repetitions\", \"--non-digits\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\D{8}36\\D{5}٣\\D{17}\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--non-digits\",\n                \"--escape\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\D{8}36\\D{5}\\u{663}\\D{17}\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--non-digits\",\n                \"--escape\",\n                \"--with-surrogates\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\D{8}36\\D{5}\\u{663}\\D{17}\n                $\n                \"#\n            )));\n        }\n    }\n}\n\nmod non_space_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[\"--non-spaces\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\S   \\\\S\\\\S\\\\S \\\\S\\\\S \\\\S\\\\S\\\\S \\\\S \\\\S\\\\S\\\\S \\\\S\\\\S\\\\S\\\\S \\\\S\\\\S\\\\S \\\\S\\\\S\\\\S$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--non-spaces\", \"--escape\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\S   \\\\S\\\\S\\\\S \\\\S\\\\S \\\\S\\\\S\\\\S \\\\S \\\\S\\\\S\\\\S \\\\S\\\\S\\\\S\\\\S \\\\S\\\\S\\\\S \\\\S\\\\S\\\\S$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--non-spaces\", \"--escape\", \"--with-surrogates\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\S   \\\\S\\\\S\\\\S \\\\S\\\\S \\\\S\\\\S\\\\S \\\\S \\\\S\\\\S\\\\S \\\\S\\\\S\\\\S\\\\S \\\\S\\\\S\\\\S \\\\S\\\\S\\\\S$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--non-spaces\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\S\\ \\ \\ \\S\\S\\S\\ \\S\\S\\ \\S\\S\\S\\ \\S\\ \\S\\S\\S\\ \\S\\S\\S\\S\\ \\S\\S\\S\\ \\S\\S\\S\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--non-spaces\", \"--escape\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\S\\ \\ \\ \\S\\S\\S\\ \\S\\S\\ \\S\\S\\S\\ \\S\\ \\S\\S\\S\\ \\S\\S\\S\\S\\ \\S\\S\\S\\ \\S\\S\\S\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--non-spaces\",\n                \"--escape\",\n                \"--with-surrogates\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\S\\ \\ \\ \\S\\S\\S\\ \\S\\S\\ \\S\\S\\S\\ \\S\\ \\S\\S\\S\\ \\S\\S\\S\\S\\ \\S\\S\\S\\ \\S\\S\\S\n                $\n                \"#\n            )));\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[\"--repetitions\", \"--non-spaces\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\S {3}\\\\S(?:\\\\S{2} ){2}\\\\S{3} (?:\\\\S(?: \\\\S{3}){2}){2}$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--repetitions\", \"--non-spaces\", \"--escape\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\S {3}\\\\S(?:\\\\S{2} ){2}\\\\S{3} (?:\\\\S(?: \\\\S{3}){2}){2}$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--non-spaces\",\n                \"--escape\",\n                \"--with-surrogates\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\S {3}\\\\S(?:\\\\S{2} ){2}\\\\S{3} (?:\\\\S(?: \\\\S{3}){2}){2}$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--repetitions\", \"--non-spaces\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\S\\ {3}\\S\n                  (?:\n                    \\S{2}\\ \n                  ){2}\n                  \\S{3}\\ \n                  (?:\n                    \\S\n                    (?:\n                      \\ \\S{3}\n                    ){2}\n                  ){2}\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--non-spaces\",\n                \"--escape\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\S\\ {3}\\S\n                  (?:\n                    \\S{2}\\ \n                  ){2}\n                  \\S{3}\\ \n                  (?:\n                    \\S\n                    (?:\n                      \\ \\S{3}\n                    ){2}\n                  ){2}\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--non-spaces\",\n                \"--escape\",\n                \"--with-surrogates\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\S\\ {3}\\S\n                  (?:\n                    \\S{2}\\ \n                  ){2}\n                  \\S{3}\\ \n                  (?:\n                    \\S\n                    (?:\n                      \\ \\S{3}\n                    ){2}\n                  ){2}\n                $\n                \"#\n            )));\n        }\n    }\n}\n\nmod non_word_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[\"--non-words\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^I\\\\W\\\\W\\\\W\\\\W\\\\W\\\\W\\\\W36\\\\Wand\\\\W٣\\\\Wand\\\\Wy̆y̆\\\\Wand\\\\W\\\\W\\\\W\\\\W$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--non-words\", \"--escape\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^I\\\\W\\\\W\\\\W\\\\W\\\\W\\\\W\\\\W36\\\\Wand\\\\W\\\\u{663}\\\\Wand\\\\Wy\\\\u{306}y\\\\u{306}\\\\Wand\\\\W\\\\W\\\\W\\\\W$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--non-words\", \"--escape\", \"--with-surrogates\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^I\\\\W\\\\W\\\\W\\\\W\\\\W\\\\W\\\\W36\\\\Wand\\\\W\\\\u{663}\\\\Wand\\\\Wy\\\\u{306}y\\\\u{306}\\\\Wand\\\\W\\\\W\\\\W\\\\W$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--non-words\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  I\\W\\W\\W\\W\\W\\W\\W36\\Wand\\W٣\\Wand\\Wy̆y̆\\Wand\\W\\W\\W\\W\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--non-words\", \"--escape\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  I\\W\\W\\W\\W\\W\\W\\W36\\Wand\\W\\u{663}\\Wand\\Wy\\u{306}y\\u{306}\\Wand\\W\\W\\W\\W\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--non-words\",\n                \"--escape\",\n                \"--with-surrogates\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  I\\W\\W\\W\\W\\W\\W\\W36\\Wand\\W\\u{663}\\Wand\\Wy\\u{306}y\\u{306}\\Wand\\W\\W\\W\\W\n                $\n                \"#\n            )));\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[\"--repetitions\", \"--non-words\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^I\\\\W{7}36\\\\Wand\\\\W٣\\\\Wand\\\\W(?:y̆){2}\\\\Wand\\\\W{4}$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--repetitions\", \"--non-words\", \"--escape\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^I\\\\W{7}36\\\\Wand\\\\W\\\\u{663}\\\\Wand\\\\W(?:y\\\\u{306}){2}\\\\Wand\\\\W{4}$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--non-words\",\n                \"--escape\",\n                \"--with-surrogates\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^I\\\\W{7}36\\\\Wand\\\\W\\\\u{663}\\\\Wand\\\\W(?:y\\\\u{306}){2}\\\\Wand\\\\W{4}$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--repetitions\", \"--non-words\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  I\\W{7}36\\Wand\\W٣\\Wand\\W\n                  (?:\n                    y̆\n                  ){2}\n                  \\Wand\\W{4}\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--non-words\",\n                \"--escape\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  I\\W{7}36\\Wand\\W\\u{663}\\Wand\\W\n                  (?:\n                    y\\u{306}\n                  ){2}\n                  \\Wand\\W{4}\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--non-words\",\n                \"--escape\",\n                \"--with-surrogates\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  I\\W{7}36\\Wand\\W\\u{663}\\Wand\\W\n                  (?:\n                    y\\u{306}\n                  ){2}\n                  \\Wand\\W{4}\n                $\n                \"#\n            )));\n        }\n    }\n}\n\nmod non_digit_non_space_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[\"--non-digits\", \"--non-spaces\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\S\\\\S\\\\D\\\\D\\\\D\\\\D\\\\D\\\\S\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--non-digits\", \"--non-spaces\", \"--escape\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\S\\\\S\\\\D\\\\D\\\\D\\\\D\\\\D\\\\S\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--non-digits\",\n                \"--non-spaces\",\n                \"--escape\",\n                \"--with-surrogates\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\S\\\\S\\\\D\\\\D\\\\D\\\\D\\\\D\\\\S\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--non-digits\", \"--non-spaces\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\D\\D\\D\\D\\D\\D\\D\\D\\S\\S\\D\\D\\D\\D\\D\\S\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--non-digits\",\n                \"--non-spaces\",\n                \"--escape\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\D\\D\\D\\D\\D\\D\\D\\D\\S\\S\\D\\D\\D\\D\\D\\S\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--non-digits\",\n                \"--non-spaces\",\n                \"--escape\",\n                \"--with-surrogates\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\D\\D\\D\\D\\D\\D\\D\\D\\S\\S\\D\\D\\D\\D\\D\\S\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\n                $\n                \"#\n            )));\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[\"--repetitions\", \"--non-digits\", \"--non-spaces\", TEST_CASE]);\n            grex.assert()\n                .success()\n                .stdout(predicate::eq(\"^\\\\D{8}\\\\S{2}\\\\D{5}\\\\S\\\\D{17}$\\n\"));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--non-digits\",\n                \"--non-spaces\",\n                \"--escape\",\n                TEST_CASE,\n            ]);\n            grex.assert()\n                .success()\n                .stdout(predicate::eq(\"^\\\\D{8}\\\\S{2}\\\\D{5}\\\\S\\\\D{17}$\\n\"));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--non-digits\",\n                \"--non-spaces\",\n                \"--escape\",\n                \"--with-surrogates\",\n                TEST_CASE,\n            ]);\n            grex.assert()\n                .success()\n                .stdout(predicate::eq(\"^\\\\D{8}\\\\S{2}\\\\D{5}\\\\S\\\\D{17}$\\n\"));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--non-digits\",\n                \"--non-spaces\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\D{8}\\S{2}\\D{5}\\S\\D{17}\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--non-digits\",\n                \"--non-spaces\",\n                \"--escape\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\D{8}\\S{2}\\D{5}\\S\\D{17}\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--non-digits\",\n                \"--non-spaces\",\n                \"--escape\",\n                \"--with-surrogates\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\D{8}\\S{2}\\D{5}\\S\\D{17}\n                $\n                \"#\n            )));\n        }\n    }\n}\n\nmod non_digit_non_word_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[\"--non-digits\", \"--non-words\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D36\\\\D\\\\D\\\\D\\\\D\\\\D٣\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--non-digits\", \"--non-words\", \"--escape\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D36\\\\D\\\\D\\\\D\\\\D\\\\D\\\\u{663}\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--non-digits\",\n                \"--non-words\",\n                \"--escape\",\n                \"--with-surrogates\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D36\\\\D\\\\D\\\\D\\\\D\\\\D\\\\u{663}\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--non-digits\", \"--non-words\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\D\\D\\D\\D\\D\\D\\D\\D36\\D\\D\\D\\D\\D٣\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--non-digits\",\n                \"--non-words\",\n                \"--escape\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\D\\D\\D\\D\\D\\D\\D\\D36\\D\\D\\D\\D\\D\\u{663}\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--non-digits\",\n                \"--non-words\",\n                \"--escape\",\n                \"--with-surrogates\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\D\\D\\D\\D\\D\\D\\D\\D36\\D\\D\\D\\D\\D\\u{663}\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\n                $\n                \"#\n            )));\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[\"--repetitions\", \"--non-digits\", \"--non-words\", TEST_CASE]);\n            grex.assert()\n                .success()\n                .stdout(predicate::eq(\"^\\\\D{8}36\\\\D{5}٣\\\\D{17}$\\n\"));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--non-digits\",\n                \"--non-words\",\n                \"--escape\",\n                TEST_CASE,\n            ]);\n            grex.assert()\n                .success()\n                .stdout(predicate::eq(\"^\\\\D{8}36\\\\D{5}\\\\u{663}\\\\D{17}$\\n\"));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--non-digits\",\n                \"--non-words\",\n                \"--escape\",\n                \"--with-surrogates\",\n                TEST_CASE,\n            ]);\n            grex.assert()\n                .success()\n                .stdout(predicate::eq(\"^\\\\D{8}36\\\\D{5}\\\\u{663}\\\\D{17}$\\n\"));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--non-digits\",\n                \"--non-words\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\D{8}36\\D{5}٣\\D{17}\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--non-digits\",\n                \"--non-words\",\n                \"--escape\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\D{8}36\\D{5}\\u{663}\\D{17}\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--non-digits\",\n                \"--non-words\",\n                \"--escape\",\n                \"--with-surrogates\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\D{8}36\\D{5}\\u{663}\\D{17}\n                $\n                \"#\n            )));\n        }\n    }\n}\n\nmod non_space_non_word_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[\"--non-spaces\", \"--non-words\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\S\\\\W\\\\W\\\\W\\\\W\\\\W\\\\W\\\\W\\\\S\\\\S\\\\W\\\\S\\\\S\\\\S\\\\W\\\\S\\\\W\\\\S\\\\S\\\\S\\\\W\\\\S\\\\S\\\\S\\\\S\\\\W\\\\S\\\\S\\\\S\\\\W\\\\W\\\\W\\\\W$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--non-spaces\", \"--non-words\", \"--escape\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\S\\\\W\\\\W\\\\W\\\\W\\\\W\\\\W\\\\W\\\\S\\\\S\\\\W\\\\S\\\\S\\\\S\\\\W\\\\S\\\\W\\\\S\\\\S\\\\S\\\\W\\\\S\\\\S\\\\S\\\\S\\\\W\\\\S\\\\S\\\\S\\\\W\\\\W\\\\W\\\\W$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--non-spaces\",\n                \"--non-words\",\n                \"--escape\",\n                \"--with-surrogates\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\S\\\\W\\\\W\\\\W\\\\W\\\\W\\\\W\\\\W\\\\S\\\\S\\\\W\\\\S\\\\S\\\\S\\\\W\\\\S\\\\W\\\\S\\\\S\\\\S\\\\W\\\\S\\\\S\\\\S\\\\S\\\\W\\\\S\\\\S\\\\S\\\\W\\\\W\\\\W\\\\W$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--non-spaces\", \"--non-words\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\S\\W\\W\\W\\W\\W\\W\\W\\S\\S\\W\\S\\S\\S\\W\\S\\W\\S\\S\\S\\W\\S\\S\\S\\S\\W\\S\\S\\S\\W\\W\\W\\W\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--non-spaces\",\n                \"--non-words\",\n                \"--escape\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\S\\W\\W\\W\\W\\W\\W\\W\\S\\S\\W\\S\\S\\S\\W\\S\\W\\S\\S\\S\\W\\S\\S\\S\\S\\W\\S\\S\\S\\W\\W\\W\\W\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--non-spaces\",\n                \"--non-words\",\n                \"--escape\",\n                \"--with-surrogates\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\S\\W\\W\\W\\W\\W\\W\\W\\S\\S\\W\\S\\S\\S\\W\\S\\W\\S\\S\\S\\W\\S\\S\\S\\S\\W\\S\\S\\S\\W\\W\\W\\W\n                $\n                \"#\n            )));\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[\"--repetitions\", \"--non-spaces\", \"--non-words\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\S\\\\W{7}\\\\S(?:\\\\S\\\\W\\\\S{3}\\\\W){2}\\\\S{4}\\\\W\\\\S{3}\\\\W{4}$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--non-spaces\",\n                \"--non-words\",\n                \"--escape\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\S\\\\W{7}\\\\S(?:\\\\S\\\\W\\\\S{3}\\\\W){2}\\\\S{4}\\\\W\\\\S{3}\\\\W{4}$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--non-spaces\",\n                \"--non-words\",\n                \"--escape\",\n                \"--with-surrogates\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\S\\\\W{7}\\\\S(?:\\\\S\\\\W\\\\S{3}\\\\W){2}\\\\S{4}\\\\W\\\\S{3}\\\\W{4}$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--non-spaces\",\n                \"--non-words\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\S\\W{7}\\S\n                  (?:\n                    \\S\\W\\S{3}\\W\n                  ){2}\n                  \\S{4}\\W\\S{3}\\W{4}\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--non-spaces\",\n                \"--non-words\",\n                \"--escape\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\S\\W{7}\\S\n                  (?:\n                    \\S\\W\\S{3}\\W\n                  ){2}\n                  \\S{4}\\W\\S{3}\\W{4}\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--non-spaces\",\n                \"--non-words\",\n                \"--escape\",\n                \"--with-surrogates\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\S\\W{7}\\S\n                  (?:\n                    \\S\\W\\S{3}\\W\n                  ){2}\n                  \\S{4}\\W\\S{3}\\W{4}\n                $\n                \"#\n            )));\n        }\n    }\n}\n\nmod non_digit_non_space_non_word_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[\"--non-digits\", \"--non-spaces\", \"--non-words\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\S\\\\S\\\\D\\\\D\\\\D\\\\D\\\\D\\\\S\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--non-digits\",\n                \"--non-spaces\",\n                \"--non-words\",\n                \"--escape\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\S\\\\S\\\\D\\\\D\\\\D\\\\D\\\\D\\\\S\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--non-digits\",\n                \"--non-spaces\",\n                \"--non-words\",\n                \"--escape\",\n                \"--with-surrogates\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\S\\\\S\\\\D\\\\D\\\\D\\\\D\\\\D\\\\S\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--non-digits\",\n                \"--non-spaces\",\n                \"--non-words\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\D\\D\\D\\D\\D\\D\\D\\D\\S\\S\\D\\D\\D\\D\\D\\S\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--non-digits\",\n                \"--non-spaces\",\n                \"--non-words\",\n                \"--escape\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\D\\D\\D\\D\\D\\D\\D\\D\\S\\S\\D\\D\\D\\D\\D\\S\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--non-digits\",\n                \"--non-spaces\",\n                \"--non-words\",\n                \"--escape\",\n                \"--with-surrogates\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\D\\D\\D\\D\\D\\D\\D\\D\\S\\S\\D\\D\\D\\D\\D\\S\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\n                $\n                \"#\n            )));\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--non-digits\",\n                \"--non-spaces\",\n                \"--non-words\",\n                TEST_CASE,\n            ]);\n            grex.assert()\n                .success()\n                .stdout(predicate::eq(\"^\\\\D{8}\\\\S{2}\\\\D{5}\\\\S\\\\D{17}$\\n\"));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--non-digits\",\n                \"--non-spaces\",\n                \"--non-words\",\n                \"--escape\",\n                TEST_CASE,\n            ]);\n            grex.assert()\n                .success()\n                .stdout(predicate::eq(\"^\\\\D{8}\\\\S{2}\\\\D{5}\\\\S\\\\D{17}$\\n\"));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--non-digits\",\n                \"--non-spaces\",\n                \"--non-words\",\n                \"--escape\",\n                \"--with-surrogates\",\n                TEST_CASE,\n            ]);\n            grex.assert()\n                .success()\n                .stdout(predicate::eq(\"^\\\\D{8}\\\\S{2}\\\\D{5}\\\\S\\\\D{17}$\\n\"));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--non-digits\",\n                \"--non-spaces\",\n                \"--non-words\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\D{8}\\S{2}\\D{5}\\S\\D{17}\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--non-digits\",\n                \"--non-spaces\",\n                \"--non-words\",\n                \"--escape\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\D{8}\\S{2}\\D{5}\\S\\D{17}\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--non-digits\",\n                \"--non-spaces\",\n                \"--non-words\",\n                \"--escape\",\n                \"--with-surrogates\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\D{8}\\S{2}\\D{5}\\S\\D{17}\n                $\n                \"#\n            )));\n        }\n    }\n}\n\nmod digit_non_digit_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[\"--digits\", \"--non-digits\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\d\\\\d\\\\D\\\\D\\\\D\\\\D\\\\D\\\\d\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--digits\", \"--non-digits\", \"--escape\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\d\\\\d\\\\D\\\\D\\\\D\\\\D\\\\D\\\\d\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--digits\",\n                \"--non-digits\",\n                \"--escape\",\n                \"--with-surrogates\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\d\\\\d\\\\D\\\\D\\\\D\\\\D\\\\D\\\\d\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--digits\", \"--non-digits\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\D\\D\\D\\D\\D\\D\\D\\D\\d\\d\\D\\D\\D\\D\\D\\d\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--digits\",\n                \"--non-digits\",\n                \"--escape\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\D\\D\\D\\D\\D\\D\\D\\D\\d\\d\\D\\D\\D\\D\\D\\d\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--digits\",\n                \"--non-digits\",\n                \"--escape\",\n                \"--with-surrogates\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\D\\D\\D\\D\\D\\D\\D\\D\\d\\d\\D\\D\\D\\D\\D\\d\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\\D\n                $\n                \"#\n            )));\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[\"--repetitions\", \"--digits\", \"--non-digits\", TEST_CASE]);\n            grex.assert()\n                .success()\n                .stdout(predicate::eq(\"^\\\\D{8}\\\\d{2}\\\\D{5}\\\\d\\\\D{17}$\\n\"));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--digits\",\n                \"--non-digits\",\n                \"--escape\",\n                TEST_CASE,\n            ]);\n            grex.assert()\n                .success()\n                .stdout(predicate::eq(\"^\\\\D{8}\\\\d{2}\\\\D{5}\\\\d\\\\D{17}$\\n\"));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--digits\",\n                \"--non-digits\",\n                \"--escape\",\n                \"--with-surrogates\",\n                TEST_CASE,\n            ]);\n            grex.assert()\n                .success()\n                .stdout(predicate::eq(\"^\\\\D{8}\\\\d{2}\\\\D{5}\\\\d\\\\D{17}$\\n\"));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--digits\",\n                \"--non-digits\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\D{8}\\d{2}\\D{5}\\d\\D{17}\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--digits\",\n                \"--non-digits\",\n                \"--escape\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\D{8}\\d{2}\\D{5}\\d\\D{17}\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--digits\",\n                \"--non-digits\",\n                \"--escape\",\n                \"--with-surrogates\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\D{8}\\d{2}\\D{5}\\d\\D{17}\n                $\n                \"#\n            )));\n        }\n    }\n}\n\nmod space_non_space_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[\"--spaces\", \"--non-spaces\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\S\\\\s\\\\s\\\\s\\\\S\\\\S\\\\S\\\\s\\\\S\\\\S\\\\s\\\\S\\\\S\\\\S\\\\s\\\\S\\\\s\\\\S\\\\S\\\\S\\\\s\\\\S\\\\S\\\\S\\\\S\\\\s\\\\S\\\\S\\\\S\\\\s\\\\S\\\\S\\\\S$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--spaces\", \"--non-spaces\", \"--escape\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\S\\\\s\\\\s\\\\s\\\\S\\\\S\\\\S\\\\s\\\\S\\\\S\\\\s\\\\S\\\\S\\\\S\\\\s\\\\S\\\\s\\\\S\\\\S\\\\S\\\\s\\\\S\\\\S\\\\S\\\\S\\\\s\\\\S\\\\S\\\\S\\\\s\\\\S\\\\S\\\\S$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--spaces\",\n                \"--non-spaces\",\n                \"--escape\",\n                \"--with-surrogates\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\S\\\\s\\\\s\\\\s\\\\S\\\\S\\\\S\\\\s\\\\S\\\\S\\\\s\\\\S\\\\S\\\\S\\\\s\\\\S\\\\s\\\\S\\\\S\\\\S\\\\s\\\\S\\\\S\\\\S\\\\S\\\\s\\\\S\\\\S\\\\S\\\\s\\\\S\\\\S\\\\S$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--spaces\", \"--non-spaces\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\S\\s\\s\\s\\S\\S\\S\\s\\S\\S\\s\\S\\S\\S\\s\\S\\s\\S\\S\\S\\s\\S\\S\\S\\S\\s\\S\\S\\S\\s\\S\\S\\S\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--spaces\",\n                \"--non-spaces\",\n                \"--escape\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\S\\s\\s\\s\\S\\S\\S\\s\\S\\S\\s\\S\\S\\S\\s\\S\\s\\S\\S\\S\\s\\S\\S\\S\\S\\s\\S\\S\\S\\s\\S\\S\\S\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--spaces\",\n                \"--non-spaces\",\n                \"--escape\",\n                \"--with-surrogates\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\S\\s\\s\\s\\S\\S\\S\\s\\S\\S\\s\\S\\S\\S\\s\\S\\s\\S\\S\\S\\s\\S\\S\\S\\S\\s\\S\\S\\S\\s\\S\\S\\S\n                $\n                \"#\n            )));\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[\"--repetitions\", \"--spaces\", \"--non-spaces\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\S\\\\s{3}\\\\S(?:\\\\S{2}\\\\s){2}\\\\S{3}\\\\s(?:\\\\S(?:\\\\s\\\\S{3}){2}){2}$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--spaces\",\n                \"--non-spaces\",\n                \"--escape\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\S\\\\s{3}\\\\S(?:\\\\S{2}\\\\s){2}\\\\S{3}\\\\s(?:\\\\S(?:\\\\s\\\\S{3}){2}){2}$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--spaces\",\n                \"--non-spaces\",\n                \"--escape\",\n                \"--with-surrogates\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\S\\\\s{3}\\\\S(?:\\\\S{2}\\\\s){2}\\\\S{3}\\\\s(?:\\\\S(?:\\\\s\\\\S{3}){2}){2}$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--spaces\",\n                \"--non-spaces\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\S\\s{3}\\S\n                  (?:\n                    \\S{2}\\s\n                  ){2}\n                  \\S{3}\\s\n                  (?:\n                    \\S\n                    (?:\n                      \\s\\S{3}\n                    ){2}\n                  ){2}\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--spaces\",\n                \"--non-spaces\",\n                \"--escape\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\S\\s{3}\\S\n                  (?:\n                    \\S{2}\\s\n                  ){2}\n                  \\S{3}\\s\n                  (?:\n                    \\S\n                    (?:\n                      \\s\\S{3}\n                    ){2}\n                  ){2}\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--spaces\",\n                \"--non-spaces\",\n                \"--escape\",\n                \"--with-surrogates\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\S\\s{3}\\S\n                  (?:\n                    \\S{2}\\s\n                  ){2}\n                  \\S{3}\\s\n                  (?:\n                    \\S\n                    (?:\n                      \\s\\S{3}\n                    ){2}\n                  ){2}\n                $\n                \"#\n            )));\n        }\n    }\n}\n\nmod word_non_word_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[\"--words\", \"--non-words\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\w\\\\W\\\\W\\\\W\\\\W\\\\W\\\\W\\\\W\\\\w\\\\w\\\\W\\\\w\\\\w\\\\w\\\\W\\\\w\\\\W\\\\w\\\\w\\\\w\\\\W\\\\w\\\\w\\\\w\\\\w\\\\W\\\\w\\\\w\\\\w\\\\W\\\\W\\\\W\\\\W$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--words\", \"--non-words\", \"--escape\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\w\\\\W\\\\W\\\\W\\\\W\\\\W\\\\W\\\\W\\\\w\\\\w\\\\W\\\\w\\\\w\\\\w\\\\W\\\\w\\\\W\\\\w\\\\w\\\\w\\\\W\\\\w\\\\w\\\\w\\\\w\\\\W\\\\w\\\\w\\\\w\\\\W\\\\W\\\\W\\\\W$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--words\",\n                \"--non-words\",\n                \"--escape\",\n                \"--with-surrogates\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\w\\\\W\\\\W\\\\W\\\\W\\\\W\\\\W\\\\W\\\\w\\\\w\\\\W\\\\w\\\\w\\\\w\\\\W\\\\w\\\\W\\\\w\\\\w\\\\w\\\\W\\\\w\\\\w\\\\w\\\\w\\\\W\\\\w\\\\w\\\\w\\\\W\\\\W\\\\W\\\\W$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--words\", \"--non-words\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\w\\W\\W\\W\\W\\W\\W\\W\\w\\w\\W\\w\\w\\w\\W\\w\\W\\w\\w\\w\\W\\w\\w\\w\\w\\W\\w\\w\\w\\W\\W\\W\\W\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--words\", \"--non-words\", \"--escape\", \"--verbose\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\w\\W\\W\\W\\W\\W\\W\\W\\w\\w\\W\\w\\w\\w\\W\\w\\W\\w\\w\\w\\W\\w\\w\\w\\w\\W\\w\\w\\w\\W\\W\\W\\W\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--words\",\n                \"--non-words\",\n                \"--escape\",\n                \"--with-surrogates\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\w\\W\\W\\W\\W\\W\\W\\W\\w\\w\\W\\w\\w\\w\\W\\w\\W\\w\\w\\w\\W\\w\\w\\w\\w\\W\\w\\w\\w\\W\\W\\W\\W\n                $\n                \"#\n            )));\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[test]\n        fn succeeds() {\n            let mut grex = init_command();\n            grex.args(&[\"--repetitions\", \"--words\", \"--non-words\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\w\\\\W{7}\\\\w(?:\\\\w\\\\W\\\\w{3}\\\\W){2}\\\\w{4}\\\\W\\\\w{3}\\\\W{4}$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--words\",\n                \"--non-words\",\n                \"--escape\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\w\\\\W{7}\\\\w(?:\\\\w\\\\W\\\\w{3}\\\\W){2}\\\\w{4}\\\\W\\\\w{3}\\\\W{4}$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--words\",\n                \"--non-words\",\n                \"--escape\",\n                \"--with-surrogates\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(\n                \"^\\\\w\\\\W{7}\\\\w(?:\\\\w\\\\W\\\\w{3}\\\\W){2}\\\\w{4}\\\\W\\\\w{3}\\\\W{4}$\\n\",\n            ));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--words\",\n                \"--non-words\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\w\\W{7}\\w\n                  (?:\n                    \\w\\W\\w{3}\\W\n                  ){2}\n                  \\w{4}\\W\\w{3}\\W{4}\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--words\",\n                \"--non-words\",\n                \"--escape\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\w\\W{7}\\w\n                  (?:\n                    \\w\\W\\w{3}\\W\n                  ){2}\n                  \\w{4}\\W\\w{3}\\W{4}\n                $\n                \"#\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_escape_and_surrogate_and_verbose_mode_option() {\n            let mut grex = init_command();\n            grex.args(&[\n                \"--repetitions\",\n                \"--words\",\n                \"--non-words\",\n                \"--escape\",\n                \"--with-surrogates\",\n                \"--verbose\",\n                TEST_CASE,\n            ]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\w\\W{7}\\w\n                  (?:\n                    \\w\\W\\w{3}\\W\n                  ){2}\n                  \\w{4}\\W\\w{3}\\W{4}\n                $\n                \"#\n            )));\n        }\n    }\n}\n\nmod anchor_conversion {\n    use super::*;\n\n    mod no_verbose {\n        use super::*;\n\n        #[test]\n        fn succeeds_with_no_start_anchor_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--no-start-anchor\", TEST_CASE]);\n            grex.assert()\n                .success()\n                .stdout(predicate::eq(\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩\\\\.$\\n\"));\n        }\n\n        #[test]\n        fn succeeds_with_no_end_anchor_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--no-end-anchor\", TEST_CASE]);\n            grex.assert()\n                .success()\n                .stdout(predicate::eq(\"^I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩\\\\.\\n\"));\n        }\n\n        #[test]\n        fn succeeds_with_no_anchors_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--no-anchors\", TEST_CASE]);\n            grex.assert()\n                .success()\n                .stdout(predicate::eq(\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩\\\\.\\n\"));\n        }\n    }\n\n    mod verbose {\n        use super::*;\n\n        #[test]\n        fn succeeds_with_verbose_mode_and_no_start_anchor_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--verbose\", \"--no-start-anchor\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                  I\\ \\ \\ ♥♥♥\\ 36\\ and\\ ٣\\ and\\ y̆y̆\\ and\\ 💩💩\\.\n                $\n                \"#,\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_and_no_end_anchor_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--verbose\", \"--no-end-anchor\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                ^\n                  I\\ \\ \\ ♥♥♥\\ 36\\ and\\ ٣\\ and\\ y̆y̆\\ and\\ 💩💩\\.\n                \"#,\n            )));\n        }\n\n        #[test]\n        fn succeeds_with_verbose_mode_and_no_anchors_option() {\n            let mut grex = init_command();\n            grex.args(&[\"--verbose\", \"--no-anchors\", TEST_CASE]);\n            grex.assert().success().stdout(predicate::eq(indoc!(\n                r#\"\n                (?x)\n                  I\\ \\ \\ ♥♥♥\\ 36\\ and\\ ٣\\ and\\ y̆y̆\\ and\\ 💩💩\\.\n                \"#,\n            )));\n        }\n    }\n}\n\nfn init_command() -> Command {\n    Command::new(cargo_bin!())\n}\n"
  },
  {
    "path": "tests/lib_integration_tests.rs",
    "content": "/*\n * Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#![cfg(not(target_family = \"wasm\"))]\n\nuse grex::RegExpBuilder;\nuse indoc::indoc;\nuse regex::Regex;\nuse rstest::rstest;\nuse std::io::Write;\nuse tempfile::NamedTempFile;\n\nmod no_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\"\"], \"^$\"),\n            case(vec![\" \"], \"^ $\"),\n            case(vec![\"   \"], \"^   $\"),\n            case(vec![\"[\"], \"^\\\\[$\"),\n            case(vec![\"a\", \"(\"], \"^[(a]$\"),\n            case(vec![\"a\", \"\\n\"], \"^[\\\\na]$\"),\n            case(vec![\"a\", \"[\"], \"^[\\\\[a]$\"),\n            case(vec![\"a\", \"-\", \"c\", \"!\"], \"^[!\\\\-ac]$\"),\n            case(vec![\"a\", \"b\"], \"^[ab]$\"),\n            case(vec![\"a\", \"b\", \"c\"], \"^[a-c]$\"),\n            case(vec![\"a\", \"c\", \"d\", \"e\", \"f\"], \"^[ac-f]$\"),\n            case(vec![\"a\", \"b\", \"x\", \"d\", \"e\"], \"^[abdex]$\"),\n            case(vec![\"a\", \"b\", \"x\", \"de\"], \"^(?:de|[abx])$\"),\n            case(vec![\"a\", \"b\", \"c\", \"x\", \"d\", \"e\"], \"^[a-ex]$\"),\n            case(vec![\"a\", \"b\", \"c\", \"x\", \"de\"], \"^(?:de|[a-cx])$\"),\n            case(vec![\"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"o\", \"x\", \"y\", \"z\"], \"^[a-fox-z]$\"),\n            case(vec![\"a\", \"b\", \"d\", \"e\", \"f\", \"o\", \"x\", \"y\", \"z\"], \"^[abd-fox-z]$\"),\n            case(vec![\"1\", \"2\"], \"^[12]$\"),\n            case(vec![\"1\", \"2\", \"3\"], \"^[1-3]$\"),\n            case(vec![\"1\", \"3\", \"4\", \"5\", \"6\"], \"^[13-6]$\"),\n            case(vec![\"1\", \"2\", \"8\", \"4\", \"5\"], \"^[12458]$\"),\n            case(vec![\"1\", \"2\", \"8\", \"45\"], \"^(?:45|[128])$\"),\n            case(vec![\"1\", \"2\", \"3\", \"8\", \"4\", \"5\"], \"^[1-58]$\"),\n            case(vec![\"1\", \"2\", \"3\", \"8\", \"45\"], \"^(?:45|[1-38])$\"),\n            case(vec![\"1\", \"2\", \"3\", \"5\", \"7\", \"8\", \"9\"], \"^[1-357-9]$\"),\n            case(vec![\"a\", \"b\", \"bc\"], \"^(?:bc?|a)$\"),\n            case(vec![\"a\", \"b\", \"bcd\"], \"^(?:b(?:cd)?|a)$\"),\n            case(vec![\"a\", \"ab\", \"abc\"], \"^a(?:bc?)?$\"),\n            case(vec![\"ac\", \"bc\"], \"^[ab]c$\"),\n            case(vec![\"ab\", \"ac\"], \"^a[bc]$\"),\n            case(vec![\"bc\", \"abc\"], \"^a?bc$\"),\n            case(vec![\"ac\", \"abc\"], \"^ab?c$\"),\n            case(vec![\"abc\", \"abxyc\"], \"^ab(?:xy)?c$\"),\n            case(vec![\"ab\", \"abc\"], \"^abc?$\"),\n            case(vec![\"abx\", \"cdx\"], \"^(?:ab|cd)x$\"),\n            case(vec![\"abd\", \"acd\"], \"^a[bc]d$\"),\n            case(vec![\"abc\", \"abcd\"], \"^abcd?$\"),\n            case(vec![\"abc\", \"abcde\"], \"^abc(?:de)?$\"),\n            case(vec![\"ade\", \"abcde\"], \"^a(?:bc)?de$\"),\n            case(vec![\"abcxy\", \"adexy\"], \"^a(?:bc|de)xy$\"),\n            case(vec![\"axy\", \"abcxy\", \"adexy\"], \"^a(?:(?:bc)?|de)xy$\"), // goal: \"^a(bc|de)?xy$\"\n            case(vec![\"abcxy\", \"abcw\", \"efgh\"], \"^(?:abc(?:xy|w)|efgh)$\"),\n            case(vec![\"abcxy\", \"efgh\", \"abcw\"], \"^(?:abc(?:xy|w)|efgh)$\"),\n            case(vec![\"efgh\", \"abcxy\", \"abcw\"], \"^(?:abc(?:xy|w)|efgh)$\"),\n            case(vec![\"abxy\", \"cxy\", \"efgh\"], \"^(?:(?:ab|c)xy|efgh)$\"),\n            case(vec![\"abxy\", \"efgh\", \"cxy\"], \"^(?:(?:ab|c)xy|efgh)$\"),\n            case(vec![\"efgh\", \"abxy\", \"cxy\"], \"^(?:(?:ab|c)xy|efgh)$\"),\n            case(vec![\"aaacaac\", \"aac\"], \"^aa(?:acaa)?c$\"),\n            case(vec![\"a\", \"ä\", \"o\", \"ö\", \"u\", \"ü\"], \"^[aouäöü]$\"),\n            case(vec![\"y̆\", \"a\", \"z\"], \"^(?:y̆|[az])$\"), // goal: \"^[az]|y\\\\u{306}$\"\n            case(vec![\"a\", \"b\\n\", \"c\"], \"^(?:b\\\\n|[ac])$\"),\n            case(vec![\"a\", \"b\\\\n\", \"c\"], \"^(?:b\\\\\\\\n|[ac])$\"),\n            case(vec![\"[a-z]\", \"(d,e,f)\"], \"^(?:\\\\(d,e,f\\\\)|\\\\[a\\\\-z\\\\])$\"),\n            case(vec![\"3.5\", \"4.5\", \"4,5\"], \"^(?:3\\\\.5|4[,.]5)$\"),\n            case(vec![\"\\u{b}\"], \"^\\\\v$\"), // U+000B Line Tabulation\n            case(vec![\"\\\\u{b}\"], \"^\\\\\\\\u\\\\{b\\\\}$\"),\n            case(vec![\"\\u{c}\"], \"^\\\\f$\"), // U+000C Form Feed\n            case(vec![\"\\\\u{c}\"], \"^\\\\\\\\u\\\\{c\\\\}$\"),\n            case(vec![\"\\u{200b}\"], \"^​$\"),\n            case(vec![\"I ♥ cake\"], \"^I ♥ cake$\"),\n            case(vec![\"I \\u{2665} cake\"], \"^I ♥ cake$\"),\n            case(vec![\"I \\\\u{2665} cake\"], \"^I \\\\\\\\u\\\\{2665\\\\} cake$\"),\n            case(vec![\"I \\\\u2665 cake\"], \"^I \\\\\\\\u2665 cake$\"),\n            case(vec![\"My ♥ is yours.\", \"My 💩 is yours.\"], \"^My [♥💩] is yours\\\\.$\"),\n            case(vec![\"[\\u{c3e}\"], \"^\\\\[\\u{c3e}$\"),\n            case(vec![\"\\\\\\u{10376}\"], \"^\\\\\\\\\\u{10376}$\"),\n            case(vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"], \"^I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩\\\\.$\"),\n            case(vec![\"\\u{890}\\0\"], \"^\\u{890}\\0$\"),\n            case(vec![\"\\u{890}\\\\0\"], \"^\\u{890}\\\\\\\\0$\"),\n            case(vec![\"\\u{890}\\\\\\0\"], \"^\\u{890}\\\\\\\\\\0$\"),\n            case(vec![\"\\u{890}\\\\\\\\0\"], \"^\\u{890}\\\\\\\\\\\\\\\\0$\"),\n            case(vec![\"\\\\𑇂\"], \"^\\\\\\\\𑇂$\"),\n            case(vec![\"𑇂\\\\\"], \"^𑇂\\\\\\\\$\")\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases).build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\"İ\"], \"(?i)^İ$\"),\n            case(vec![\"ABC\", \"abc\", \"AbC\", \"aBc\"], \"(?i)^abc$\"),\n            case(vec![\"ABC\", \"zBC\", \"abc\", \"AbC\", \"aBc\"], \"(?i)^[az]bc$\"),\n            case(vec![\"Ä@Ö€Ü\", \"ä@ö€ü\", \"Ä@ö€Ü\", \"ä@Ö€ü\"], \"(?i)^ä@ö€ü$\"),\n        )]\n        fn succeeds_with_ignore_case_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_case_insensitive_matching()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\"My ♥ and 💩 is yours.\"], \"^My \\\\u{2665} and \\\\u{1f4a9} is yours\\\\.$\"),\n            case(vec![\"My ♥ is yours.\", \"My 💩 is yours.\"], \"^My (?:\\\\u{2665}|\\\\u{1f4a9}) is yours\\\\.$\"),\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^I   \\\\u{2665}\\\\u{2665}\\\\u{2665} 36 and \\\\u{663} and y\\\\u{306}y\\\\u{306} and \\\\u{1f4a9}\\\\u{1f4a9}\\\\.$\"\n            )\n        )]\n        fn succeeds_with_escape_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_escaping_of_non_ascii_chars(false)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\"My ♥ and 💩 is yours.\"], \"^My \\\\u{2665} and \\\\u{d83d}\\\\u{dca9} is yours\\\\.$\"),\n            case(vec![\"My ♥ is yours.\", \"My 💩 is yours.\"], \"^My (?:\\\\u{2665}|\\\\u{d83d}\\\\u{dca9}) is yours\\\\.$\"),\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^I   \\\\u{2665}\\\\u{2665}\\\\u{2665} 36 and \\\\u{663} and y\\\\u{306}y\\\\u{306} and \\\\u{d83d}\\\\u{dca9}\\\\u{d83d}\\\\u{dca9}\\\\.$\"\n            )\n        )]\n        fn succeeds_with_escape_and_surrogate_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_escaping_of_non_ascii_chars(true)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\"a\", \"b\", \"bc\"], \"^(bc?|a)$\"),\n            case(vec![\"a\", \"b\", \"bcd\"], \"^(b(cd)?|a)$\"),\n            case(vec![\"a\", \"ab\", \"abc\"], \"^a(bc?)?$\"),\n            case(vec![\"efgh\", \"abcxy\", \"abcw\"], \"^(abc(xy|w)|efgh)$\"),\n        )]\n        fn succeeds_with_capturing_groups_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_capturing_groups()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\"\"], indoc!(\n                r#\"\n                (?x)\n                ^\n                $\"#\n            )),\n            case(vec![\" \"], indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\ \n                $\"#\n            )),\n            case(vec![\"   \"], indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\ \\ \\ \n                $\"#\n            )),\n            case(vec![\"\\u{200b}\"], indoc!(\n                r#\"\n                (?x)\n                ^\n                  ​\n                $\"#\n            )),\n            case(vec![\"a\", \"b\", \"c\"], indoc!(\n                r#\"\n                (?x)\n                ^\n                  [a-c]\n                $\"#\n            )),\n            case(vec![\"a\", \"b\", \"bc\"], indoc!(\n                r#\"\n                (?x)\n                ^\n                  (?:\n                    bc?\n                    |\n                    a\n                  )\n                $\"#\n            )),\n            case(vec![\"a\", \"ab\", \"abc\"], indoc!(\n                r#\"\n                (?x)\n                ^\n                  a\n                  (?:\n                    bc?\n                  )?\n                $\"#\n            )),\n            case(vec![\"a\", \"b\", \"bcd\"], indoc!(\n                r#\"\n                (?x)\n                ^\n                  (?:\n                    b\n                    (?:\n                      cd\n                    )?\n                    |\n                    a\n                  )\n                $\"#\n            )),\n            case(vec![\"a\", \"b\", \"x\", \"de\"], indoc!(\n                r#\"\n                (?x)\n                ^\n                  (?:\n                    de\n                    |\n                    [abx]\n                  )\n                $\"#\n            )),\n            case(vec![\"[a-z]\", \"(d,e,f)\"], indoc!(\n                r#\"\n                (?x)\n                ^\n                  (?:\n                    \\(d,e,f\\)\n                    |\n                    \\[a\\-z\\]\n                  )\n                $\"#\n            )),\n            case(vec![\"3.5\", \"4.5\", \"4,5\"], indoc!(\n                r#\"\n                (?x)\n                ^\n                  (?:\n                    3\\.5\n                    |\n                    4[,.]5\n                  )\n                $\"#\n            )),\n            case(vec![\"Ga\", \"G)\"], indoc!(\n                r#\"\n                (?x)\n                ^\n                  G[)a]\n                $\"#\n            )),\n            case(vec![\"aG\", \")G\"], indoc!(\n                r#\"\n                (?x)\n                ^\n                  [)a]G\n                $\"#\n            )),\n            case(vec![\"Ga\", \"G)\", \"G(\"], indoc!(\n                r#\"\n                (?x)\n                ^\n                  G[()a]\n                $\"#\n            )),\n            case(vec![\"aG\", \")G\", \"(G\"], indoc!(\n                r#\"\n                (?x)\n                ^\n                  [()a]G\n                $\"#\n            )),\n            case(vec![\"aaacaac\", \"aac\"], indoc!(\n                r#\"\n                (?x)\n                ^\n                  aa\n                  (?:\n                    acaa\n                  )?\n                  c\n                $\"#\n            )),\n        )]\n        fn succeeds_with_verbose_mode_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases).with_verbose_mode().build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\"İ\"], indoc!(\n                r#\"\n                (?ix)\n                ^\n                  İ\n                $\"#\n            )),\n            case(vec![\"ABC\", \"abc\", \"AbC\", \"aBc\"], indoc!(\n                r#\"\n                (?ix)\n                ^\n                  abc\n                $\"#\n            )),\n            case(vec![\"ABC\", \"zBC\", \"abc\", \"AbC\", \"aBc\"], indoc!(\n                r#\"\n                (?ix)\n                ^\n                  [az]bc\n                $\"#\n            )),\n            case(vec![\"Ä@Ö€Ü\", \"ä@ö€ü\", \"Ä@ö€Ü\", \"ä@Ö€ü\"], indoc!(\n                r#\"\n                (?ix)\n                ^\n                  ä@ö€ü\n                $\"#\n            ))\n        )]\n        fn succeeds_with_ignore_case_and_verbose_mode_option(\n            test_cases: Vec<&str>,\n            expected_output: &str,\n        ) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_case_insensitive_matching()\n                .with_verbose_mode()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[test]\n        fn succeeds_with_file_input() {\n            let mut file = NamedTempFile::new().unwrap();\n            writeln!(file, \"a\\nb\\nc\\r\\nxyz\").unwrap();\n\n            let expected_output = \"^(?:xyz|[a-c])$\";\n            let test_cases = vec![\"a\", \"b\", \"c\", \"xyz\"];\n\n            let regexp = RegExpBuilder::from_file(file.path()).build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\"\"], \"^$\"),\n            case(vec![\" \"], \"^ $\"),\n            case(vec![\"   \"], \"^ {3}$\"),\n            case(vec![\"a\"], \"^a$\"),\n            case(vec![\"aa\"], \"^a{2}$\"),\n            case(vec![\"aaa\"], \"^a{3}$\"),\n            case(vec![\"aaa aaa\"], \"^a{3} a{3}$\"),\n            case(vec![\"ababab ababab\"], \"^(?:ab){3} (?:ab){3}$\"),\n            case(vec![\"ababab  ababab\"], \"^(?:ab){3} {2}(?:ab){3}$\"),\n            case(vec![\"a ababab ababab\"], \"^a(?: (?:ab){3}){2}$\"),\n            case(vec![\"ababab ababab a\"], \"^a(?:b(?:ab){2} a){2}$\"),\n            case(vec![\"ababababab abab ababab\"], \"^ababab(?:(?:ab){2} ){2}(?:ab){3}$\"),\n            case(vec![\"a\", \"aa\"], \"^a{1,2}$\"),\n            case(vec![\"aaa\", \"a\", \"aa\"], \"^a{1,3}$\"),\n            case(vec![\"aaaa\", \"a\", \"aa\"], \"^(?:a{1,2}|a{4})$\"),\n            case(vec![\"a\", \"aa\", \"aaa\", \"aaaa\", \"aaab\"], \"^(?:a{3}b|a{1,4})$\"),\n            case(vec![\"baabaaaaaabb\"], \"^ba{2}ba{6}b{2}$\"),\n            case(vec![\"aabbaabbaaa\"], \"^(?:a{2}b{2}){2}a{3}$\"),\n            case(vec![\"aabbaa\"], \"^a{2}b{2}a{2}$\"),\n            case(vec![\"aabbabb\"], \"^a(?:ab{2}){2}$\"),\n            case(vec![\"ababab\"], \"^(?:ab){3}$\"),\n            case(vec![\"abababa\"], \"^a(?:ba){3}$\"),\n            case(vec![\"aababab\"], \"^a(?:ab){3}$\"),\n            case(vec![\"abababaa\"], \"^(?:ab){3}a{2}$\"),\n            case(vec![\"aaaaaabbbbb\"], \"^a{6}b{5}$\"),\n            case(vec![\"aabaababab\"], \"^a{2}ba(?:ab){3}$\"),\n            case(vec![\"aaaaaaabbbbbba\"], \"^a{7}b{6}a$\"),\n            case(vec![\"abaaaabaaba\"], \"^abaaa(?:aba){2}$\"),\n            case(vec![\"bbaababb\"], \"^b{2}a{2}bab{2}$\"),\n            case(vec![\"b\", \"ba\"], \"^ba?$\"),\n            case(vec![\"b\", \"ba\", \"baa\"], \"^b(?:a{1,2})?$\"),\n            case(vec![\"b\", \"ba\", \"baaa\", \"baa\"], \"^b(?:a{1,3})?$\"),\n            case(vec![\"b\", \"ba\", \"baaaa\", \"baa\"], \"^b(?:a{1,2}|a{4})?$\"),\n            case(vec![\"axy\", \"abcxyxy\", \"adexy\"], \"^a(?:(?:de)?xy|bc(?:xy){2})$\"),\n            case(vec![\"xy̆y̆y̆y̆z\"], \"^x(?:y̆){4}z$\"),\n            case(vec![\"xy̆y̆z\", \"xy̆y̆y̆z\"], \"^x(?:y̆){2,3}z$\"),\n            case(vec![\"xy̆y̆z\", \"xy̆y̆y̆y̆z\"], \"^x(?:(?:y̆){2}|(?:y̆){4})z$\"),\n            case(vec![\"zyxx\", \"yxx\"], \"^z?yx{2}$\"),\n            case(vec![\"zyxx\", \"yxx\", \"yxxx\"], \"^(?:zyx{2}|yx{2,3})$\"),\n            case(vec![\"zyxxx\", \"yxx\", \"yxxx\"], \"^(?:zyx{3}|yx{2,3})$\"),\n            case(vec![\"a\", \"b\\n\\n\", \"c\"], \"^(?:b\\\\n{2}|[ac])$\"),\n            case(vec![\"a\", \"b\\nb\\nb\", \"c\"], \"^(?:b(?:\\\\nb){2}|[ac])$\"),\n            case(vec![\"a\", \"b\\nx\\nx\", \"c\"], \"^(?:b(?:\\\\nx){2}|[ac])$\"),\n            case(vec![\"a\", \"b\\n\\t\\n\\t\", \"c\"], \"^(?:b(?:\\\\n\\\\t){2}|[ac])$\"),\n            case(vec![\"a\", \"b\\n\", \"b\\n\\n\", \"b\\n\\n\\n\", \"c\"], \"^(?:b\\\\n{1,3}|[ac])$\"),\n            case(vec![\"4.5\", \"3.55\"], \"^(?:4\\\\.5|3\\\\.5{2})$\"),\n            case(vec![\"4.5\", \"4.55\"], \"^4\\\\.5{1,2}$\"),\n            case(vec![\"4.5\", \"4.55\", \"3.5\"], \"^(?:3\\\\.5|4\\\\.5{1,2})$\"),\n            case(vec![\"4.5\", \"44.5\", \"44.55\", \"4.55\"], \"^4{1,2}\\\\.5{1,2}$\"),\n            case(vec![\"I ♥♥ cake\"], \"^I ♥{2} cake$\"),\n            case(vec![\"I ♥ cake\", \"I ♥♥ cake\"], \"^I ♥{1,2} cake$\"),\n            case(vec![\"I \\u{2665}\\u{2665} cake\"], \"^I ♥{2} cake$\"),\n            case(vec![\"I \\\\u{2665} cake\"], \"^I \\\\\\\\u\\\\{26{2}5\\\\} cake$\"),\n            case(vec![\"I \\\\u{2665}\\\\u{2665} cake\"], \"^I (?:\\\\\\\\u\\\\{26{2}5\\\\}){2} cake$\"),\n            case(vec![\"I \\\\u2665\\\\u2665 cake\"], \"^I (?:\\\\\\\\u26{2}5){2} cake$\"),\n            case(vec![\"My ♥♥♥ is yours.\", \"My 💩💩 is yours.\"], \"^My (?:💩{2}|♥{3}) is yours\\\\.$\"),\n            case(vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"], \"^I {3}♥{3} 36 and ٣ and (?:y̆){2} and 💩{2}\\\\.$\")\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\"İ\", \"İİ\"], \"(?i)^İ{1,2}$\"),\n            case(vec![\"AAAAB\", \"aaaab\", \"AaAaB\", \"aAaAB\"], \"(?i)^a{4}b$\"),\n            case(vec![\"ÄÖÜäöü@Ö€\", \"äöüÄöÜ@ö€\"], \"(?i)^(?:äöü){2}@ö€$\"),\n        )]\n        fn succeeds_with_ignore_case_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_case_insensitive_matching()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\"My ♥♥♥ and 💩💩 is yours.\"], \"^My \\\\u{2665}{3} and \\\\u{1f4a9}{2} is yours\\\\.$\"),\n            case(vec![\"My ♥♥♥ is yours.\", \"My 💩💩 is yours.\"], \"^My (?:\\\\u{1f4a9}{2}|\\\\u{2665}{3}) is yours\\\\.$\"),\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^I {3}\\\\u{2665}{3} 36 and \\\\u{663} and (?:y\\\\u{306}){2} and \\\\u{1f4a9}{2}\\\\.$\"\n            )\n        )]\n        fn succeeds_with_escape_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_escaping_of_non_ascii_chars(false)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\"My ♥♥♥ and 💩💩 is yours.\"], \"^My \\\\u{2665}{3} and (?:\\\\u{d83d}\\\\u{dca9}){2} is yours\\\\.$\"),\n            case(vec![\"My ♥♥♥ is yours.\", \"My 💩💩 is yours.\"], \"^My (?:(?:\\\\u{d83d}\\\\u{dca9}){2}|\\\\u{2665}{3}) is yours\\\\.$\"),\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^I {3}\\\\u{2665}{3} 36 and \\\\u{663} and (?:y\\\\u{306}){2} and (?:\\\\u{d83d}\\\\u{dca9}){2}\\\\.$\"\n            )\n        )]\n        fn succeeds_with_escape_and_surrogate_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_escaping_of_non_ascii_chars(true)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\"   \"], indoc!(\n                r#\"\n                (?x)\n                ^\n                  \\ {3}\n                $\"#\n            )),\n            case(vec![\"aa\"], indoc!(\n                r#\"\n                (?x)\n                ^\n                  a{2}\n                $\"#\n            )),\n            case(vec![\"aaa\", \"a\", \"aa\"], indoc!(\n                r#\"\n                (?x)\n                ^\n                  a{1,3}\n                $\"#\n            )),\n            case(vec![\"aaaa\", \"a\", \"aa\"], indoc!(\n                r#\"\n                (?x)\n                ^\n                  (?:\n                    a{1,2}\n                    |\n                    a{4}\n                  )\n                $\"#\n            )),\n            case(vec![\"ababab\"], indoc!(\n                r#\"\n                (?x)\n                ^\n                  (?:\n                    ab\n                  ){3}\n                $\"#\n            )),\n            case(vec![\"abababa\"], indoc!(\n                r#\"\n                (?x)\n                ^\n                  a\n                  (?:\n                    ba\n                  ){3}\n                $\"#\n            )),\n            case(vec![\"abababaa\"], indoc!(\n                r#\"\n                (?x)\n                ^\n                  (?:\n                    ab\n                  ){3}\n                  a{2}\n                $\"#\n            )),\n            case(vec![\"aabaababab\"], indoc!(\n                r#\"\n                (?x)\n                ^\n                  a{2}ba\n                  (?:\n                    ab\n                  ){3}\n                $\"#\n            )),\n            case(vec![\"abaaaabaaba\"], indoc!(\n                r#\"\n                (?x)\n                ^\n                  abaaa\n                  (?:\n                    aba\n                  ){2}\n                $\"#\n            )),\n            case(vec![\"xy̆y̆z\", \"xy̆y̆y̆y̆z\"], indoc!(\n                r#\"\n                (?x)\n                ^\n                  x\n                  (?:\n                    (?:\n                      y̆\n                    ){2}\n                    |\n                    (?:\n                      y̆\n                    ){4}\n                  )\n                  z\n                $\"#\n            )),\n            case(vec![\"a\", \"b\\n\\t\\n\\t\", \"c\"], indoc!(\n                r#\"\n                (?x)\n                ^\n                  (?:\n                    b\n                    (?:\n                      \\n\\t\n                    ){2}\n                    |\n                    [ac]\n                  )\n                $\"#\n            )),\n            case(vec![\"My ♥♥♥ is yours.\", \"My 💩💩 is yours.\"], indoc!(\n                r#\"\n                (?x)\n                ^\n                  My\\ \n                  (?:\n                    💩{2}\n                    |\n                    ♥{3}\n                  )\n                  \\ is\\ yours\\.\n                $\"#\n            ))\n        )]\n        fn succeeds_with_verbose_mode_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_verbose_mode()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\"\"], \"^$\"),\n            case(vec![\" \"], \"^ $\"),\n            case(vec![\"   \"], \"^   $\"),\n            case(vec![\"    \"], \"^ {4}$\"),\n            case(vec![\"      \"], \"^ {6}$\"),\n            case(vec![\"a\"], \"^a$\"),\n            case(vec![\"aa\"], \"^aa$\"),\n            case(vec![\"aaa\"], \"^aaa$\"),\n            case(vec![\"aaaa\"], \"^a{4}$\"),\n            case(vec![\"aaaaa\"], \"^a{5}$\"),\n            case(vec![\"ababababab abab ababab\"], \"^(?:ab){5} abab ababab$\"),\n            case(vec![\"aabbaaaabbbabbbbba\"], \"^aabba{4}bbbab{5}a$\"),\n            case(vec![\"baabaaaaaabb\"], \"^baaba{6}bb$\"),\n            case(vec![\"ababab\"], \"^ababab$\"),\n            case(vec![\"abababab\"], \"^(?:ab){4}$\"),\n            case(vec![\"abababa\"], \"^abababa$\"),\n            case(vec![\"ababababa\"], \"^a(?:ba){4}$\"),\n            case(vec![\"aababab\"], \"^aababab$\"),\n            case(vec![\"aabababab\"], \"^a(?:ab){4}$\"),\n            case(vec![\"xy̆y̆z\", \"xy̆y̆y̆y̆z\"], \"^x(?:y̆y̆|(?:y̆){4})z$\"),\n            case(vec![\"aaa\", \"a\", \"aa\"], \"^a(?:aa?)?$\"),\n            case(vec![\"a\", \"aa\", \"aaa\", \"aaaa\"], \"^(?:aaa|aa?|a{4})$\"),\n            case(vec![\"a\", \"aa\", \"aaa\", \"aaaa\", \"aaaaa\", \"aaaaaa\"], \"^(?:aaa|aa?|a{4,6})$\")\n        )]\n        fn succeeds_with_increased_minimum_repetitions(\n            test_cases: Vec<&str>,\n            expected_output: &str,\n        ) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_minimum_repetitions(3)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\"aaa\"], \"^aaa$\"),\n            case(vec![\"ababab\"], \"^ababab$\"),\n            case(vec![\"abcabcabc\"], \"^(?:abc){3}$\"),\n            case(vec![\"abcabcabc\", \"dede\"], \"^(?:dede|(?:abc){3})$\"),\n            case(vec![\"abcabcabc\", \"defgdefg\"], \"^(?:(?:defg){2}|(?:abc){3})$\"),\n            case(vec![\"ababababab abab ababab\"], \"^ababab(?:abab ){2}ababab$\")\n        )]\n        fn succeeds_with_increased_minimum_substring_length(\n            test_cases: Vec<&str>,\n            expected_output: &str,\n        ) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_minimum_substring_length(3)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\"abababab\"], \"^abababab$\"),\n            case(vec![\"abcabcabc\"], \"^abcabcabc$\"),\n            case(vec![\"abcabcabcabc\"], \"^(?:abc){4}$\"),\n            case(vec![\"aaaaaaaaaaaa\"], \"^aaaaaaaaaaaa$\"),\n            case(vec![\"abababab\", \"abcabcabcabc\"], \"^(?:abababab|(?:abc){4})$\"),\n            case(vec![\"ababababab abab ababab\"], \"^ababababab abab ababab$\")\n        )]\n        fn succeeds_with_increased_minimum_repetitions_and_substring_length(\n            test_cases: Vec<&str>,\n            expected_output: &str,\n        ) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_minimum_repetitions(3)\n                .with_minimum_substring_length(3)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n    }\n}\n\nmod digit_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\"\"], \"^$\"),\n            case(vec![\"a\"], \"^a$\"),\n            case(vec![\"1\"], \"^\\\\d$\"),\n            case(vec![\"-1\"], \"^\\\\-\\\\d$\"),\n            case(vec![\"12\"], \"^\\\\d\\\\d$\"),\n            case(vec![\"1\", \"2\"], \"^\\\\d$\"),\n            case(vec![\"1\", \"23\"], \"^\\\\d(?:\\\\d)?$\"),\n            case(vec![\"1\", \"234\"], \"^\\\\d(?:\\\\d\\\\d)?$\"),\n            case(vec![\"8\", \"234\"], \"^\\\\d(?:\\\\d\\\\d)?$\"),\n            case(vec![\"890\", \"34\"], \"^\\\\d\\\\d(?:\\\\d)?$\"),\n            case(vec![\"abc123\"], \"^abc\\\\d\\\\d\\\\d$\"),\n            case(vec![\"a1b2c3\"], \"^a\\\\db\\\\dc\\\\d$\"),\n            case(vec![\"abc\", \"123\"], \"^(?:\\\\d\\\\d\\\\d|abc)$\"),\n            case(vec![\"١\", \"٣\", \"٥\"], \"^\\\\d$\"), // Arabic digits: ١ = 1, ٣ = 3, ٥ = 5\n            case(vec![\"١٣٥\"], \"^\\\\d\\\\d\\\\d$\"),\n            case(vec![\"a٣3\", \"b5٥\"], \"^[ab]\\\\d\\\\d$\"),\n            case(vec![\"I ♥ 123\"], \"^I ♥ \\\\d\\\\d\\\\d$\"),\n            case(vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"], \"^I   ♥♥♥ \\\\d\\\\d and \\\\d and y̆y̆ and 💩💩\\\\.$\")\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_digits()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^I   \\\\u{2665}\\\\u{2665}\\\\u{2665} \\\\d\\\\d and \\\\d and y\\\\u{306}y\\\\u{306} and \\\\u{1f4a9}\\\\u{1f4a9}\\\\.$\"\n            )\n        )]\n        fn succeeds_with_escape_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_digits()\n                .with_escaping_of_non_ascii_chars(false)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^I   \\\\u{2665}\\\\u{2665}\\\\u{2665} \\\\d\\\\d and \\\\d and y\\\\u{306}y\\\\u{306} and \\\\u{d83d}\\\\u{dca9}\\\\u{d83d}\\\\u{dca9}\\\\.$\"\n            )\n        )]\n        fn succeeds_with_escape_and_surrogate_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_digits()\n                .with_escaping_of_non_ascii_chars(true)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^I {3}♥{3} \\\\d(?:\\\\d and ){2}(?:y̆){2} and 💩{2}\\\\.$\"\n            )\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_digits()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^I {3}\\\\u{2665}{3} \\\\d(?:\\\\d and ){2}(?:y\\\\u{306}){2} and \\\\u{1f4a9}{2}\\\\.$\"\n            )\n        )]\n        fn succeeds_with_escape_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_digits()\n                .with_escaping_of_non_ascii_chars(false)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^I {3}\\\\u{2665}{3} \\\\d(?:\\\\d and ){2}(?:y\\\\u{306}){2} and (?:\\\\u{d83d}\\\\u{dca9}){2}\\\\.$\"\n            )\n        )]\n        fn succeeds_with_escape_and_surrogate_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_digits()\n                .with_escaping_of_non_ascii_chars(true)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\"1\"], \"^\\\\d$\"),\n            case(vec![\"12\"], \"^\\\\d\\\\d$\"),\n            case(vec![\"123\"], \"^\\\\d{3}$\"),\n            case(vec![\"1\", \"12\", \"123\"], \"^(?:\\\\d\\\\d|\\\\d|\\\\d{3})$\"),\n            case(vec![\"12\", \"123\", \"1234\"], \"^(?:\\\\d\\\\d|\\\\d{3,4})$\"),\n            case(vec![\"123\", \"1234\", \"12345\"], \"^\\\\d{3,5}$\"),\n            case(vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"], \"^I {3}♥{3} \\\\d\\\\d and \\\\d and y̆y̆ and 💩💩\\\\.$\")\n        )]\n        fn succeeds_with_increased_minimum_repetitions(\n            test_cases: Vec<&str>,\n            expected_output: &str,\n        ) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_digits()\n                .with_minimum_repetitions(2)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n    }\n}\n\nmod space_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\"\"], \"^$\"),\n            case(vec![\" \"], \"^\\\\s$\"),\n            case(vec![\"   \"], \"^\\\\s\\\\s\\\\s$\"),\n            case(vec![\"\\n\"], \"^\\\\s$\"),\n            case(vec![\"\\u{c}\"], \"^\\\\s$\"), // form feed \\f\n            case(vec![\"\\u{b}\"], \"^\\\\s$\"), // vertical tab \\v\n            case(vec![\"\\n\", \"\\r\"], \"^\\\\s$\"),\n            case(vec![\"\\n\\t\", \"\\r\"], \"^\\\\s(?:\\\\s)?$\"),\n            case(vec![\"a\"], \"^a$\"),\n            case(vec![\"1\"], \"^1$\"),\n            case(vec![\"I ♥ 123\"], \"^I\\\\s♥\\\\s123$\"),\n            case(vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"], \"^I\\\\s\\\\s\\\\s♥♥♥\\\\s36\\\\sand\\\\s٣\\\\sand\\\\sy̆y̆\\\\sand\\\\s💩💩\\\\.$\")\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_whitespace()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^I\\\\s\\\\s\\\\s\\\\u{2665}\\\\u{2665}\\\\u{2665}\\\\s36\\\\sand\\\\s\\\\u{663}\\\\sand\\\\sy\\\\u{306}y\\\\u{306}\\\\sand\\\\s\\\\u{1f4a9}\\\\u{1f4a9}\\\\.$\"\n            )\n        )]\n        fn succeeds_with_escape_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_whitespace()\n                .with_escaping_of_non_ascii_chars(false)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^I\\\\s\\\\s\\\\s\\\\u{2665}\\\\u{2665}\\\\u{2665}\\\\s36\\\\sand\\\\s\\\\u{663}\\\\sand\\\\sy\\\\u{306}y\\\\u{306}\\\\sand\\\\s\\\\u{d83d}\\\\u{dca9}\\\\u{d83d}\\\\u{dca9}\\\\.$\"\n            )\n        )]\n        fn succeeds_with_escape_and_surrogate_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_whitespace()\n                .with_escaping_of_non_ascii_chars(true)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^I\\\\s{3}♥{3}\\\\s36\\\\sand\\\\s٣\\\\sand\\\\s(?:y̆){2}\\\\sand\\\\s💩{2}\\\\.$\"\n            )\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_whitespace()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^I\\\\s{3}\\\\u{2665}{3}\\\\s36\\\\sand\\\\s\\\\u{663}\\\\sand\\\\s(?:y\\\\u{306}){2}\\\\sand\\\\s\\\\u{1f4a9}{2}\\\\.$\"\n            )\n        )]\n        fn succeeds_with_escape_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_whitespace()\n                .with_escaping_of_non_ascii_chars(false)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^I\\\\s{3}\\\\u{2665}{3}\\\\s36\\\\sand\\\\s\\\\u{663}\\\\sand\\\\s(?:y\\\\u{306}){2}\\\\sand\\\\s(?:\\\\u{d83d}\\\\u{dca9}){2}\\\\.$\"\n            )\n        )]\n        fn succeeds_with_escape_and_surrogate_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_whitespace()\n                .with_escaping_of_non_ascii_chars(true)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\" \"], \"^\\\\s$\"),\n            case(vec![\"  \"], \"^\\\\s\\\\s$\"),\n            case(vec![\"   \"], \"^\\\\s{3}$\"),\n            case(vec![\" \", \"  \", \"   \"], \"^(?:\\\\s\\\\s|\\\\s|\\\\s{3})$\"),\n            case(vec![\"  \", \"   \", \"    \"], \"^(?:\\\\s\\\\s|\\\\s{3,4})$\"),\n            case(vec![\"   \", \"    \", \"     \"], \"^\\\\s{3,5}$\"),\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^I\\\\s{3}♥{3}\\\\s36\\\\sand\\\\s٣\\\\sand\\\\sy\\u{306}y\\u{306}\\\\sand\\\\s💩💩\\\\.$\"\n            )\n        )]\n        fn succeeds_with_increased_minimum_repetitions(\n            test_cases: Vec<&str>,\n            expected_output: &str,\n        ) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_whitespace()\n                .with_minimum_repetitions(2)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n    }\n}\n\nmod word_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\"\"], \"^$\"),\n            case(vec![\" \"], \"^ $\"),\n            case(vec![\"a\"], \"^\\\\w$\"),\n            case(vec![\"1\"], \"^\\\\w$\"),\n            case(vec![\"-1\"], \"^\\\\-\\\\w$\"),\n            case(vec![\"1\", \"2\"], \"^\\\\w$\"),\n            case(vec![\"ä\", \"ß\"], \"^\\\\w$\"),\n            case(vec![\"abc\", \"1234\"], \"^\\\\w\\\\w\\\\w(?:\\\\w)?$\"),\n            case(vec![\"١\", \"٣\", \"٥\"], \"^\\\\w$\"), // Arabic digits: ١ = 1, ٣ = 3, ٥ = 5\n            case(vec![\"١٣٥\"], \"^\\\\w\\\\w\\\\w$\"),\n            case(vec![\"a٣3\", \"b5٥\"], \"^\\\\w\\\\w\\\\w$\"),\n            case(vec![\"I ♥ 123\"], \"^\\\\w ♥ \\\\w\\\\w\\\\w$\"),\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\w   ♥♥♥ \\\\w\\\\w \\\\w\\\\w\\\\w \\\\w \\\\w\\\\w\\\\w \\\\w\\\\w\\\\w\\\\w \\\\w\\\\w\\\\w 💩💩\\\\.$\"\n            )\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_words()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\w   \\\\u{2665}\\\\u{2665}\\\\u{2665} \\\\w\\\\w \\\\w\\\\w\\\\w \\\\w \\\\w\\\\w\\\\w \\\\w\\\\w\\\\w\\\\w \\\\w\\\\w\\\\w \\\\u{1f4a9}\\\\u{1f4a9}\\\\.$\"\n            )\n        )]\n        fn succeeds_with_escape_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_words()\n                .with_escaping_of_non_ascii_chars(false)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\w   \\\\u{2665}\\\\u{2665}\\\\u{2665} \\\\w\\\\w \\\\w\\\\w\\\\w \\\\w \\\\w\\\\w\\\\w \\\\w\\\\w\\\\w\\\\w \\\\w\\\\w\\\\w \\\\u{d83d}\\\\u{dca9}\\\\u{d83d}\\\\u{dca9}\\\\.$\"\n            )\n        )]\n        fn succeeds_with_escape_and_surrogate_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_words()\n                .with_escaping_of_non_ascii_chars(true)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\w {3}♥{3} \\\\w{2}(?: \\\\w{3} \\\\w){2}(?:\\\\w{3} ){2}💩{2}\\\\.$\"\n            )\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_words()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\w {3}\\\\u{2665}{3} \\\\w{2}(?: \\\\w{3} \\\\w){2}(?:\\\\w{3} ){2}\\\\u{1f4a9}{2}\\\\.$\"\n            )\n        )]\n        fn succeeds_with_escape_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_words()\n                .with_escaping_of_non_ascii_chars(false)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\w {3}\\\\u{2665}{3} \\\\w{2}(?: \\\\w{3} \\\\w){2}(?:\\\\w{3} ){2}(?:\\\\u{d83d}\\\\u{dca9}){2}\\\\.$\"\n            )\n        )]\n        fn succeeds_with_escape_and_surrogate_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_words()\n                .with_escaping_of_non_ascii_chars(true)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\"a\"], \"^\\\\w$\"),\n            case(vec![\"ab\"], \"^\\\\w\\\\w$\"),\n            case(vec![\"abc\"], \"^\\\\w{3}$\"),\n            case(vec![\"a\", \"ab\", \"abc\"], \"^(?:\\\\w\\\\w|\\\\w|\\\\w{3})$\"),\n            case(vec![\"ab\", \"abc\", \"abcd\"], \"^(?:\\\\w\\\\w|\\\\w{3,4})$\"),\n            case(vec![\"abc\", \"abcd\", \"abcde\"], \"^\\\\w{3,5}$\"),\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\w {3}♥{3} \\\\w\\\\w \\\\w{3} \\\\w \\\\w{3} \\\\w{4} \\\\w{3} 💩💩\\\\.$\"\n            )\n        )]\n        fn succeeds_with_increased_minimum_repetitions(\n            test_cases: Vec<&str>,\n            expected_output: &str,\n        ) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_words()\n                .with_minimum_repetitions(2)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n    }\n}\n\nmod digit_space_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^I\\\\s\\\\s\\\\s♥♥♥\\\\s\\\\d\\\\d\\\\sand\\\\s\\\\d\\\\sand\\\\sy̆y̆\\\\sand\\\\s💩💩\\\\.$\"\n            )\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_digits()\n                .with_conversion_of_whitespace()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^I\\\\s\\\\s\\\\s\\\\u{2665}\\\\u{2665}\\\\u{2665}\\\\s\\\\d\\\\d\\\\sand\\\\s\\\\d\\\\sand\\\\sy\\\\u{306}y\\\\u{306}\\\\sand\\\\s\\\\u{1f4a9}\\\\u{1f4a9}\\\\.$\"\n            )\n        )]\n        fn succeeds_with_escape_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_digits()\n                .with_conversion_of_whitespace()\n                .with_escaping_of_non_ascii_chars(false)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^I\\\\s\\\\s\\\\s\\\\u{2665}\\\\u{2665}\\\\u{2665}\\\\s\\\\d\\\\d\\\\sand\\\\s\\\\d\\\\sand\\\\sy\\\\u{306}y\\\\u{306}\\\\sand\\\\s\\\\u{d83d}\\\\u{dca9}\\\\u{d83d}\\\\u{dca9}\\\\.$\"\n            )\n        )]\n        fn succeeds_with_escape_and_surrogate_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_digits()\n                .with_conversion_of_whitespace()\n                .with_escaping_of_non_ascii_chars(true)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^I\\\\s{3}♥{3}\\\\s\\\\d(?:\\\\d\\\\sand\\\\s){2}(?:y̆){2}\\\\sand\\\\s💩{2}\\\\.$\"\n            )\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_digits()\n                .with_conversion_of_whitespace()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^I\\\\s{3}\\\\u{2665}{3}\\\\s\\\\d(?:\\\\d\\\\sand\\\\s){2}(?:y\\\\u{306}){2}\\\\sand\\\\s\\\\u{1f4a9}{2}\\\\.$\"\n            )\n        )]\n        fn succeeds_with_escape_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_digits()\n                .with_conversion_of_whitespace()\n                .with_escaping_of_non_ascii_chars(false)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^I\\\\s{3}\\\\u{2665}{3}\\\\s\\\\d(?:\\\\d\\\\sand\\\\s){2}(?:y\\\\u{306}){2}\\\\sand\\\\s(?:\\\\u{d83d}\\\\u{dca9}){2}\\\\.$\"\n            )\n        )]\n        fn succeeds_with_escape_and_surrogate_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_digits()\n                .with_conversion_of_whitespace()\n                .with_escaping_of_non_ascii_chars(true)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\"1\\n\"], \"^\\\\d\\\\s$\"),\n            case(vec![\"1\\n1\\n\"], \"^\\\\d\\\\s\\\\d\\\\s$\"),\n            case(vec![\"1\\n1\\n1\\n\"], \"^(?:\\\\d\\\\s){3}$\"),\n            case(vec![\"1\\n\", \"1\\n1\\n\", \"1\\n1\\n1\\n\"], \"^(?:\\\\d\\\\s\\\\d\\\\s|\\\\d\\\\s|(?:\\\\d\\\\s){3})$\"),\n            case(vec![\"1\\n1\\n\", \"1\\n1\\n1\\n\", \"1\\n1\\n1\\n1\\n\"], \"^(?:\\\\d\\\\s\\\\d\\\\s|(?:\\\\d\\\\s){3,4})$\"),\n            case(vec![\"1\\n1\\n1\\n\", \"1\\n1\\n1\\n1\\n\", \"1\\n1\\n1\\n1\\n1\\n\"], \"^(?:\\\\d\\\\s){3,5}$\"),\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^I\\\\s{3}♥{3}\\\\s\\\\d\\\\d\\\\sand\\\\s\\\\d\\\\sand\\\\sy̆y̆\\\\sand\\\\s💩💩\\\\.$\"\n            )\n        )]\n        fn succeeds_with_increased_minimum_repetitions(\n            test_cases: Vec<&str>,\n            expected_output: &str,\n        ) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_digits()\n                .with_conversion_of_whitespace()\n                .with_minimum_repetitions(2)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\"1\\n1\\n\"], \"^1\\\\n1\\\\n$\"),\n            case(vec![\"1\\n\\n1\\n\\n\"], \"^(?:1\\\\n\\\\n){2}$\")\n        )]\n        fn succeeds_with_increased_minimum_substring_length(\n            test_cases: Vec<&str>,\n            expected_output: &str,\n        ) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_minimum_substring_length(3)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\"1\\n1\\n\"], \"^1\\\\n1\\\\n$\"),\n            case(vec![\"1\\n1\\n1\\n\"], \"^1\\\\n1\\\\n1\\\\n$\"),\n            case(vec![\"1\\n\\n1\\n\\n\"], \"^1\\\\n\\\\n1\\\\n\\\\n$\"),\n            case(vec![\"1\\n\\n1\\n\\n1\\n\\n\"], \"^(?:1\\\\n\\\\n){3}$\")\n        )]\n        fn succeeds_with_increased_minimum_repetitions_and_substring_length(\n            test_cases: Vec<&str>,\n            expected_output: &str,\n        ) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_minimum_repetitions(2)\n                .with_minimum_substring_length(3)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n    }\n}\n\nmod digit_word_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\w   ♥♥♥ \\\\d\\\\d \\\\w\\\\w\\\\w \\\\d \\\\w\\\\w\\\\w \\\\w\\\\w\\\\w\\\\w \\\\w\\\\w\\\\w 💩💩\\\\.$\"\n            )\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_digits()\n                .with_conversion_of_words()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\w   \\\\u{2665}\\\\u{2665}\\\\u{2665} \\\\d\\\\d \\\\w\\\\w\\\\w \\\\d \\\\w\\\\w\\\\w \\\\w\\\\w\\\\w\\\\w \\\\w\\\\w\\\\w \\\\u{1f4a9}\\\\u{1f4a9}\\\\.$\"\n            )\n        )]\n        fn succeeds_with_escape_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_digits()\n                .with_conversion_of_words()\n                .with_escaping_of_non_ascii_chars(false)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\w   \\\\u{2665}\\\\u{2665}\\\\u{2665} \\\\d\\\\d \\\\w\\\\w\\\\w \\\\d \\\\w\\\\w\\\\w \\\\w\\\\w\\\\w\\\\w \\\\w\\\\w\\\\w \\\\u{d83d}\\\\u{dca9}\\\\u{d83d}\\\\u{dca9}\\\\.$\"\n            )\n        )]\n        fn succeeds_with_escape_and_surrogate_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_digits()\n                .with_conversion_of_words()\n                .with_escaping_of_non_ascii_chars(true)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\w {3}♥{3} \\\\d(?:\\\\d \\\\w{3} ){2}\\\\w(?:\\\\w{3} ){2}💩{2}\\\\.$\"\n            )\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_digits()\n                .with_conversion_of_words()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\w {3}\\\\u{2665}{3} \\\\d(?:\\\\d \\\\w{3} ){2}\\\\w(?:\\\\w{3} ){2}\\\\u{1f4a9}{2}\\\\.$\"\n            )\n        )]\n        fn succeeds_with_escape_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_digits()\n                .with_conversion_of_words()\n                .with_escaping_of_non_ascii_chars(false)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\w {3}\\\\u{2665}{3} \\\\d(?:\\\\d \\\\w{3} ){2}\\\\w(?:\\\\w{3} ){2}(?:\\\\u{d83d}\\\\u{dca9}){2}\\\\.$\"\n            )\n        )]\n        fn succeeds_with_escape_and_surrogate_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_digits()\n                .with_conversion_of_words()\n                .with_escaping_of_non_ascii_chars(true)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n        }\n    }\n}\n\nmod space_word_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\w\\\\s\\\\s\\\\s♥♥♥\\\\s\\\\w\\\\w\\\\s\\\\w\\\\w\\\\w\\\\s\\\\w\\\\s\\\\w\\\\w\\\\w\\\\s\\\\w\\\\w\\\\w\\\\w\\\\s\\\\w\\\\w\\\\w\\\\s💩💩\\\\.$\"\n            )\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_whitespace()\n                .with_conversion_of_words()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\w\\\\s\\\\s\\\\s\\\\u{2665}\\\\u{2665}\\\\u{2665}\\\\s\\\\w\\\\w\\\\s\\\\w\\\\w\\\\w\\\\s\\\\w\\\\s\\\\w\\\\w\\\\w\\\\s\\\\w\\\\w\\\\w\\\\w\\\\s\\\\w\\\\w\\\\w\\\\s\\\\u{1f4a9}\\\\u{1f4a9}\\\\.$\"\n            )\n        )]\n        fn succeeds_with_escape_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_whitespace()\n                .with_conversion_of_words()\n                .with_escaping_of_non_ascii_chars(false)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\w\\\\s\\\\s\\\\s\\\\u{2665}\\\\u{2665}\\\\u{2665}\\\\s\\\\w\\\\w\\\\s\\\\w\\\\w\\\\w\\\\s\\\\w\\\\s\\\\w\\\\w\\\\w\\\\s\\\\w\\\\w\\\\w\\\\w\\\\s\\\\w\\\\w\\\\w\\\\s\\\\u{d83d}\\\\u{dca9}\\\\u{d83d}\\\\u{dca9}\\\\.$\"\n            )\n        )]\n        fn succeeds_with_escape_and_surrogate_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_whitespace()\n                .with_conversion_of_words()\n                .with_escaping_of_non_ascii_chars(true)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\w\\\\s{3}♥{3}\\\\s\\\\w{2}(?:\\\\s\\\\w{3}\\\\s\\\\w){2}(?:\\\\w{3}\\\\s){2}💩{2}\\\\.$\"\n            )\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_whitespace()\n                .with_conversion_of_words()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\w\\\\s{3}\\\\u{2665}{3}\\\\s\\\\w{2}(?:\\\\s\\\\w{3}\\\\s\\\\w){2}(?:\\\\w{3}\\\\s){2}\\\\u{1f4a9}{2}\\\\.$\"\n            )\n        )]\n        fn succeeds_with_escape_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_whitespace()\n                .with_conversion_of_words()\n                .with_escaping_of_non_ascii_chars(false)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\w\\\\s{3}\\\\u{2665}{3}\\\\s\\\\w{2}(?:\\\\s\\\\w{3}\\\\s\\\\w){2}(?:\\\\w{3}\\\\s){2}(?:\\\\u{d83d}\\\\u{dca9}){2}\\\\.$\"\n            )\n        )]\n        fn succeeds_with_escape_and_surrogate_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_whitespace()\n                .with_conversion_of_words()\n                .with_escaping_of_non_ascii_chars(true)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n        }\n    }\n}\n\nmod digit_space_word_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\w\\\\s\\\\s\\\\s♥♥♥\\\\s\\\\d\\\\d\\\\s\\\\w\\\\w\\\\w\\\\s\\\\d\\\\s\\\\w\\\\w\\\\w\\\\s\\\\w\\\\w\\\\w\\\\w\\\\s\\\\w\\\\w\\\\w\\\\s💩💩\\\\.$\"\n            )\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_digits()\n                .with_conversion_of_whitespace()\n                .with_conversion_of_words()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\w\\\\s\\\\s\\\\s\\\\u{2665}\\\\u{2665}\\\\u{2665}\\\\s\\\\d\\\\d\\\\s\\\\w\\\\w\\\\w\\\\s\\\\d\\\\s\\\\w\\\\w\\\\w\\\\s\\\\w\\\\w\\\\w\\\\w\\\\s\\\\w\\\\w\\\\w\\\\s\\\\u{1f4a9}\\\\u{1f4a9}\\\\.$\"\n            )\n        )]\n        fn succeeds_with_escape_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_digits()\n                .with_conversion_of_whitespace()\n                .with_conversion_of_words()\n                .with_escaping_of_non_ascii_chars(false)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\w\\\\s\\\\s\\\\s\\\\u{2665}\\\\u{2665}\\\\u{2665}\\\\s\\\\d\\\\d\\\\s\\\\w\\\\w\\\\w\\\\s\\\\d\\\\s\\\\w\\\\w\\\\w\\\\s\\\\w\\\\w\\\\w\\\\w\\\\s\\\\w\\\\w\\\\w\\\\s\\\\u{d83d}\\\\u{dca9}\\\\u{d83d}\\\\u{dca9}\\\\.$\"\n            )\n        )]\n        fn succeeds_with_escape_and_surrogate_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_digits()\n                .with_conversion_of_whitespace()\n                .with_conversion_of_words()\n                .with_escaping_of_non_ascii_chars(true)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\w\\\\s{3}♥{3}\\\\s\\\\d(?:\\\\d\\\\s\\\\w{3}\\\\s){2}\\\\w(?:\\\\w{3}\\\\s){2}💩{2}\\\\.$\"\n            )\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_digits()\n                .with_conversion_of_whitespace()\n                .with_conversion_of_words()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\w\\\\s{3}\\\\u{2665}{3}\\\\s\\\\d(?:\\\\d\\\\s\\\\w{3}\\\\s){2}\\\\w(?:\\\\w{3}\\\\s){2}\\\\u{1f4a9}{2}\\\\.$\"\n            )\n        )]\n        fn succeeds_with_escape_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_digits()\n                .with_conversion_of_whitespace()\n                .with_conversion_of_words()\n                .with_escaping_of_non_ascii_chars(false)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\w\\\\s{3}\\\\u{2665}{3}\\\\s\\\\d(?:\\\\d\\\\s\\\\w{3}\\\\s){2}\\\\w(?:\\\\w{3}\\\\s){2}(?:\\\\u{d83d}\\\\u{dca9}){2}\\\\.$\"\n            )\n        )]\n        fn succeeds_with_escape_and_surrogate_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_digits()\n                .with_conversion_of_whitespace()\n                .with_conversion_of_words()\n                .with_escaping_of_non_ascii_chars(true)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n        }\n    }\n}\n\nmod non_digit_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D36\\\\D\\\\D\\\\D\\\\D\\\\D٣\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D$\"\n            )\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_non_digits()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D36\\\\D\\\\D\\\\D\\\\D\\\\D\\\\u{663}\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D$\"\n            )\n        )]\n        fn succeeds_with_escape_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_non_digits()\n                .with_escaping_of_non_ascii_chars(false)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"], \"^\\\\D{8}36\\\\D{5}٣\\\\D{17}$\")\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_non_digits()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"], \"^\\\\D{8}36\\\\D{5}\\\\u{663}\\\\D{17}$\")\n        )]\n        fn succeeds_with_escape_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_non_digits()\n                .with_escaping_of_non_ascii_chars(false)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n    }\n}\n\nmod non_space_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\S   \\\\S\\\\S\\\\S \\\\S\\\\S \\\\S\\\\S\\\\S \\\\S \\\\S\\\\S\\\\S \\\\S\\\\S\\\\S\\\\S \\\\S\\\\S\\\\S \\\\S\\\\S\\\\S$\"\n            )\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_non_whitespace()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\S {3}\\\\S(?:\\\\S{2} ){2}\\\\S{3} (?:\\\\S(?: \\\\S{3}){2}){2}$\"\n            )\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_non_whitespace()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n    }\n}\n\nmod non_word_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^I\\\\W\\\\W\\\\W\\\\W\\\\W\\\\W\\\\W36\\\\Wand\\\\W٣\\\\Wand\\\\Wy̆y̆\\\\Wand\\\\W\\\\W\\\\W\\\\W$\"\n            )\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_non_words()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^I\\\\W\\\\W\\\\W\\\\W\\\\W\\\\W\\\\W36\\\\Wand\\\\W\\\\u{663}\\\\Wand\\\\Wy\\\\u{306}y\\\\u{306}\\\\Wand\\\\W\\\\W\\\\W\\\\W$\"\n            )\n        )]\n        fn succeeds_with_escape_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_non_words()\n                .with_escaping_of_non_ascii_chars(false)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^I\\\\W{7}36\\\\Wand\\\\W٣\\\\Wand\\\\W(?:y̆){2}\\\\Wand\\\\W{4}$\"\n            )\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_non_words()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^I\\\\W{7}36\\\\Wand\\\\W\\\\u{663}\\\\Wand\\\\W(?:y\\\\u{306}){2}\\\\Wand\\\\W{4}$\"\n            )\n        )]\n        fn succeeds_with_escape_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_non_words()\n                .with_escaping_of_non_ascii_chars(false)\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n    }\n}\n\nmod non_digit_non_space_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\S\\\\S\\\\D\\\\D\\\\D\\\\D\\\\D\\\\S\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D$\"\n            )\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_non_digits()\n                .with_conversion_of_non_whitespace()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"], \"^\\\\D{8}\\\\S{2}\\\\D{5}\\\\S\\\\D{17}$\")\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_non_digits()\n                .with_conversion_of_non_whitespace()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n    }\n}\n\nmod non_digit_non_word_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D36\\\\D\\\\D\\\\D\\\\D\\\\D٣\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D$\"\n            )\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_non_digits()\n                .with_conversion_of_non_words()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"], \"^\\\\D{8}36\\\\D{5}٣\\\\D{17}$\")\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_non_digits()\n                .with_conversion_of_non_words()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n    }\n}\n\nmod non_space_non_word_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\S\\\\W\\\\W\\\\W\\\\W\\\\W\\\\W\\\\W\\\\S\\\\S\\\\W\\\\S\\\\S\\\\S\\\\W\\\\S\\\\W\\\\S\\\\S\\\\S\\\\W\\\\S\\\\S\\\\S\\\\S\\\\W\\\\S\\\\S\\\\S\\\\W\\\\W\\\\W\\\\W$\"\n            )\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_non_whitespace()\n                .with_conversion_of_non_words()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\S\\\\W{7}\\\\S(?:\\\\S\\\\W\\\\S{3}\\\\W){2}\\\\S{4}\\\\W\\\\S{3}\\\\W{4}$\"\n            )\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_non_whitespace()\n                .with_conversion_of_non_words()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n    }\n}\n\nmod non_digit_non_space_non_word_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\S\\\\S\\\\D\\\\D\\\\D\\\\D\\\\D\\\\S\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D$\"\n            )\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_non_digits()\n                .with_conversion_of_non_whitespace()\n                .with_conversion_of_non_words()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"], \"^\\\\D{8}\\\\S{2}\\\\D{5}\\\\S\\\\D{17}$\")\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_non_digits()\n                .with_conversion_of_non_whitespace()\n                .with_conversion_of_non_words()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n    }\n}\n\nmod digit_non_digit_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\d\\\\d\\\\D\\\\D\\\\D\\\\D\\\\D\\\\d\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D\\\\D$\"\n            )\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_digits()\n                .with_conversion_of_non_digits()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"], \"^\\\\D{8}\\\\d{2}\\\\D{5}\\\\d\\\\D{17}$\")\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_digits()\n                .with_conversion_of_non_digits()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n    }\n}\n\nmod space_non_space_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\S\\\\s\\\\s\\\\s\\\\S\\\\S\\\\S\\\\s\\\\S\\\\S\\\\s\\\\S\\\\S\\\\S\\\\s\\\\S\\\\s\\\\S\\\\S\\\\S\\\\s\\\\S\\\\S\\\\S\\\\S\\\\s\\\\S\\\\S\\\\S\\\\s\\\\S\\\\S\\\\S$\"\n            )\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_whitespace()\n                .with_conversion_of_non_whitespace()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\S\\\\s{3}\\\\S(?:\\\\S{2}\\\\s){2}\\\\S{3}\\\\s(?:\\\\S(?:\\\\s\\\\S{3}){2}){2}$\"\n            )\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_whitespace()\n                .with_conversion_of_non_whitespace()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n    }\n}\n\nmod word_non_word_conversion {\n    use super::*;\n\n    mod no_repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\w\\\\W\\\\W\\\\W\\\\W\\\\W\\\\W\\\\W\\\\w\\\\w\\\\W\\\\w\\\\w\\\\w\\\\W\\\\w\\\\W\\\\w\\\\w\\\\w\\\\W\\\\w\\\\w\\\\w\\\\w\\\\W\\\\w\\\\w\\\\w\\\\W\\\\W\\\\W\\\\W$\"\n            )\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_words()\n                .with_conversion_of_non_words()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n    }\n\n    mod repetition {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(\n                vec![\"I   ♥♥♥ 36 and ٣ and y̆y̆ and 💩💩.\"],\n                \"^\\\\w\\\\W{7}\\\\w(?:\\\\w\\\\W\\\\w{3}\\\\W){2}\\\\w{4}\\\\W\\\\w{3}\\\\W{4}$\"\n            )\n        )]\n        fn succeeds(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_conversion_of_repetitions()\n                .with_conversion_of_words()\n                .with_conversion_of_non_words()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n    }\n}\n\nmod anchor_conversion {\n    use super::*;\n\n    mod no_verbose {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\"aaacaac\", \"aac\"], \"aa(?:acaa)?c$\"),\n            case(vec![\"My ♥♥♥ and 💩💩 is yours.\"], \"My ♥♥♥ and 💩💩 is yours\\\\.$\"),\n        )]\n        fn succeeds_without_start_anchor_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .without_start_anchor()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\"aaacaac\", \"aac\"], \"^aa(?:acaa)?c\"),\n            case(vec![\"My ♥♥♥ and 💩💩 is yours.\"], \"^My ♥♥♥ and 💩💩 is yours\\\\.\"),\n        )]\n        fn succeeds_without_end_anchor_option(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .without_end_anchor()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\"bab\", \"b\", \"cb\", \"bba\"], \"(?:b(?:ba|ab)?|cb)\"),\n            case(vec![\"a\", \"aba\", \"baaa\", \"aaab\"], \"(?:baaa|a(?:aab|ba)?)\"),\n            case(vec![\"a\", \"abab\", \"bbb\", \"aaac\"], \"(?:a(?:bab|aac)?|bbb)\"),\n            case(vec![\"aaacaac\", \"aac\"], \"aa(?:acaa)?c\"),\n            case(vec![\"My ♥♥♥ and 💩💩 is yours.\"], \"My ♥♥♥ and 💩💩 is yours\\\\.\"),\n            case(\n                // https://github.com/pemistahl/grex/issues/31\n                vec![\"agbhd\", \"eibcd\", \"egbcd\", \"fbjbf\", \"agbh\", \"eibc\", \"egbc\", \"ebc\", \"fbc\", \"cd\", \"f\", \"c\", \"abcd\", \"ebcd\", \"fbcd\"],\n                \"(?:a(?:gbhd?|bcd)|e(?:ibcd?|gbcd?|bcd?)|f(?:b(?:jbf|cd?))?|cd?)\"\n            )\n        )]\n        fn succeeds_without_anchors(test_cases: Vec<&str>, expected_output: &str) {\n            let regexp = RegExpBuilder::from(&test_cases).without_anchors().build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n    }\n\n    mod verbose {\n        use super::*;\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\"My ♥♥♥ and 💩💩 is yours.\"], indoc!(\n                r#\"\n                (?x)\n                  My\\ ♥♥♥\\ and\\ 💩💩\\ is\\ yours\\.\n                $\"#\n            ))\n        )]\n        fn succeeds_with_verbose_mode_and_without_start_anchor_option(\n            test_cases: Vec<&str>,\n            expected_output: &str,\n        ) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_verbose_mode()\n                .without_start_anchor()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\"My ♥♥♥ and 💩💩 is yours.\"], indoc!(\n                r#\"\n                (?x)\n                ^\n                  My\\ ♥♥♥\\ and\\ 💩💩\\ is\\ yours\\.\"#\n            ))\n        )]\n        fn succeeds_with_verbose_mode_and_without_end_anchor_option(\n            test_cases: Vec<&str>,\n            expected_output: &str,\n        ) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_verbose_mode()\n                .without_end_anchor()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n\n        #[rstest(test_cases, expected_output,\n            case(vec![\"aaacaac\", \"aac\"], indoc!(\n                r#\"\n                (?x)\n                  aa\n                  (?:\n                    acaa\n                  )?\n                  c\"#\n            )),\n            case(vec![\"My ♥♥♥ and 💩💩 is yours.\"], indoc!(\n                r#\"\n                (?x)\n                  My\\ ♥♥♥\\ and\\ 💩💩\\ is\\ yours\\.\"#\n            ))\n        )]\n        fn succeeds_with_verbose_mode_and_without_anchors_option(\n            test_cases: Vec<&str>,\n            expected_output: &str,\n        ) {\n            let regexp = RegExpBuilder::from(&test_cases)\n                .with_verbose_mode()\n                .without_anchors()\n                .build();\n            assert_that_regexp_is_correct(regexp, expected_output, &test_cases);\n            assert_that_regexp_matches_test_cases(expected_output, test_cases);\n        }\n    }\n}\n\nfn assert_that_regexp_is_correct(regexp: String, expected_output: &str, test_cases: &[&str]) {\n    assert_eq!(\n        regexp, expected_output,\n        \"\\n\\ninput: {:?}\\nexpected: {}\\nactual: {}\\n\\n\",\n        test_cases, expected_output, regexp\n    );\n}\n\nfn assert_that_regexp_matches_test_cases(expected_output: &str, test_cases: Vec<&str>) {\n    let regexp = Regex::new(expected_output).unwrap();\n    for test_case in test_cases {\n        let substrings = regexp\n            .find_iter(test_case)\n            .map(|m| m.as_str())\n            .collect::<Vec<_>>();\n\n        assert_eq!(\n            substrings.len(),\n            1,\n            \"expression '{}' does not match test case '{}' entirely but {} of its substrings: {:?}\",\n            expected_output,\n            test_case,\n            substrings.len(),\n            substrings\n        );\n    }\n}\n"
  },
  {
    "path": "tests/property_tests.rs",
    "content": "/*\n * Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#![cfg(not(target_family = \"wasm\"))]\n\nuse grex::RegExpBuilder;\nuse proptest::prelude::*;\nuse regex::{Error, Regex, RegexBuilder};\n\nproptest! {\n    #![proptest_config(ProptestConfig::with_cases(500))]\n\n    #[test]\n    fn valid_regexes_with_default_settings(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec).build();\n        prop_assert!(compile_regexp(&regexp).is_ok());\n    }\n\n    #[test]\n    fn valid_regexes_with_case_insensitive_matching(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_case_insensitive_matching()\n            .build();\n        prop_assert!(compile_regexp(&regexp).is_ok());\n    }\n\n    #[test]\n    fn valid_regexes_with_case_insensitive_matching_and_verbose_mode(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_case_insensitive_matching()\n            .with_verbose_mode()\n            .build();\n        prop_assert!(compile_regexp(&regexp).is_ok());\n    }\n\n    #[test]\n    fn valid_regexes_with_escape_sequences(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_escaping_of_non_ascii_chars(false)\n            .build();\n        prop_assert!(compile_regexp(&regexp).is_ok());\n    }\n\n    #[test]\n    fn valid_regexes_with_verbose_mode(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_verbose_mode()\n            .build();\n        prop_assert!(compile_regexp(&regexp).is_ok());\n    }\n\n    #[test]\n    fn valid_regexes_with_escape_sequences_and_verbose_mode(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_escaping_of_non_ascii_chars(false)\n            .with_verbose_mode()\n            .build();\n        prop_assert!(compile_regexp(&regexp).is_ok());\n    }\n\n    #[test]\n    fn valid_regexes_with_conversion_of_digits(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_conversion_of_digits()\n            .build();\n        prop_assert!(compile_regexp(&regexp).is_ok());\n    }\n\n    #[test]\n    fn valid_regexes_with_conversion_of_digits_and_verbose_mode(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_conversion_of_digits()\n            .with_verbose_mode()\n            .build();\n        prop_assert!(compile_regexp(&regexp).is_ok());\n    }\n\n    #[test]\n    fn valid_regexes_with_conversion_of_non_digits(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_conversion_of_non_digits()\n            .build();\n        prop_assert!(compile_regexp(&regexp).is_ok());\n    }\n\n    #[test]\n    fn valid_regexes_with_conversion_of_non_digits_and_verbose_mode(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_conversion_of_non_digits()\n            .with_verbose_mode()\n            .build();\n        prop_assert!(compile_regexp(&regexp).is_ok());\n    }\n\n    #[test]\n    fn valid_regexes_with_conversion_of_whitespace(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_conversion_of_whitespace()\n            .build();\n        prop_assert!(compile_regexp(&regexp).is_ok());\n    }\n\n    #[test]\n    fn valid_regexes_with_conversion_of_whitespace_and_verbose_mode(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_conversion_of_whitespace()\n            .with_verbose_mode()\n            .build();\n        prop_assert!(compile_regexp(&regexp).is_ok());\n    }\n\n    #[test]\n    fn valid_regexes_with_conversion_of_non_whitespace(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_conversion_of_non_whitespace()\n            .build();\n        prop_assert!(compile_regexp(&regexp).is_ok());\n    }\n\n    #[test]\n    fn valid_regexes_with_conversion_of_non_whitespace_and_verbose_mode(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_conversion_of_non_whitespace()\n            .with_verbose_mode()\n            .build();\n        prop_assert!(compile_regexp(&regexp).is_ok());\n    }\n\n    #[test]\n    fn valid_regexes_with_conversion_of_words(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_conversion_of_words()\n            .build();\n        prop_assert!(compile_regexp(&regexp).is_ok());\n    }\n\n    #[test]\n    fn valid_regexes_with_conversion_of_words_and_verbose_mode(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_conversion_of_words()\n            .with_verbose_mode()\n            .build();\n        prop_assert!(compile_regexp(&regexp).is_ok());\n    }\n\n    #[test]\n    fn valid_regexes_with_conversion_of_non_words(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_conversion_of_non_words()\n            .build();\n        prop_assert!(compile_regexp(&regexp).is_ok());\n    }\n\n    #[test]\n    fn valid_regexes_with_conversion_of_non_words_and_verbose_mode(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_conversion_of_non_words()\n            .with_verbose_mode()\n            .build();\n        prop_assert!(compile_regexp(&regexp).is_ok());\n    }\n\n    #[test]\n    fn valid_regexes_with_conversion_of_repetitions(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5),\n        minimum_repetitions in 1..100u32,\n        minimum_substring_length in 1..100u32\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_conversion_of_repetitions()\n            .with_minimum_repetitions(minimum_repetitions)\n            .with_minimum_substring_length(minimum_substring_length)\n            .build();\n        prop_assert!(compile_regexp(&regexp).is_ok());\n    }\n\n    #[test]\n    fn valid_regexes_with_conversion_of_repetitions_and_verbose_mode(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5),\n        minimum_repetitions in 1..100u32,\n        minimum_substring_length in 1..100u32\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_conversion_of_repetitions()\n            .with_minimum_repetitions(minimum_repetitions)\n            .with_minimum_substring_length(minimum_substring_length)\n            .with_verbose_mode()\n            .build();\n        prop_assert!(compile_regexp(&regexp).is_ok());\n    }\n\n    #[test]\n    fn matching_regexes_with_default_settings(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec).build();\n        if let Ok(compiled_regexp) = compile_regexp(&regexp) {\n            prop_assert!(test_cases.iter().all(|test_case| compiled_regexp.is_match(test_case)));\n        }\n    }\n\n    #[test]\n    fn matching_regexes_with_case_insensitive_matching(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_case_insensitive_matching()\n            .build();\n        if let Ok(compiled_regexp) = compile_regexp(&regexp) {\n            prop_assert!(test_cases.iter().all(|test_case| compiled_regexp.is_match(test_case)));\n        }\n    }\n\n    #[test]\n    fn matching_regexes_with_case_insensitive_matching_and_verbose_mode(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_case_insensitive_matching()\n            .with_verbose_mode()\n            .build();\n        if let Ok(compiled_regexp) = compile_regexp(&regexp) {\n            prop_assert!(test_cases.iter().all(|test_case| compiled_regexp.is_match(test_case)));\n        }\n    }\n\n    #[test]\n    fn matching_regexes_with_escape_sequences(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_escaping_of_non_ascii_chars(false)\n            .build();\n        if let Ok(compiled_regexp) = compile_regexp(&regexp) {\n            prop_assert!(test_cases.iter().all(|test_case| compiled_regexp.is_match(test_case)));\n        }\n    }\n\n    #[test]\n    fn matching_regexes_with_verbose_mode(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_verbose_mode()\n            .build();\n        if let Ok(compiled_regexp) = compile_regexp(&regexp) {\n            prop_assert!(test_cases.iter().all(|test_case| compiled_regexp.is_match(test_case)));\n        }\n    }\n\n    #[test]\n    fn matching_regexes_with_escape_sequences_and_verbose_mode(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_escaping_of_non_ascii_chars(false)\n            .with_verbose_mode()\n            .build();\n        if let Ok(compiled_regexp) = compile_regexp(&regexp) {\n            prop_assert!(test_cases.iter().all(|test_case| compiled_regexp.is_match(test_case)));\n        }\n    }\n\n    #[test]\n    fn matching_regexes_with_conversion_of_digits(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_conversion_of_digits()\n            .build();\n        if let Ok(compiled_regexp) = compile_regexp(&regexp) {\n            prop_assert!(test_cases.iter().all(|test_case| compiled_regexp.is_match(test_case)));\n        }\n    }\n\n    #[test]\n    fn matching_regexes_with_conversion_of_digits_and_verbose_mode(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_conversion_of_digits()\n            .with_verbose_mode()\n            .build();\n        if let Ok(compiled_regexp) = compile_regexp(&regexp) {\n            prop_assert!(test_cases.iter().all(|test_case| compiled_regexp.is_match(test_case)));\n        }\n    }\n\n    #[test]\n    fn matching_regexes_with_conversion_of_non_digits(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_conversion_of_digits()\n            .build();\n        if let Ok(compiled_regexp) = compile_regexp(&regexp) {\n            prop_assert!(test_cases.iter().all(|test_case| compiled_regexp.is_match(test_case)));\n        }\n    }\n\n    #[test]\n    fn matching_regexes_with_conversion_of_non_digits_and_verbose_mode(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_conversion_of_digits()\n            .with_verbose_mode()\n            .build();\n        if let Ok(compiled_regexp) = compile_regexp(&regexp) {\n            prop_assert!(test_cases.iter().all(|test_case| compiled_regexp.is_match(test_case)));\n        }\n    }\n\n    #[test]\n    fn matching_regexes_with_conversion_of_whitespace(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_conversion_of_whitespace()\n            .build();\n        if let Ok(compiled_regexp) = compile_regexp(&regexp) {\n            prop_assert!(test_cases.iter().all(|test_case| compiled_regexp.is_match(test_case)));\n        }\n    }\n\n    #[test]\n    fn matching_regexes_with_conversion_of_whitespace_and_verbose_mode(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_conversion_of_whitespace()\n            .with_verbose_mode()\n            .build();\n        if let Ok(compiled_regexp) = compile_regexp(&regexp) {\n            prop_assert!(test_cases.iter().all(|test_case| compiled_regexp.is_match(test_case)));\n        }\n    }\n\n    #[test]\n    fn matching_regexes_with_conversion_of_non_whitespace(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_conversion_of_non_whitespace()\n            .build();\n        if let Ok(compiled_regexp) = compile_regexp(&regexp) {\n            prop_assert!(test_cases.iter().all(|test_case| compiled_regexp.is_match(test_case)));\n        }\n    }\n\n    #[test]\n    fn matching_regexes_with_conversion_of_non_whitespace_and_verbose_mode(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_conversion_of_non_whitespace()\n            .with_verbose_mode()\n            .build();\n        if let Ok(compiled_regexp) = compile_regexp(&regexp) {\n            prop_assert!(test_cases.iter().all(|test_case| compiled_regexp.is_match(test_case)));\n        }\n    }\n\n    #[test]\n    fn matching_regexes_with_conversion_of_words(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_conversion_of_words()\n            .build();\n        if let Ok(compiled_regexp) = compile_regexp(&regexp) {\n            prop_assert!(test_cases.iter().all(|test_case| compiled_regexp.is_match(test_case)));\n        }\n    }\n\n    #[test]\n    fn matching_regexes_with_conversion_of_words_and_verbose_mode(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_conversion_of_words()\n            .with_verbose_mode()\n            .build();\n        if let Ok(compiled_regexp) = compile_regexp(&regexp) {\n            prop_assert!(test_cases.iter().all(|test_case| compiled_regexp.is_match(test_case)));\n        }\n    }\n\n    #[test]\n    fn matching_regexes_with_conversion_of_non_words(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_conversion_of_non_words()\n            .build();\n        if let Ok(compiled_regexp) = compile_regexp(&regexp) {\n            prop_assert!(test_cases.iter().all(|test_case| compiled_regexp.is_match(test_case)));\n        }\n    }\n\n    #[test]\n    fn matching_regexes_with_conversion_of_non_words_and_verbose_mode(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_conversion_of_non_words()\n            .with_verbose_mode()\n            .build();\n        if let Ok(compiled_regexp) = compile_regexp(&regexp) {\n            prop_assert!(test_cases.iter().all(|test_case| compiled_regexp.is_match(test_case)));\n        }\n    }\n\n    #[test]\n    fn matching_regexes_with_conversion_of_repetitions(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5),\n        minimum_repetitions in 1..100u32,\n        minimum_substring_length in 1..100u32\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_conversion_of_repetitions()\n            .with_minimum_repetitions(minimum_repetitions)\n            .with_minimum_substring_length(minimum_substring_length)\n            .build();\n        if let Ok(compiled_regexp) = compile_regexp(&regexp) {\n            prop_assert!(test_cases.iter().all(|test_case| compiled_regexp.is_match(test_case)));\n        }\n    }\n\n    #[test]\n    fn matching_regexes_with_conversion_of_repetitions_and_verbose_mode(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5),\n        minimum_repetitions in 1..100u32,\n        minimum_substring_length in 1..100u32\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec)\n            .with_conversion_of_repetitions()\n            .with_minimum_repetitions(minimum_repetitions)\n            .with_minimum_substring_length(minimum_substring_length)\n            .with_verbose_mode()\n            .build();\n        if let Ok(compiled_regexp) = compile_regexp(&regexp) {\n            prop_assert!(test_cases.iter().all(|test_case| compiled_regexp.is_match(test_case)));\n        }\n    }\n\n    #[test]\n    fn matching_regexes_without_start_anchor(\n        test_cases in prop::collection::hash_set(\"[A-C]{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec).without_start_anchor().build();\n        if let Ok(compiled_regexp) = compile_regexp(&regexp) {\n            for test_case in test_cases_vec {\n                let substrings = compiled_regexp.find_iter(&test_case).map(|m| m.as_str()).collect::<Vec<_>>();\n                prop_assert_eq!(\n                    substrings.len(),\n                    1,\n                    \"expression '{}' does not match test case '{}' entirely but {} of its substrings: {:?}\",\n                    regexp,\n                    test_case,\n                    substrings.len(),\n                    substrings\n                );\n            }\n        }\n    }\n\n    #[test]\n    fn matching_regexes_without_end_anchor(\n        test_cases in prop::collection::hash_set(\"[A-C]{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec).without_end_anchor().build();\n        if let Ok(compiled_regexp) = compile_regexp(&regexp) {\n            for test_case in test_cases_vec {\n                let substrings = compiled_regexp.find_iter(&test_case).map(|m| m.as_str()).collect::<Vec<_>>();\n                prop_assert_eq!(\n                    substrings.len(),\n                    1,\n                    \"expression '{}' does not match test case '{}' entirely but {} of its substrings: {:?}\",\n                    regexp,\n                    test_case,\n                    substrings.len(),\n                    substrings\n                );\n            }\n        }\n    }\n\n    #[test]\n    fn matching_regexes_without_anchors(\n        test_cases in prop::collection::hash_set(\"[A-C]{1,10}\", 1..=5)\n    ) {\n        let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n        let regexp = RegExpBuilder::from(&test_cases_vec).without_anchors().build();\n        if let Ok(compiled_regexp) = compile_regexp(&regexp) {\n            for test_case in test_cases_vec {\n                let substrings = compiled_regexp.find_iter(&test_case).map(|m| m.as_str()).collect::<Vec<_>>();\n                prop_assert_eq!(\n                    substrings.len(),\n                    1,\n                    \"expression '{}' does not match test case '{}' entirely but {} of its substrings: {:?}\",\n                    regexp,\n                    test_case,\n                    substrings.len(),\n                    substrings\n                );\n            }\n        }\n    }\n\n    #[test]\n    fn regexes_not_matching_other_strings_with_default_settings(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5),\n        other_strings in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        if test_cases.is_disjoint(&other_strings) {\n            let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n            let regexp = RegExpBuilder::from(&test_cases_vec).build();\n            if let Ok(compiled_regexp) = compile_regexp(&regexp) {\n                prop_assert!(other_strings.iter().all(|other_string| !compiled_regexp.is_match(other_string)));\n            }\n        }\n    }\n\n    #[test]\n    fn regexes_not_matching_other_strings_with_escape_sequences(\n        test_cases in prop::collection::hash_set(\".{1,10}\", 1..=5),\n        other_strings in prop::collection::hash_set(\".{1,10}\", 1..=5)\n    ) {\n        if test_cases.is_disjoint(&other_strings) {\n            let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();\n            let regexp = RegExpBuilder::from(&test_cases_vec)\n                .with_escaping_of_non_ascii_chars(false)\n                .build();\n            if let Ok(compiled_regexp) = compile_regexp(&regexp) {\n                prop_assert!(other_strings.iter().all(|other_string| !compiled_regexp.is_match(other_string)));\n            }\n        }\n    }\n}\n\nfn compile_regexp(regexp: &str) -> Result<Regex, Error> {\n    RegexBuilder::new(regexp).size_limit(20000000).build()\n}\n"
  },
  {
    "path": "tests/python/test_grex.py",
    "content": "#\n# Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport inspect\nimport pytest\nimport re\n\nfrom grex import RegExpBuilder\n\n\n@pytest.mark.parametrize(\n    \"test_cases,expected_pattern\",\n    [\n        pytest.param([\"abc\", \"abd\", \"abe\"], \"^ab[c-e]$\"),\n    ]\n)\ndef test_default_settings(test_cases, expected_pattern):\n    pattern = RegExpBuilder.from_test_cases(test_cases).build()\n    assert pattern == expected_pattern\n    for test_case in test_cases:\n        assert re.match(pattern, test_case)\n\n\n@pytest.mark.parametrize(\n    \"test_cases,expected_pattern\",\n    [\n        pytest.param([\"My ♥ and 💩 is yours.\"], \"^My \\\\u2665 and \\\\U0001f4a9 is yours\\\\.$\"),\n    ]\n)\ndef test_escaping(test_cases, expected_pattern):\n    pattern = (RegExpBuilder.from_test_cases(test_cases)\n               .with_escaping_of_non_ascii_chars(use_surrogate_pairs=False)\n               .build())\n    assert pattern == expected_pattern\n    for test_case in test_cases:\n        assert re.match(pattern, test_case)\n\n\n@pytest.mark.parametrize(\n    \"test_cases,expected_pattern\",\n    [\n        pytest.param([\"My ♥ and 💩 is yours.\"], \"^My \\\\u2665 and \\\\ud83d\\\\udca9 is yours\\\\.$\"),\n    ]\n)\ndef test_escaping_with_surrogate_pairs(test_cases, expected_pattern):\n    pattern = (RegExpBuilder.from_test_cases(test_cases)\n               .with_escaping_of_non_ascii_chars(use_surrogate_pairs=True)\n               .build())\n    assert pattern == expected_pattern\n    # module re does not support matching surrogate pairs\n\n\n@pytest.mark.parametrize(\n    \"test_cases,expected_pattern\",\n    [\n        pytest.param([\"efgh\", \"abcxy\", \"abcw\"], \"^(abc(xy|w)|efgh)$\"),\n    ]\n)\ndef test_capturing_groups(test_cases, expected_pattern):\n    pattern = (RegExpBuilder.from_test_cases(test_cases)\n               .with_capturing_groups()\n               .build())\n    assert pattern == expected_pattern\n    for test_case in test_cases:\n        assert re.match(pattern, test_case)\n\n\n@pytest.mark.parametrize(\n    \"test_cases,expected_pattern\",\n    [\n        pytest.param([\"efgh\", \"abcxy\", \"abcw\"], \"(?:abc(?:xy|w)|efgh)\"),\n    ]\n)\ndef test_without_anchors(test_cases, expected_pattern):\n    pattern = (RegExpBuilder.from_test_cases(test_cases)\n               .without_anchors()\n               .build())\n    assert pattern == expected_pattern\n    for test_case in test_cases:\n        assert re.match(pattern, test_case)\n\n\n@pytest.mark.parametrize(\n    \"test_cases,expected_pattern\",\n    [\n        pytest.param([\"ABC\", \"zBC\", \"abc\", \"AbC\", \"aBc\"], \"(?i)^[az]bc$\"),\n    ]\n)\ndef test_case_insensitive_matching(test_cases, expected_pattern):\n    pattern = (RegExpBuilder.from_test_cases(test_cases)\n               .with_case_insensitive_matching()\n               .build())\n    assert pattern == expected_pattern\n    for test_case in test_cases:\n        assert re.match(pattern, test_case)\n\n\n@pytest.mark.parametrize(\n    \"test_cases,expected_pattern\",\n    [\n        pytest.param(\n            [\"[a-z]\", \"(d,e,f)\"],\n            inspect.cleandoc(\"\"\"\n                (?x)\n                ^\n                  (?:\n                    \\\\(d,e,f\\\\)\n                    |\n                    \\\\[a\\\\-z\\\\]\n                  )\n                $\n                \"\"\")\n        ),\n    ]\n)\ndef test_verbose_mode(test_cases, expected_pattern):\n    pattern = (RegExpBuilder.from_test_cases(test_cases)\n               .with_verbose_mode()\n               .build())\n    assert pattern == expected_pattern\n    for test_case in test_cases:\n        assert re.match(pattern, test_case)\n\n\n@pytest.mark.parametrize(\n    \"test_cases,expected_pattern\",\n    [\n        pytest.param(\n            [\"Ä@Ö€Ü\", \"ä@ö€ü\", \"Ä@ö€Ü\", \"ä@Ö€ü\"],\n            inspect.cleandoc(\"\"\"\n                (?ix)\n                ^\n                  ä@ö€ü\n                $\n                \"\"\")\n        )\n    ]\n)\ndef test_case_insensitive_matching_and_verbose_mode(test_cases, expected_pattern):\n    pattern = (RegExpBuilder.from_test_cases(test_cases)\n               .with_case_insensitive_matching()\n               .with_verbose_mode()\n               .build())\n    assert pattern == expected_pattern\n    for test_case in test_cases:\n        assert re.match(pattern, test_case)\n\n\n@pytest.mark.parametrize(\n    \"test_cases,expected_pattern\",\n    [\n        pytest.param([\"a\", \"b\\nx\\nx\", \"c\"], \"^(?:b(?:\\\\nx){2}|[ac])$\"),\n    ]\n)\ndef test_conversion_of_repetitions(test_cases, expected_pattern):\n    pattern = (RegExpBuilder.from_test_cases(test_cases)\n               .with_conversion_of_repetitions()\n               .build())\n    assert pattern == expected_pattern\n    for test_case in test_cases:\n        assert re.match(pattern, test_case)\n\n\n@pytest.mark.parametrize(\n    \"test_cases,expected_pattern\",\n    [\n        pytest.param([\"My ♥♥♥ and 💩💩 is yours.\"], \"^My \\\\u2665{3} and \\\\U0001f4a9{2} is yours\\\\.$\"),\n    ]\n)\ndef test_escaping_and_conversion_of_repetitions(test_cases, expected_pattern):\n    pattern = (RegExpBuilder.from_test_cases(test_cases)\n               .with_escaping_of_non_ascii_chars(use_surrogate_pairs=False)\n               .with_conversion_of_repetitions()\n               .build())\n    assert pattern == expected_pattern\n    for test_case in test_cases:\n        assert re.match(pattern, test_case)\n\n\n@pytest.mark.parametrize(\n    \"test_cases,expected_pattern\",\n    [\n        pytest.param([\"a1b2c3\"], \"^a\\\\db\\\\dc\\\\d$\"),\n    ]\n)\ndef test_conversion_of_digits(test_cases, expected_pattern):\n    pattern = (RegExpBuilder.from_test_cases(test_cases)\n               .with_conversion_of_digits()\n               .build())\n    assert pattern == expected_pattern\n    for test_case in test_cases:\n        assert re.match(pattern, test_case)\n\n\n@pytest.mark.parametrize(\n    \"test_cases,expected_pattern\",\n    [\n        pytest.param([\"a1b2c3\"], \"^\\\\D1\\\\D2\\\\D3$\"),\n    ]\n)\ndef test_conversion_of_non_digits(test_cases, expected_pattern):\n    pattern = (RegExpBuilder.from_test_cases(test_cases)\n               .with_conversion_of_non_digits()\n               .build())\n    assert pattern == expected_pattern\n    for test_case in test_cases:\n        assert re.match(pattern, test_case)\n\n\n@pytest.mark.parametrize(\n    \"test_cases,expected_pattern\",\n    [\n        pytest.param([\"\\n\\t\", \"\\r\"], \"^\\\\s(?:\\\\s)?$\"),\n    ]\n)\ndef test_conversion_of_whitespace(test_cases, expected_pattern):\n    pattern = (RegExpBuilder.from_test_cases(test_cases)\n               .with_conversion_of_whitespace()\n               .build())\n    assert pattern == expected_pattern\n    for test_case in test_cases:\n        assert re.match(pattern, test_case)\n\n\n@pytest.mark.parametrize(\n    \"test_cases,expected_pattern\",\n    [\n        pytest.param([\"a1 b2 c3\"], \"^\\\\S\\\\S \\\\S\\\\S \\\\S\\\\S$\"),\n    ]\n)\ndef test_conversion_of_non_whitespace(test_cases, expected_pattern):\n    pattern = (RegExpBuilder.from_test_cases(test_cases)\n               .with_conversion_of_non_whitespace()\n               .build())\n    assert pattern == expected_pattern\n    for test_case in test_cases:\n        assert re.match(pattern, test_case)\n\n\n@pytest.mark.parametrize(\n    \"test_cases,expected_pattern\",\n    [\n        pytest.param([\"abc\", \"1234\"], \"^\\\\w\\\\w\\\\w(?:\\\\w)?$\"),\n    ]\n)\ndef test_conversion_of_words(test_cases, expected_pattern):\n    pattern = (RegExpBuilder.from_test_cases(test_cases)\n               .with_conversion_of_words()\n               .build())\n    assert pattern == expected_pattern\n    for test_case in test_cases:\n        assert re.match(pattern, test_case)\n\n\n@pytest.mark.parametrize(\n    \"test_cases,expected_pattern\",\n    [\n        pytest.param([\"abc 1234\"], \"^abc\\\\W1234$\"),\n    ]\n)\ndef test_conversion_of_non_words(test_cases, expected_pattern):\n    pattern = (RegExpBuilder.from_test_cases(test_cases)\n               .with_conversion_of_non_words()\n               .build())\n    assert pattern == expected_pattern\n    for test_case in test_cases:\n        assert re.match(pattern, test_case)\n\n\n@pytest.mark.parametrize(\n    \"test_cases,expected_pattern\",\n    [\n        pytest.param([\"aababab\"], \"^aababab$\"),\n        pytest.param([\"aabababab\"], \"^a(?:ab){4}$\")\n    ]\n)\ndef test_minimum_repetitions(test_cases, expected_pattern):\n    pattern = (RegExpBuilder.from_test_cases(test_cases)\n               .with_conversion_of_repetitions()\n               .with_minimum_repetitions(3)\n               .build())\n    assert pattern == expected_pattern\n    for test_case in test_cases:\n        assert re.match(pattern, test_case)\n\n\n@pytest.mark.parametrize(\n    \"test_cases,expected_pattern\",\n    [\n        pytest.param([\"ababab\"], \"^ababab$\"),\n        pytest.param([\"abcabcabc\"], \"^(?:abc){3}$\")\n    ]\n)\ndef test_minimum_substring_length(test_cases, expected_pattern):\n    pattern = (RegExpBuilder.from_test_cases(test_cases)\n               .with_conversion_of_repetitions()\n               .with_minimum_substring_length(3)\n               .build())\n    assert pattern == expected_pattern\n    for test_case in test_cases:\n        assert re.match(pattern, test_case)\n\n\ndef test_error_for_empty_test_cases():\n    with pytest.raises(ValueError) as exception_info:\n        RegExpBuilder.from_test_cases([])\n    assert (\n        exception_info.value.args[0] ==\n        \"No test cases have been provided for regular expression generation\"\n    )\n\n\ndef test_error_for_invalid_minimum_repetitions():\n    with pytest.raises(ValueError) as exception_info:\n        RegExpBuilder.from_test_cases([\"abcd\"]).with_minimum_repetitions(-4)\n    assert (\n        exception_info.value.args[0] ==\n        \"Quantity of minimum repetitions must be greater than zero\"\n    )\n\n\ndef test_error_for_invalid_minimum_substring_length():\n    with pytest.raises(ValueError) as exception_info:\n        RegExpBuilder.from_test_cases([\"abcd\"]).with_minimum_substring_length(-2)\n    assert (\n        exception_info.value.args[0] ==\n        \"Minimum substring length must be greater than zero\"\n    )\n"
  },
  {
    "path": "tests/wasm_browser_tests.rs",
    "content": "/*\n * Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#![cfg(target_family = \"wasm\")]\n\nuse grex::WasmRegExpBuilder;\nuse indoc::indoc;\nuse wasm_bindgen::JsValue;\nuse wasm_bindgen_test::*;\n\nwasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);\n\n#[wasm_bindgen_test]\nfn assert_regexpbuilder_succeeds() {\n    let test_cases = Box::new([JsValue::from(\"hello\"), JsValue::from(\"world\")]);\n    let builder = WasmRegExpBuilder::from(test_cases);\n    assert!(builder.is_ok());\n    let regexp = builder.unwrap().build();\n    assert_eq!(regexp, \"^(?:hello|world)$\");\n}\n\n#[wasm_bindgen_test]\nfn assert_regexpbuilder_fails() {\n    let builder = WasmRegExpBuilder::from(Box::new([]));\n    assert_eq!(\n        builder.err(),\n        Some(JsValue::from(\n            \"No test cases have been provided for regular expression generation\"\n        ))\n    );\n}\n\n#[wasm_bindgen_test]\nfn test_conversion_of_digits() {\n    let test_cases = Box::new([JsValue::from(\"abc  \"), JsValue::from(\"123\")]);\n    let regexp = WasmRegExpBuilder::from(test_cases)\n        .unwrap()\n        .withConversionOfDigits()\n        .build();\n    assert_eq!(regexp, \"^(?:abc  |\\\\d\\\\d\\\\d)$\");\n}\n\n#[wasm_bindgen_test]\nfn test_conversion_of_non_digits() {\n    let test_cases = Box::new([JsValue::from(\"abc  \"), JsValue::from(\"123\")]);\n    let regexp = WasmRegExpBuilder::from(test_cases)\n        .unwrap()\n        .withConversionOfNonDigits()\n        .build();\n    assert_eq!(regexp, \"^(?:\\\\D\\\\D\\\\D\\\\D\\\\D|123)$\");\n}\n\n#[wasm_bindgen_test]\nfn test_conversion_of_whitespace() {\n    let test_cases = Box::new([JsValue::from(\"abc  \"), JsValue::from(\"123\")]);\n    let regexp = WasmRegExpBuilder::from(test_cases)\n        .unwrap()\n        .withConversionOfWhitespace()\n        .build();\n    assert_eq!(regexp, \"^(?:abc\\\\s\\\\s|123)$\");\n}\n\n#[wasm_bindgen_test]\nfn test_conversion_of_non_whitespace() {\n    let test_cases = Box::new([JsValue::from(\"abc  \"), JsValue::from(\"123\")]);\n    let regexp = WasmRegExpBuilder::from(test_cases)\n        .unwrap()\n        .withConversionOfNonWhitespace()\n        .build();\n    assert_eq!(regexp, \"^\\\\S\\\\S\\\\S(?:  )?$\");\n}\n\n#[wasm_bindgen_test]\nfn test_conversion_of_words() {\n    let test_cases = Box::new([JsValue::from(\"abc  \"), JsValue::from(\"123\")]);\n    let regexp = WasmRegExpBuilder::from(test_cases)\n        .unwrap()\n        .withConversionOfWords()\n        .build();\n    assert_eq!(regexp, \"^\\\\w\\\\w\\\\w(?:  )?$\");\n}\n\n#[wasm_bindgen_test]\nfn test_conversion_of_non_words() {\n    let test_cases = Box::new([JsValue::from(\"abc  \"), JsValue::from(\"123\")]);\n    let regexp = WasmRegExpBuilder::from(test_cases)\n        .unwrap()\n        .withConversionOfNonWords()\n        .build();\n    assert_eq!(regexp, \"^(?:abc\\\\W\\\\W|123)$\");\n}\n\n#[wasm_bindgen_test]\nfn test_conversion_of_repetitions() {\n    let test_cases = Box::new([JsValue::from(\"abc  \"), JsValue::from(\"123\")]);\n    let regexp = WasmRegExpBuilder::from(test_cases)\n        .unwrap()\n        .withConversionOfRepetitions()\n        .build();\n    assert_eq!(regexp, \"^(?:abc {2}|123)$\");\n}\n\n#[wasm_bindgen_test]\nfn test_case_insensitive_matching() {\n    let test_cases = Box::new([\n        JsValue::from(\"ABC\"),\n        JsValue::from(\"abc  \"),\n        JsValue::from(\"123\"),\n    ]);\n    let regexp = WasmRegExpBuilder::from(test_cases)\n        .unwrap()\n        .withCaseInsensitiveMatching()\n        .build();\n    assert_eq!(regexp, \"(?i)^(?:abc(?:  )?|123)$\");\n}\n\n#[wasm_bindgen_test]\nfn test_capturing_groups() {\n    let test_cases = Box::new([JsValue::from(\"abc  \"), JsValue::from(\"123\")]);\n    let regexp = WasmRegExpBuilder::from(test_cases)\n        .unwrap()\n        .withCapturingGroups()\n        .build();\n    assert_eq!(regexp, \"^(abc  |123)$\");\n}\n\n#[wasm_bindgen_test]\nfn test_escaping_of_non_ascii_chars() {\n    let test_cases = Box::new([\n        JsValue::from(\"abc  \"),\n        JsValue::from(\"123\"),\n        JsValue::from(\"♥\"),\n    ]);\n    let regexp = WasmRegExpBuilder::from(test_cases)\n        .unwrap()\n        .withEscapingOfNonAsciiChars(false)\n        .build();\n    assert_eq!(regexp, \"^(?:abc  |123|\\\\u{2665})$\");\n}\n\n#[wasm_bindgen_test]\nfn test_verbose_mode() {\n    let test_cases = Box::new([\n        JsValue::from(\"abc  \"),\n        JsValue::from(\"123\"),\n        JsValue::from(\"♥\"),\n    ]);\n    let regexp = WasmRegExpBuilder::from(test_cases)\n        .unwrap()\n        .withVerboseMode()\n        .build();\n    assert_eq!(\n        regexp,\n        indoc!(\n            r#\"\n            (?x)\n            ^\n              (?:\n                abc\\ \\ \n                |\n                123\n                |\n                ♥\n              )\n            $\"#\n        )\n    );\n}\n\n#[wasm_bindgen_test]\nfn test_without_start_anchor() {\n    let test_cases = Box::new([JsValue::from(\"abc  \"), JsValue::from(\"123\")]);\n    let regexp = WasmRegExpBuilder::from(test_cases)\n        .unwrap()\n        .withoutStartAnchor()\n        .build();\n    assert_eq!(regexp, \"(?:abc  |123)$\");\n}\n\n#[wasm_bindgen_test]\nfn test_without_end_anchor() {\n    let test_cases = Box::new([JsValue::from(\"abc  \"), JsValue::from(\"123\")]);\n    let regexp = WasmRegExpBuilder::from(test_cases)\n        .unwrap()\n        .withoutEndAnchor()\n        .build();\n    assert_eq!(regexp, \"^(?:abc  |123)\");\n}\n\n#[wasm_bindgen_test]\nfn test_without_anchors() {\n    let test_cases = Box::new([JsValue::from(\"abc  \"), JsValue::from(\"123\")]);\n    let regexp = WasmRegExpBuilder::from(test_cases)\n        .unwrap()\n        .withoutAnchors()\n        .build();\n    assert_eq!(regexp, \"(?:abc  |123)\");\n}\n\n#[wasm_bindgen_test]\nfn test_minimum_repetitions() {\n    let test_cases = Box::new([JsValue::from(\"abc  \"), JsValue::from(\"123\")]);\n    let builder = WasmRegExpBuilder::from(test_cases)\n        .unwrap()\n        .withMinimumRepetitions(0);\n    assert_eq!(\n        builder.err(),\n        Some(JsValue::from(\n            \"Quantity of minimum repetitions must be greater than zero\"\n        ))\n    );\n}\n\n#[wasm_bindgen_test]\nfn test_minimum_substring_length() {\n    let test_cases = Box::new([JsValue::from(\"abc  \"), JsValue::from(\"123\")]);\n    let builder = WasmRegExpBuilder::from(test_cases)\n        .unwrap()\n        .withMinimumSubstringLength(0);\n    assert_eq!(\n        builder.err(),\n        Some(JsValue::from(\n            \"Minimum substring length must be greater than zero\"\n        ))\n    );\n}\n"
  },
  {
    "path": "tests/wasm_node_tests.rs",
    "content": "/*\n * Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#![cfg(target_family = \"wasm\")]\n\nuse grex::WasmRegExpBuilder;\nuse indoc::indoc;\nuse wasm_bindgen::JsValue;\nuse wasm_bindgen_test::*;\n\n#[wasm_bindgen_test]\nfn assert_regexpbuilder_succeeds() {\n    let test_cases = Box::new([JsValue::from(\"hello\"), JsValue::from(\"world\")]);\n    let builder = WasmRegExpBuilder::from(test_cases);\n    assert!(builder.is_ok());\n    let regexp = builder.unwrap().build();\n    assert_eq!(regexp, \"^(?:hello|world)$\");\n}\n\n#[wasm_bindgen_test]\nfn assert_regexpbuilder_fails() {\n    let builder = WasmRegExpBuilder::from(Box::new([]));\n    assert_eq!(\n        builder.err(),\n        Some(JsValue::from(\n            \"No test cases have been provided for regular expression generation\"\n        ))\n    );\n}\n\n#[wasm_bindgen_test]\nfn test_conversion_of_digits() {\n    let test_cases = Box::new([JsValue::from(\"abc  \"), JsValue::from(\"123\")]);\n    let regexp = WasmRegExpBuilder::from(test_cases)\n        .unwrap()\n        .withConversionOfDigits()\n        .build();\n    assert_eq!(regexp, \"^(?:abc  |\\\\d\\\\d\\\\d)$\");\n}\n\n#[wasm_bindgen_test]\nfn test_conversion_of_non_digits() {\n    let test_cases = Box::new([JsValue::from(\"abc  \"), JsValue::from(\"123\")]);\n    let regexp = WasmRegExpBuilder::from(test_cases)\n        .unwrap()\n        .withConversionOfNonDigits()\n        .build();\n    assert_eq!(regexp, \"^(?:\\\\D\\\\D\\\\D\\\\D\\\\D|123)$\");\n}\n\n#[wasm_bindgen_test]\nfn test_conversion_of_whitespace() {\n    let test_cases = Box::new([JsValue::from(\"abc  \"), JsValue::from(\"123\")]);\n    let regexp = WasmRegExpBuilder::from(test_cases)\n        .unwrap()\n        .withConversionOfWhitespace()\n        .build();\n    assert_eq!(regexp, \"^(?:abc\\\\s\\\\s|123)$\");\n}\n\n#[wasm_bindgen_test]\nfn test_conversion_of_non_whitespace() {\n    let test_cases = Box::new([JsValue::from(\"abc  \"), JsValue::from(\"123\")]);\n    let regexp = WasmRegExpBuilder::from(test_cases)\n        .unwrap()\n        .withConversionOfNonWhitespace()\n        .build();\n    assert_eq!(regexp, \"^\\\\S\\\\S\\\\S(?:  )?$\");\n}\n\n#[wasm_bindgen_test]\nfn test_conversion_of_words() {\n    let test_cases = Box::new([JsValue::from(\"abc  \"), JsValue::from(\"123\")]);\n    let regexp = WasmRegExpBuilder::from(test_cases)\n        .unwrap()\n        .withConversionOfWords()\n        .build();\n    assert_eq!(regexp, \"^\\\\w\\\\w\\\\w(?:  )?$\");\n}\n\n#[wasm_bindgen_test]\nfn test_conversion_of_non_words() {\n    let test_cases = Box::new([JsValue::from(\"abc  \"), JsValue::from(\"123\")]);\n    let regexp = WasmRegExpBuilder::from(test_cases)\n        .unwrap()\n        .withConversionOfNonWords()\n        .build();\n    assert_eq!(regexp, \"^(?:abc\\\\W\\\\W|123)$\");\n}\n\n#[wasm_bindgen_test]\nfn test_conversion_of_repetitions() {\n    let test_cases = Box::new([JsValue::from(\"abc  \"), JsValue::from(\"123\")]);\n    let regexp = WasmRegExpBuilder::from(test_cases)\n        .unwrap()\n        .withConversionOfRepetitions()\n        .build();\n    assert_eq!(regexp, \"^(?:abc {2}|123)$\");\n}\n\n#[wasm_bindgen_test]\nfn test_case_insensitive_matching() {\n    let test_cases = Box::new([\n        JsValue::from(\"ABC\"),\n        JsValue::from(\"abc  \"),\n        JsValue::from(\"123\"),\n    ]);\n    let regexp = WasmRegExpBuilder::from(test_cases)\n        .unwrap()\n        .withCaseInsensitiveMatching()\n        .build();\n    assert_eq!(regexp, \"(?i)^(?:abc(?:  )?|123)$\");\n}\n\n#[wasm_bindgen_test]\nfn test_capturing_groups() {\n    let test_cases = Box::new([JsValue::from(\"abc  \"), JsValue::from(\"123\")]);\n    let regexp = WasmRegExpBuilder::from(test_cases)\n        .unwrap()\n        .withCapturingGroups()\n        .build();\n    assert_eq!(regexp, \"^(abc  |123)$\");\n}\n\n#[wasm_bindgen_test]\nfn test_escaping_of_non_ascii_chars() {\n    let test_cases = Box::new([\n        JsValue::from(\"abc  \"),\n        JsValue::from(\"123\"),\n        JsValue::from(\"♥\"),\n    ]);\n    let regexp = WasmRegExpBuilder::from(test_cases)\n        .unwrap()\n        .withEscapingOfNonAsciiChars(false)\n        .build();\n    assert_eq!(regexp, \"^(?:abc  |123|\\\\u{2665})$\");\n}\n\n#[wasm_bindgen_test]\nfn test_verbose_mode() {\n    let test_cases = Box::new([\n        JsValue::from(\"abc  \"),\n        JsValue::from(\"123\"),\n        JsValue::from(\"♥\"),\n    ]);\n    let regexp = WasmRegExpBuilder::from(test_cases)\n        .unwrap()\n        .withVerboseMode()\n        .build();\n    assert_eq!(\n        regexp,\n        indoc!(\n            r#\"\n            (?x)\n            ^\n              (?:\n                abc\\ \\ \n                |\n                123\n                |\n                ♥\n              )\n            $\"#\n        )\n    );\n}\n\n#[wasm_bindgen_test]\nfn test_without_start_anchor() {\n    let test_cases = Box::new([JsValue::from(\"abc  \"), JsValue::from(\"123\")]);\n    let regexp = WasmRegExpBuilder::from(test_cases)\n        .unwrap()\n        .withoutStartAnchor()\n        .build();\n    assert_eq!(regexp, \"(?:abc  |123)$\");\n}\n\n#[wasm_bindgen_test]\nfn test_without_end_anchor() {\n    let test_cases = Box::new([JsValue::from(\"abc  \"), JsValue::from(\"123\")]);\n    let regexp = WasmRegExpBuilder::from(test_cases)\n        .unwrap()\n        .withoutEndAnchor()\n        .build();\n    assert_eq!(regexp, \"^(?:abc  |123)\");\n}\n\n#[wasm_bindgen_test]\nfn test_without_anchors() {\n    let test_cases = Box::new([JsValue::from(\"abc  \"), JsValue::from(\"123\")]);\n    let regexp = WasmRegExpBuilder::from(test_cases)\n        .unwrap()\n        .withoutAnchors()\n        .build();\n    assert_eq!(regexp, \"(?:abc  |123)\");\n}\n\n#[wasm_bindgen_test]\nfn test_minimum_repetitions() {\n    let test_cases = Box::new([JsValue::from(\"abc  \"), JsValue::from(\"123\")]);\n    let builder = WasmRegExpBuilder::from(test_cases)\n        .unwrap()\n        .withMinimumRepetitions(0);\n    assert_eq!(\n        builder.err(),\n        Some(JsValue::from(\n            \"Quantity of minimum repetitions must be greater than zero\"\n        ))\n    );\n}\n\n#[wasm_bindgen_test]\nfn test_minimum_substring_length() {\n    let test_cases = Box::new([JsValue::from(\"abc  \"), JsValue::from(\"123\")]);\n    let builder = WasmRegExpBuilder::from(test_cases)\n        .unwrap()\n        .withMinimumSubstringLength(0);\n    assert_eq!(\n        builder.err(),\n        Some(JsValue::from(\n            \"Minimum substring length must be greater than zero\"\n        ))\n    );\n}\n"
  }
]