pax_global_header00006660000000000000000000000064147357671250014533gustar00rootroot0000000000000052 comment=3310f98a5572ad78efaefaac2f95d63bb9bd6fca pathvalidate-3.2.3/000077500000000000000000000000001473576712500142065ustar00rootroot00000000000000pathvalidate-3.2.3/.github/000077500000000000000000000000001473576712500155465ustar00rootroot00000000000000pathvalidate-3.2.3/.github/FUNDING.yml000066400000000000000000000011421473576712500173610ustar00rootroot00000000000000# These are supported funding model platforms github: thombashi patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: thombashi issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] pathvalidate-3.2.3/.github/dependabot.yml000066400000000000000000000002501473576712500203730ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" groups: actions-dependencies: patterns: ["*"] pathvalidate-3.2.3/.github/workflows/000077500000000000000000000000001473576712500176035ustar00rootroot00000000000000pathvalidate-3.2.3/.github/workflows/ci.yml000066400000000000000000000111251473576712500207210ustar00rootroot00000000000000name: CI on: push: branches: - master paths-ignore: - ".gitignore" - "README.rst" pull_request: paths-ignore: - ".gitignore" - "README.rst" permissions: contents: read jobs: build-package: runs-on: ubuntu-latest concurrency: group: ${{ github.event_name }}-${{ github.workflow }}-${{ github.ref_name }}-build-pkg cancel-in-progress: true timeout-minutes: 20 steps: - uses: actions/checkout@v4 with: fetch-depth: 0 fetch-tags: true - uses: actions/setup-python@v5 with: python-version: "3.13" cache: pip cache-dependency-path: | setup.py **/*requirements.txt tox.ini - run: make setup-ci - run: make build - uses: actions/upload-artifact@v4 with: name: dist path: ./dist/* build-docs: runs-on: ubuntu-latest concurrency: group: ${{ github.event_name }}-${{ github.workflow }}-${{ github.ref_name }}-build-docs cancel-in-progress: true timeout-minutes: 20 steps: - uses: actions/checkout@v4 with: fetch-depth: 0 fetch-tags: true - uses: actions/setup-python@v5 with: python-version: "3.13" cache: pip cache-dependency-path: | setup.py **/*requirements.txt tox.ini - run: make setup-ci - run: make docs lint: runs-on: ubuntu-latest concurrency: group: ${{ github.event_name }}-${{ github.workflow }}-${{ github.ref_name }}-lint cancel-in-progress: true timeout-minutes: 20 steps: - uses: actions/checkout@v4 with: fetch-depth: 0 fetch-tags: true - uses: actions/setup-python@v5 with: python-version: "3.13" cache: pip cache-dependency-path: | setup.py **/*requirements.txt tox.ini - run: make setup-ci - run: make check unit-test: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "pypy-3.10"] os: [ubuntu-latest, macos-latest, windows-latest] exclude: - os: windows-latest python-version: "3.11" concurrency: group: ${{ github.event_name }}-${{ github.workflow }}-unit-test-${{ matrix.os }}-${{ matrix.python-version }} cancel-in-progress: true timeout-minutes: 20 steps: - uses: GitHubSecurityLab/actions-permissions/monitor@v1 with: config: ${{ vars.PERMISSIONS_CONFIG }} if: ${{ matrix.os == 'ubuntu-latest' }} - uses: actions/checkout@v4 - name: Setup Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} cache: pip cache-dependency-path: | setup.py **/*requirements.txt tox.ini - run: make setup-ci - name: Run tests env: PYTEST_DISCORD_WEBHOOK: ${{ secrets.PYTEST_DISCORD_WEBHOOK }} REPORT_OUTPUT: md_report.md run: | echo "REPORT_FILE=${REPORT_OUTPUT}" >> "$GITHUB_ENV" tox -e cov -- --md-report-output "${REPORT_OUTPUT}" - run: ls -alR if : ${{ matrix.os != 'windows-latest' }} - name: Output reports to the job summary when tests fail if: failure() shell: bash run: | if [ -f "$REPORT_FILE" ]; then echo "
Failed Test Report" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY cat "$REPORT_FILE" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "
" >> $GITHUB_STEP_SUMMARY else echo "No failed tests to report" fi - name: Install coveralls run: python -m pip install --upgrade --disable-pip-version-check coveralls tomli - name: Upload coverage data to coveralls.io env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} COVERALLS_FLAG_NAME: ${{ matrix.os }}-${{ matrix.python-version }} COVERALLS_PARALLEL: true run: coveralls coveralls: name: Indicate completion to coveralls.io needs: unit-test runs-on: ubuntu-latest container: python:3-slim steps: - run: pip3 install --upgrade coveralls - name: Finished env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: coveralls --finish pathvalidate-3.2.3/.github/workflows/on_push_default_branch.yml000066400000000000000000000044101473576712500250210ustar00rootroot00000000000000name: Publish to TestPyPI and Sign with Sigstore on: push: branches: - master paths-ignore: - ".gitignore" - "README.rst" permissions: contents: read jobs: build-wheel-package: runs-on: ubuntu-latest concurrency: group: ${{ github.event_name }}-${{ github.workflow }}-${{ github.ref_name }}-build cancel-in-progress: true timeout-minutes: 20 steps: - uses: actions/checkout@v4 with: fetch-depth: 0 fetch-tags: true - uses: actions/setup-python@v5 with: python-version: "3.13" cache: pip cache-dependency-path: | setup.py **/*requirements.txt tox.ini - run: make setup-ci - run: make build - uses: actions/upload-artifact@v4 with: name: dist path: ./dist/* publish-package: needs: build-wheel-package runs-on: ubuntu-latest concurrency: group: ${{ github.event_name }}-${{ github.workflow }}-${{ github.ref_name }}-publish-pkg cancel-in-progress: true timeout-minutes: 10 environment: name: testpypi url: https://test.pypi.org/p/pathvalidate permissions: id-token: write # IMPORTANT: this permission is mandatory for trusted publishing steps: - uses: actions/download-artifact@v4 with: name: dist path: ./dist - name: Publish package to TestPyPI uses: pypa/gh-action-pypi-publish@release/v1 with: repository-url: https://test.pypi.org/legacy/ skip-existing: true sign-package: needs: publish-package runs-on: ubuntu-latest concurrency: group: ${{ github.event_name }}-${{ github.workflow }}-${{ github.ref_name }}-sign-pkg cancel-in-progress: true timeout-minutes: 10 permissions: id-token: write steps: - uses: actions/download-artifact@v4 with: name: dist path: ./dist - name: Sign the dists with Sigstore uses: sigstore/gh-action-sigstore-python@v3.0.0 with: inputs: >- ./dist/*.tar.gz ./dist/*.whl - uses: actions/upload-artifact@v4 with: name: sigstore path: ./dist/* pathvalidate-3.2.3/.github/workflows/permissions_advisor.yml000066400000000000000000000010361473576712500244300ustar00rootroot00000000000000name: Permissions Advisor permissions: actions: read on: workflow_dispatch: inputs: name: description: 'The name of the workflow file to analyze' required: true type: string count: description: 'How many last runs to analyze' required: false type: number default: 10 jobs: advisor: runs-on: ubuntu-latest steps: - uses: GitHubSecurityLab/actions-permissions/advisor@v1 with: name: ${{ inputs.name }} count: ${{ inputs.count }} pathvalidate-3.2.3/.github/workflows/release.yml000066400000000000000000000033571473576712500217560ustar00rootroot00000000000000name: Build and publish the new version to PyPI on: push: tags: - "v*.*.*" permissions: contents: read jobs: build-package: runs-on: ubuntu-latest timeout-minutes: 20 steps: - uses: actions/checkout@v4 with: fetch-depth: 0 fetch-tags: true - uses: actions/setup-python@v5 with: python-version: "3.13" cache: pip cache-dependency-path: | setup.py **/*requirements.txt tox.ini - run: make setup-ci - run: make build - uses: actions/upload-artifact@v4 with: name: dist path: ./dist/* publish-package: needs: build-package runs-on: ubuntu-latest timeout-minutes: 10 environment: name: pypi url: https://pypi.org/p/pathvalidate permissions: id-token: write # IMPORTANT: this permission is mandatory for trusted publishing steps: - uses: actions/download-artifact@v4 with: name: dist path: ./dist - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@release/v1 generate-relese: needs: publish-package runs-on: ubuntu-latest timeout-minutes: 10 permissions: id-token: write contents: write steps: - uses: actions/download-artifact@v4 with: name: dist path: ./dist - name: Sign the dists with Sigstore uses: sigstore/gh-action-sigstore-python@v3.0.0 with: inputs: >- ./dist/*.tar.gz ./dist/*.whl - name: Generate a GitHub release uses: softprops/action-gh-release@v2 with: generate_release_notes: true files: dist/* pathvalidate-3.2.3/.gitignore000066400000000000000000000025741473576712500162060ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ pip-wheel-metadata/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ # User settings _sandbox/ bin/ *_profile Untitled.ipynb pathvalidate-3.2.3/.readthedocs.yaml000066400000000000000000000003311473576712500174320ustar00rootroot00000000000000version: 2 build: os: ubuntu-22.04 tools: python: "3.11" sphinx: configuration: docs/conf.py formats: - epub python: install: - method: pip path: . extra_requirements: - docs pathvalidate-3.2.3/CHANGELOG.md000066400000000000000000000537501473576712500160310ustar00rootroot00000000000000 # [v3.2.2](https://github.com/thombashi/pathvalidate/releases/tag/v3.2.2) - 2025-01-01 - Fix the detection logic of the reservation words for the file name on Windows: [#57](https://github.com/thombashi/pathvalidate/issues/57) (Thanks to [@jplarocque](https://github.com/jplarocque)) - Drop support for Python 3.7/3.8 - Refactor type annotations **Full Changelog**: https://github.com/thombashi/pathvalidate/compare/v3.2.1...v3.2.2 [Changes][v3.2.2] # [v3.2.1](https://github.com/thombashi/pathvalidate/releases/tag/v3.2.1) - 2024-08-23 ## What's Changed * Test Python 3.12 in CIs by [@MatthieuDartiailh](https://github.com/MatthieuDartiailh) in [#40](https://github.com/thombashi/pathvalidate/pull/40) * Bump actions/setup-python from 4 to 5 by [@dependabot](https://github.com/dependabot) in [#36](https://github.com/thombashi/pathvalidate/pull/36) * Bump actions/upload-artifact from 3 to 4 by [@dependabot](https://github.com/dependabot) in [#38](https://github.com/thombashi/pathvalidate/pull/38) * Bump actions/download-artifact from 3 to 4 by [@dependabot](https://github.com/dependabot) in [#37](https://github.com/thombashi/pathvalidate/pull/37) * Fix CI by [@thombashi](https://github.com/thombashi) in [#41](https://github.com/thombashi/pathvalidate/pull/41) * Update the CI workflow to include a job that publishes packages to TestPyPI by [@thombashi](https://github.com/thombashi) in [#42](https://github.com/thombashi/pathvalidate/pull/42) * Fix coverage report by [@thombashi](https://github.com/thombashi) in [#45](https://github.com/thombashi/pathvalidate/pull/45) * Fix `sanitize_filename` truncation by [@7x11x13](https://github.com/7x11x13) in [#48](https://github.com/thombashi/pathvalidate/pull/48) * Fix validation functions of filepaths by [@thombashi](https://github.com/thombashi) in [#55](https://github.com/thombashi/pathvalidate/pull/55) - If `platform` argument is `windows` or `universal`, filepaths ending with a space or a period should be detected as an error - Fix POSIX-style absolute paths were not detected as errors with `platform="windows"` or `platform="universal"` on Python 3.12 and below * Add support for Python 3.13 by [@thombashi](https://github.com/thombashi) in [#56](https://github.com/thombashi/pathvalidate/pull/56) * Improve type annotations * Add a build and publish workflow * Add Sigstore signatures to release assets * Update copyright year to include the last update year: [#54](https://github.com/thombashi/pathvalidate/issues/54) (Thanks to [@Flimm](https://github.com/Flimm)) * Add CHANGELOG ## New Contributors * [@MatthieuDartiailh](https://github.com/MatthieuDartiailh) made their first contribution in [#40](https://github.com/thombashi/pathvalidate/pull/40) * [@dependabot](https://github.com/dependabot) made their first contribution in [#36](https://github.com/thombashi/pathvalidate/pull/36) * [@7x11x13](https://github.com/7x11x13) made their first contribution in [#48](https://github.com/thombashi/pathvalidate/pull/48) **Full Changelog**: https://github.com/thombashi/pathvalidate/compare/v3.2.0...v3.2.1 [Changes][v3.2.1] # [v3.2.0](https://github.com/thombashi/pathvalidate/releases/tag/v3.2.0) - 2023-09-17 - Add `reserved_name_handler` argument to `sanitize_filename` function and `sanitize_filepath` function - Add `NullValueHandler` class and `ReservedNameHandler` class - Add `fs_encoding` property and `byte_count` property to `ValidationError` class - Add `additional_reserved_names` argument to validate/sanitize functions to allow custom reserved names - Modify the return value format of `ValidationError.__str__` method - Improve type annotations **Full Changelog**: https://github.com/thombashi/pathvalidate/compare/v3.1.0...v3.2.0 [Changes][v3.2.0] # [v3.1.0](https://github.com/thombashi/pathvalidate/releases/tag/v3.1.0) - 2023-07-16 - Fix validation error messages to show the `target-platform` properly: [#34](https://github.com/thombashi/pathvalidate/issues/34) (Thanks to [@matanster](https://github.com/matanster)) - Fix README: out of date with the actual error generated by `validate_filename` [#35](https://github.com/thombashi/pathvalidate/issues/35) (Thanks to [@hXtreme](https://github.com/hXtreme)) - Add `description` property to `ErrorReason` class - Add `as_slog` method to `ValidationError` class - Add `docs` extras - Change the type of the return value of `ValidationError.reason` from `Optional[ErrorReason]` to `ErrorReason` - Update `[build-system]` - Drop support for Python 3.6 **Full Changelog**: https://github.com/thombashi/pathvalidate/compare/v3.0.0...v3.1.0 [Changes][v3.1.0] # [v3.0.0](https://github.com/thombashi/pathvalidate/releases/tag/v3.0.0) - 2023-05-22 ## What's Changed - Trim heading spaces in Windows by [@eggplants](https://github.com/eggplants) in [#28](https://github.com/thombashi/pathvalidate/pull/28) - Consider filesystem encoding for length calculations: [#26](https://github.com/thombashi/pathvalidate/issues/26) (Thanks to [@virlos](https://github.com/virlos)) - Fix type model: [#29](https://github.com/thombashi/pathvalidate/issues/29) (Thanks to [@rogalski](https://github.com/rogalski)) - Fix sanitizing of filenames that only consist of whitespaces and periods - Add `validate_unprintable_char` function - Add `validate_after_sanitize` keyword argument to `sanitize_filename` and `sanitize_filepath` functions - Add error codes to `ErrorReason` - Add `zip_safe=False` to `setup` - Modify to accept `pathvalidate.Platform` type as `platform` arguments - Rename type alias from `Handler` to `NullValueHandler` - Remove `InvalidLengthError` to use `ValidationError` - Improve type annotations - Make it possible to import `FileNameValidator` and `FilePathValidator` classes from the package root - Change constructor arguments of `FileNameSanitizer` and `FilePathSanitizer` classes - Remove `min_len` - Add `validator` - Remove deprecated functions - Modify error messages - Refactoring **Full Changelog**: https://github.com/thombashi/pathvalidate/compare/v2.5.2...v3.0.0 [Changes][v3.0.0] # [v2.5.2](https://github.com/thombashi/pathvalidate/releases/tag/v2.5.2) - 2022-08-20 - Add support for Python 3.11: [#22](https://github.com/thombashi/pathvalidate/issues/22) (Thanks to [@hegjon](https://github.com/hegjon)) **Full Changelog**: https://github.com/thombashi/pathvalidate/compare/v2.5.1...v2.5.2 [Changes][v2.5.2] # [v2.5.1](https://github.com/thombashi/pathvalidate/releases/tag/v2.5.1) - 2022-07-31 ## What's Changed * Add `__all__` by [@eggplants](https://github.com/eggplants) in [#24](https://github.com/thombashi/pathvalidate/pull/24) * Add `DeprecationWarning` to deprecated functions ## New Contributors * [@eggplants](https://github.com/eggplants) made their first contribution in [#24](https://github.com/thombashi/pathvalidate/pull/24) **Full Changelog**: https://github.com/thombashi/pathvalidate/compare/v2.5.0...v2.5.1 [Changes][v2.5.1] # [v2.5.0](https://github.com/thombashi/pathvalidate/releases/tag/v2.5.0) - 2021-09-26 - Add support for Python 3.10 - Drop support for Python 3.5 - Add `null_value_handler` argument to `sanitize_filename`/`sanitize_filepath` functions: [#20](https://github.com/thombashi/pathvalidate/issues/20) (Thanks to @ mkbloke) - Add `AbstractSanitizer`/`AbstractValidator` classes to import path - Add `replace_ansi_escape` function - Add `setup-ci` target to `Makefile` - Modify `min_len`/`max_len` to use default values when assigned minus values [Changes][v2.5.0] # [v2.4.1](https://github.com/thombashi/pathvalidate/releases/tag/v2.4.1) - 2021-04-03 - Fix filename validations that include `'\'` (backslash) on other than Windows: [#18](https://github.com/thombashi/pathvalidate/issues/18) (Thanks to [@Traktormaster](https://github.com/Traktormaster)) [Changes][v2.4.1] # [v2.4.0](https://github.com/thombashi/pathvalidate/releases/tag/v2.4.0) - 2021-03-21 - Add `exclude_symbols` argument to `replace_symbol` function - Fix permissions of files included in `sdist` package binary (Thanks to [@hegjon](https://github.com/hegjon)) [Changes][v2.4.0] # [v2.3.2](https://github.com/thombashi/pathvalidate/releases/tag/v2.3.2) - 2021-01-03 - Fix to disallow file name/path that only white spaces for `universal` platform [Changes][v2.3.2] # [v2.3.1](https://github.com/thombashi/pathvalidate/releases/tag/v2.3.1) - 2020-12-13 - Modify to accept file name/path that consists only whitespaces: [#15](https://github.com/thombashi/pathvalidate/issues/15) (Thank to [@Traktormaster](https://github.com/Traktormaster)) [Changes][v2.3.1] # [v2.3.0](https://github.com/thombashi/pathvalidate/releases/tag/v2.3.0) - 2020-05-03 - Change not to process for `"."`/`".."` by sanitization functions: [#13](https://github.com/thombashi/pathvalidate/issues/13) (Thanks to [@ProfElectric](https://github.com/ProfElectric)) - Change to normalize with `sanitize_filepath` in default - Add normalize interface to `sanitize_filepath` [Changes][v2.3.0] # [v2.2.2](https://github.com/thombashi/pathvalidate/releases/tag/v2.2.2) - 2020-03-28 - Improve file path validation for Windows platform: [#12](https://github.com/thombashi/pathvalidate/issues/12) (Thanks to [@bschollnick](https://github.com/bschollnick)) - Fix `__str__` method - Fix to avoid raise an exception when an absolute path includes `"."`/`".."` - Modify an error message - Modify raising exception from `NullNameError` to `ValidationError` of `validate_pathtype` [Changes][v2.2.2] # [v2.2.1](https://github.com/thombashi/pathvalidate/releases/tag/v2.2.1) - 2020-03-20 - Fix to include `py.typed` to the package [Changes][v2.2.1] # [v2.2.0](https://github.com/thombashi/pathvalidate/releases/tag/v2.2.0) - 2020-02-12 - Add `POSIX` as a platform - Add a reserved keyword for macOS - Change platform of `validate_filepath_arg`/`sanitize_filepath_arg` to `'auto'`: [#11](https://github.com/thombashi/pathvalidate/issues/11) (Thanks to [@freelanceAndy](https://github.com/freelanceAndy)) [Changes][v2.2.0] # [v2.1.0](https://github.com/thombashi/pathvalidate/releases/tag/v2.1.0) - 2020-02-01 - Add `check_reserved` argument to validate/sanitize functions - Add `'/'` as a reserved file path for Linux/macOS - Suppress errors when sanitizing null values - Fix `max_len` value check for file names - Include type annotation information to the package - Remove `dev` extras_require - Bug fixes [Changes][v2.1.0] # [v2.0.0](https://github.com/thombashi/pathvalidate/releases/tag/v2.0.0) - 2020-01-13 - Change to be more strict validation for absolute paths - Fix argparse validator/sanitizer failed when empty inputs - Bug fixes [Changes][v2.0.0] # [v1.1.0](https://github.com/thombashi/pathvalidate/releases/tag/v1.1.0) - 2020-01-04 - Modify validate/sanitize functions for `argparse` - Modify validate/sanitize functions for `click` - Update `dev` extras [Changes][v1.1.0] # [v1.0.0](https://github.com/thombashi/pathvalidate/releases/tag/v1.0.0) - 2020-01-03 - Drop Python 2 support - Modify to use Python 3 functionality - Update `extras_require` - Bug fixes - Remove a deprecated property [Changes][v1.0.0] # [v0.29.1](https://github.com/thombashi/pathvalidate/releases/tag/v0.29.1) - 2020-01-02 - Fix file path length validation: [#10](https://github.com/thombashi/pathvalidate/issues/10) (Thanks to [@UncleGoogle](https://github.com/UncleGoogle)) - Add `.asc` files of packages to PyPI [Changes][v0.29.1] # [v0.29.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.29.0) - 2019-06-16 - Add filename/filepath validators for `argparse`/`click` - Modify error messages [Changes][v0.29.0] # [v0.28.2](https://github.com/thombashi/pathvalidate/releases/tag/v0.28.2) - 2019-05-18 - Fix to properly escape special chars for validation error messages: [#9](https://github.com/thombashi/pathvalidate/issues/9) (Thanks to [@UncleGoogle](https://github.com/UncleGoogle)) [Changes][v0.28.2] # [v0.28.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.28.0) - 2019-05-01 - Drop support for Python 3.4 [Changes][v0.28.0] # [v0.26.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.26.0) - 2019-03-15 - Add support for NTFS reserved names - Improve drive letter handling [Changes][v0.26.0] # [v0.25.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.25.0) - 2019-03-14 - Add `CLOCK$` as a reserved filename for Windows platform: [#8](https://github.com/thombashi/pathvalidate/issues/8) (Thanks to [@sparr](https://github.com/sparr)) - Improve reserved name detection - Add `reserved_name` property to `ReservedNameError` class [Changes][v0.25.0] # [v0.24.1](https://github.com/thombashi/pathvalidate/releases/tag/v0.24.1) - 2019-02-12 - Fix improper error messages - Improve error message readability [Changes][v0.24.1] # [v0.24.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.24.0) - 2019-02-03 - Add `is_valid_filename`/`is_valid_filepath` function - Add `FileNameSanitizer`/`FilePathSanitizer` classes - Add minimum length validation support - Remove deprecated functions [Changes][v0.24.0] # [v0.23.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.23.0) - 2019-01-06 - Improve sanitization/validation for files - Add `.` and `..` as reserved keywords for files - Add limit to `max_filename_len` - Fix platform specific sanitization: [#7](https://github.com/thombashi/pathvalidate/issues/7) - Fix reserved keywords sanitization/validation for files - Integrate `InvalidCharWindowsError` into `InvalidCharError` - Change to use `ReservedNameError` instead of `InvalidReservedNameError` - Breaking changes - Rename a property for `FileSanitizer` from `platform_name` to `platform` - Rename methods argument from `platform_name` to `platform` [Changes][v0.23.0] # [v0.22.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.22.0) - 2018-12-23 - Add universal (platform independent) filename/filepath sanitization/validation - Treat ASCII whitespace other than normal space as invalid on Windows [#6](https://github.com/thombashi/pathvalidate/issues/6) (Thanks to [@nyuszika7h](https://github.com/nyuszika7h)) [Changes][v0.22.0] # [v0.21.1](https://github.com/thombashi/pathvalidate/releases/tag/v0.21.1) - 2018-07-28 - Add support for PathLike object - Bug fixes [Changes][v0.21.1] # [v0.18.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.18.0) - 2018-07-07 - Add validations/sanitizations for unprintable characters - Add support for Python 3.7 [Changes][v0.18.0] # [v0.15.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.15.0) - 2017-03-18 - Remove package dependencies - pathvalidate functions are expected to passing unicode strings. [Changes][v0.15.0] # [v0.14.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.14.0) - 2017-02-11 - Change max file name/path length to configurable - Add support for Python 3.6 - Bug fixes [Changes][v0.14.0] # [v0.13.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.13.0) - 2017-01-03 - Add JavaScript validator/sanitizer - Bug fixes [Changes][v0.13.0] # [v0.11.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.11.0) - 2016-12-25 - Add multibyte character validate/sanitize support [Changes][v0.11.0] # [v0.10.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.10.0) - 2016-12-23 - Add LTSV support [Changes][v0.10.0] # [v0.9.1](https://github.com/thombashi/pathvalidate/releases/tag/v0.9.1) - 2016-11-17 - Support UTF8 [Changes][v0.9.1] # [v0.9.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.9.0) - 2016-11-13 - Add validate_symbol function [Changes][v0.9.0] # [v0.8.2](https://github.com/thombashi/pathvalidate/releases/tag/v0.8.2) - 2016-10-27 - Fix Windows path validation - Bug fixes [Changes][v0.8.2] # [v0.6.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.6.0) - 2016-09-19 - Add SQLite name validation function [Changes][v0.6.0] # [v0.5.2](https://github.com/thombashi/pathvalidate/releases/tag/v0.5.2) - 2016-08-20 - Fix validate/sanitize of excel sheet - Subdividing errors - Add file name validation for Windows reserved names and path length [Changes][v0.5.2] # [v0.5.1](https://github.com/thombashi/pathvalidate/releases/tag/v0.5.1) - 2016-07-23 - Modify error handling [Changes][v0.5.1] # [v0.5.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.5.0) - 2016-07-17 - Drop support for Python 2.6 - Add validate_excel_sheet_name function - Add sanitize_excel_sheet_name function [Changes][v0.5.0] # [v0.4.2](https://github.com/thombashi/pathvalidate/releases/tag/v0.4.2) - 2016-06-19 - Make pytest-runner a conditional requirement [Changes][v0.4.2] # [v0.4.1](https://github.com/thombashi/pathvalidate/releases/tag/v0.4.1) - 2016-05-29 - Modify replace_symbol function behavior [Changes][v0.4.1] # [v0.4.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.4.0) - 2016-05-28 - Add validate_file_path/sanitize_file_path functions - Fix validate/sanitize filename functions - Fix validate/sanitize python variable name functions [Changes][v0.4.0] # [v0.3.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.3.0) - 2016-05-22 - Add validate_python_var_name function [Changes][v0.3.0] # [v0.2.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.2.0) - 2016-05-21 - Add sanitize_python_var_name function [Changes][v0.2.0] # [v0.1.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.1.0) - 2016-03-24 [Changes][v0.1.0] [v3.2.2]: https://github.com/thombashi/pathvalidate/compare/v3.2.1...v3.2.2 [v3.2.1]: https://github.com/thombashi/pathvalidate/compare/v3.2.0...v3.2.1 [v3.2.0]: https://github.com/thombashi/pathvalidate/compare/v3.1.0...v3.2.0 [v3.1.0]: https://github.com/thombashi/pathvalidate/compare/v3.0.0...v3.1.0 [v3.0.0]: https://github.com/thombashi/pathvalidate/compare/v2.5.2...v3.0.0 [v2.5.2]: https://github.com/thombashi/pathvalidate/compare/v2.5.1...v2.5.2 [v2.5.1]: https://github.com/thombashi/pathvalidate/compare/v2.5.0...v2.5.1 [v2.5.0]: https://github.com/thombashi/pathvalidate/compare/v2.4.1...v2.5.0 [v2.4.1]: https://github.com/thombashi/pathvalidate/compare/v2.4.0...v2.4.1 [v2.4.0]: https://github.com/thombashi/pathvalidate/compare/v2.3.2...v2.4.0 [v2.3.2]: https://github.com/thombashi/pathvalidate/compare/v2.3.1...v2.3.2 [v2.3.1]: https://github.com/thombashi/pathvalidate/compare/v2.3.0...v2.3.1 [v2.3.0]: https://github.com/thombashi/pathvalidate/compare/v2.2.2...v2.3.0 [v2.2.2]: https://github.com/thombashi/pathvalidate/compare/v2.2.1...v2.2.2 [v2.2.1]: https://github.com/thombashi/pathvalidate/compare/v2.2.0...v2.2.1 [v2.2.0]: https://github.com/thombashi/pathvalidate/compare/v2.1.0...v2.2.0 [v2.1.0]: https://github.com/thombashi/pathvalidate/compare/v2.0.0...v2.1.0 [v2.0.0]: https://github.com/thombashi/pathvalidate/compare/v1.1.0...v2.0.0 [v1.1.0]: https://github.com/thombashi/pathvalidate/compare/v1.0.0...v1.1.0 [v1.0.0]: https://github.com/thombashi/pathvalidate/compare/v0.29.1...v1.0.0 [v0.29.1]: https://github.com/thombashi/pathvalidate/compare/v0.29.0...v0.29.1 [v0.29.0]: https://github.com/thombashi/pathvalidate/compare/v0.28.2...v0.29.0 [v0.28.2]: https://github.com/thombashi/pathvalidate/compare/v0.28.0...v0.28.2 [v0.28.0]: https://github.com/thombashi/pathvalidate/compare/v0.26.0...v0.28.0 [v0.26.0]: https://github.com/thombashi/pathvalidate/compare/v0.25.0...v0.26.0 [v0.25.0]: https://github.com/thombashi/pathvalidate/compare/v0.24.1...v0.25.0 [v0.24.1]: https://github.com/thombashi/pathvalidate/compare/v0.24.0...v0.24.1 [v0.24.0]: https://github.com/thombashi/pathvalidate/compare/v0.23.0...v0.24.0 [v0.23.0]: https://github.com/thombashi/pathvalidate/compare/v0.22.0...v0.23.0 [v0.22.0]: https://github.com/thombashi/pathvalidate/compare/v0.21.1...v0.22.0 [v0.21.1]: https://github.com/thombashi/pathvalidate/compare/v0.18.0...v0.21.1 [v0.18.0]: https://github.com/thombashi/pathvalidate/compare/v0.15.0...v0.18.0 [v0.15.0]: https://github.com/thombashi/pathvalidate/compare/v0.14.0...v0.15.0 [v0.14.0]: https://github.com/thombashi/pathvalidate/compare/v0.13.0...v0.14.0 [v0.13.0]: https://github.com/thombashi/pathvalidate/compare/v0.11.0...v0.13.0 [v0.11.0]: https://github.com/thombashi/pathvalidate/compare/v0.10.0...v0.11.0 [v0.10.0]: https://github.com/thombashi/pathvalidate/compare/v0.9.1...v0.10.0 [v0.9.1]: https://github.com/thombashi/pathvalidate/compare/v0.9.0...v0.9.1 [v0.9.0]: https://github.com/thombashi/pathvalidate/compare/v0.8.2...v0.9.0 [v0.8.2]: https://github.com/thombashi/pathvalidate/compare/v0.6.0...v0.8.2 [v0.6.0]: https://github.com/thombashi/pathvalidate/compare/v0.5.2...v0.6.0 [v0.5.2]: https://github.com/thombashi/pathvalidate/compare/v0.5.1...v0.5.2 [v0.5.1]: https://github.com/thombashi/pathvalidate/compare/v0.5.0...v0.5.1 [v0.5.0]: https://github.com/thombashi/pathvalidate/compare/v0.4.2...v0.5.0 [v0.4.2]: https://github.com/thombashi/pathvalidate/compare/v0.4.1...v0.4.2 [v0.4.1]: https://github.com/thombashi/pathvalidate/compare/v0.4.0...v0.4.1 [v0.4.0]: https://github.com/thombashi/pathvalidate/compare/v0.3.0...v0.4.0 [v0.3.0]: https://github.com/thombashi/pathvalidate/compare/v0.2.0...v0.3.0 [v0.2.0]: https://github.com/thombashi/pathvalidate/compare/v0.1.0...v0.2.0 [v0.1.0]: https://github.com/thombashi/pathvalidate/tree/v0.1.0 pathvalidate-3.2.3/LICENSE000066400000000000000000000021011473576712500152050ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2016-2025 Tsuyoshi Hombashi Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pathvalidate-3.2.3/MANIFEST.in000066400000000000000000000003401473576712500157410ustar00rootroot00000000000000include LICENSE include README.rst include tox.ini include docs/pages/introduction/summary.txt include */py.typed recursive-include test * recursive-include requirements * global-exclude __pycache__/* global-exclude *.pyc pathvalidate-3.2.3/Makefile000066400000000000000000000033241473576712500156500ustar00rootroot00000000000000BIN_DIR := $(shell pwd)/bin PYTHON := python3 BIN_CHANGELOG_FROM_RELEASE := $(BIN_DIR)/changelog-from-release AUTHOR := Tsuyoshi Hombashi FIRST_RELEASE_YEAR := 2016 LAST_UPDATE_YEAR := $(shell git log -1 --format=%cd --date=format:%Y) $(BIN_CHANGELOG_FROM_RELEASE): mkdir -p $(BIN_DIR) GOBIN=$(BIN_DIR) go install github.com/rhysd/changelog-from-release/v3@latest .PHONY: build build: clean @$(PYTHON) -m tox -e build ls -lh dist/* .PHONY: changelog changelog: $(BIN_CHANGELOG_FROM_RELEASE) $(BIN_CHANGELOG_FROM_RELEASE) > CHANGELOG.md cp -a CHANGELOG.md docs/pages/CHANGELOG.md .PHONY: check check: $(PYTHON) -m tox -e lint -$(PYTHON) -m tox -e lint-examples -rm examples/pathvalidate_examples.py .PHONY: clean clean: rm -rf $(BIN_DIR) $(PYTHON) -m tox -e clean .PHONY: docs docs: @$(PYTHON) -m tox -e docs .PHONY: idocs idocs: @$(PYTHON) -m pip install -q --disable-pip-version-check --upgrade -e . @$(MAKE) docs .PHONY: fmt fmt: @$(PYTHON) -m tox -e fmt .PHONY: readme readme: @$(PYTHON) -m tox -e readme .PHONY: release release: $(PYTHON) -m tox -e release $(MAKE) clean .PHONY: setup-ci setup-ci: $(PYTHON) -m pip install -q --disable-pip-version-check --upgrade pip $(PYTHON) -m pip install -q --disable-pip-version-check --upgrade tox .PHONY: setup-dev setup-dev: setup-ci $(PYTHON) -m pip install -q --disable-pip-version-check --upgrade -e .[test] $(PYTHON) -m pip check .PHONY: test test: $(PYTHON) -m tox -e py .PHONY: update-copyright update-copyright: sed -i "s/f\"Copyright .*/f\"Copyright $(FIRST_RELEASE_YEAR)-$(LAST_UPDATE_YEAR), {__author__}\"/" pathvalidate/__version__.py sed -i "s/^Copyright (c) .* $(AUTHOR)/Copyright (c) $(FIRST_RELEASE_YEAR)-$(LAST_UPDATE_YEAR) $(AUTHOR)/" LICENSE pathvalidate-3.2.3/README.rst000066400000000000000000000240361473576712500157020ustar00rootroot00000000000000.. contents:: **pathvalidate** :backlinks: top :depth: 2 Summary ========= `pathvalidate `__ is a Python library to sanitize/validate a string such as filenames/file-paths/etc. |PyPI pkg ver| |conda pkg ver| |Supported Python ver| |Supported Python impl| |CI status| |Test coverage| |CodeQL| .. |PyPI pkg ver| image:: https://badge.fury.io/py/pathvalidate.svg :target: https://badge.fury.io/py/pathvalidate :alt: PyPI package version .. |conda pkg ver| image:: https://anaconda.org/conda-forge/pathvalidate/badges/version.svg :target: https://anaconda.org/conda-forge/pathvalidate :alt: conda package version .. |Supported Python ver| image:: https://img.shields.io/pypi/pyversions/pathvalidate.svg :target: https://pypi.org/project/pathvalidate :alt: Supported Python versions .. |Supported Python impl| image:: https://img.shields.io/pypi/implementation/pathvalidate.svg :target: https://pypi.org/project/pathvalidate :alt: Supported Python implementations .. |CI status| image:: https://github.com/thombashi/pathvalidate/actions/workflows/ci.yml/badge.svg :target: https://github.com/thombashi/pathvalidate/actions/workflows/ci.yml :alt: CI status of Linux/macOS/Windows .. |Test coverage| image:: https://coveralls.io/repos/github/thombashi/pathvalidate/badge.svg?branch=master :target: https://coveralls.io/github/thombashi/pathvalidate?branch=master :alt: Test coverage: coveralls .. |CodeQL| image:: https://github.com/thombashi/pathvalidate/actions/workflows/github-code-scanning/codeql/badge.svg :target: https://github.com/thombashi/pathvalidate/actions/workflows/github-code-scanning/codeql :alt: CodeQL Features --------- - Sanitize/Validate a string as a: - file name - file path - Sanitize will do: - Remove invalid characters for a target platform - Replace reserved names for a target platform - Normalize - Remove unprintable characters - Argument validator/sanitizer for ``argparse`` and ``click`` - Multi platform support: - ``Linux`` - ``Windows`` - ``macOS`` - ``POSIX``: POSIX-compliant systems (Linux, macOS, etc.) - ``universal``: platform independent - Multibyte character support CLI tool --------- You can find this package's command line interface tool at the `pathvalidate-cli `__ repository. Examples ========== Sanitize a filename --------------------- :Sample Code: .. code-block:: python from pathvalidate import sanitize_filename fname = "fi:l*e/p\"a?t>h|.t {sanitize_filename(fname)}\n") fname = "\0_a*b:ce%f/(g)h+i_0.txt" print(f"{fname} -> {sanitize_filename(fname)}\n") :Output: .. code-block:: fi:l*e/p"a?t>h|.t filepath.txt _a*b:ce%f/(g)h+i_0.txt -> _abcde%f(g)h+i_0.txt The default target ``platform`` is ``universal``. i.e. the sanitized file name is valid for any platform. Sanitize a filepath --------------------- :Sample Code: .. code-block:: python from pathvalidate import sanitize_filepath fpath = "fi:l*e/p\"a?t>h|.t {sanitize_filepath(fpath)}\n") fpath = "\0_a*b:ce%f/(g)h+i_0.txt" print(f"{fpath} -> {sanitize_filepath(fpath)}\n") :Output: .. code-block:: fi:l*e/p"a?t>h|.t file/path.txt _a*b:ce%f/(g)h+i_0.txt -> _abcde%f/(g)h+i_0.txt Validate a filename --------------------- :Sample Code: .. code-block:: python import sys from pathvalidate import ValidationError, validate_filename try: validate_filename("fi:l*e/p\"a?t>h|.th|.th|.t None: if filename: click.echo(f"filename: {filename}") if filepath: click.echo(f"filepath: {filepath}") if __name__ == "__main__": cli() :Output: .. code-block:: $ ./examples/click_validate.py --filename ab filename: ab $ ./examples/click_validate.py --filepath e?g Usage: click_validate.py [OPTIONS] Try 'click_validate.py --help' for help. Error: Invalid value for '--filename': [PV1100] invalid characters found: invalids=('?'), value='e?g', platform=Windows filename/filepath sanitizer for ``click`` ------------------------------------------- :Sample Code: .. code-block:: python import click from pathvalidate.click import sanitize_filename_arg, sanitize_filepath_arg @click.command() @click.option("--filename", callback=sanitize_filename_arg) @click.option("--filepath", callback=sanitize_filepath_arg) def cli(filename, filepath): if filename: click.echo(f"filename: {filename}") if filepath: click.echo(f"filepath: {filepath}") if __name__ == "__main__": cli() :Output: .. code-block:: $ ./examples/click_sanitize.py --filename a/b filename: ab For more information ---------------------- More examples can be found at https://pathvalidate.rtfd.io/en/latest/pages/examples/index.html Installation ============ Installation: pip ------------------------------ :: pip install pathvalidate Installation: conda ------------------------------ :: conda install conda-forge::pathvalidate Installation: apt ------------------------------ :: sudo add-apt-repository ppa:thombashi/ppa sudo apt update sudo apt install python3-pathvalidate Dependencies ============ Python 3.9+ no external dependencies. Documentation =============== https://pathvalidate.rtfd.io/ Sponsors ==================================== |chasbecker| |shiguredo| |b4tman| |Arturi0| |github| .. |chasbecker| image:: https://avatars.githubusercontent.com/u/44389260?s=48&u=6da7176e51ae2654bcfd22564772ef8a3bb22318&v=4 :target: https://github.com/chasbecker :alt: ex-sponsor: Charles Becker (chasbecker) .. |shiguredo| image:: https://avatars.githubusercontent.com/u/2549434?s=48&v=4 :target: https://github.com/shiguredo :alt: ex-sponsor: 時雨堂 (shiguredo) .. |b4tman| image:: https://avatars.githubusercontent.com/u/3658062?s=48&v=4 :target: https://github.com/b4tman :alt: onetime: Dmitry Belyaev (b4tman) .. |Arturi0| image:: https://avatars.githubusercontent.com/u/46711571?s=48&u=57687c0e02d5d6e8eeaf9177f7b7af4c9f275eb5&v=4 :target: https://github.com/Arturi0 :alt: onetime: Arturi0 .. |github| image:: https://avatars.githubusercontent.com/u/9919?s=48&v=4 :target: https://github.com/github :alt: onetime: GitHub (github) `Become a sponsor `__ pathvalidate-3.2.3/docs/000077500000000000000000000000001473576712500151365ustar00rootroot00000000000000pathvalidate-3.2.3/docs/Makefile000066400000000000000000000176301473576712500166050ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don\'t have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " applehelp to make an Apple Help Book" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " epub3 to make an epub3" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" @echo " coverage to run coverage check of the documentation (if enabled)" @echo " dummy to check syntax errors of document sources" .PHONY: clean clean: rm -rf $(BUILDDIR)/* .PHONY: html html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." .PHONY: dirhtml dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." .PHONY: singlehtml singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." .PHONY: pickle pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." .PHONY: json json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." .PHONY: htmlhelp htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." .PHONY: qthelp qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pathvalidate.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pathvalidate.qhc" .PHONY: applehelp applehelp: $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp @echo @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." @echo "N.B. You won't be able to view it unless you put it in" \ "~/Library/Documentation/Help or install it in your application" \ "bundle." .PHONY: devhelp devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/pathvalidate" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pathvalidate" @echo "# devhelp" .PHONY: epub epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." .PHONY: epub3 epub3: $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 @echo @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." .PHONY: latex latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." .PHONY: latexpdf latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: latexpdfja latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: text text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." .PHONY: man man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." .PHONY: texinfo texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." .PHONY: info info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." .PHONY: gettext gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." .PHONY: changes changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." .PHONY: linkcheck linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." .PHONY: doctest doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." .PHONY: coverage coverage: $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage @echo "Testing of coverage in the sources finished, look at the " \ "results in $(BUILDDIR)/coverage/python.txt." .PHONY: xml xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." .PHONY: pseudoxml pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." .PHONY: dummy dummy: $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy @echo @echo "Build finished. Dummy builder generates no files." pathvalidate-3.2.3/docs/conf.py000066400000000000000000000235211473576712500164400ustar00rootroot00000000000000import os import sys import sphinx_rtd_theme from pathvalidate import __author__, __copyright__, __name__, __version__ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath('../pathvalidate')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.viewcode', 'sphinx.ext.napoleon', ] intersphinx_mapping = {'python': ('https://docs.python.org/', None)} # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = __name__ copyright = __copyright__ author = __author__ # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = __version__ # The full version, including alpha/beta/rc tags. release = version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = 'en' # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # The reST default role (used for this markup: `text`) to use for all # documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # The name for this set of Sphinx documents. # " v documentation" by default. #html_title = u'pathvalidate v0.1.0' # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (relative to this directory) to use as a favicon of # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. #html_extra_path = [] # If not None, a 'Last updated on:' timestamp is inserted at every page # bottom, using the given strftime format. # The empty string is equivalent to '%b %d, %Y'. #html_last_updated_fmt = None # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' #html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # 'ja' uses this config value. # 'zh' user can custom change `jieba` dictionary path. #html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. #html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. htmlhelp_basename = 'pathvalidatedoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', # Latex figure (float) alignment #'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'pathvalidate.tex', 'pathvalidate Documentation', __author__, 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'pathvalidate', 'pathvalidate Documentation', [author], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'pathvalidate', 'pathvalidate Documentation', author, 'pathvalidate', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False rst_prolog = r""" .. |False| replace:: :py:obj:`False` .. |True| replace:: :py:obj:`True` .. |None| replace:: :py:obj:`None` .. |invalid_file_path_chars| replace:: ``\\0`` .. |invalid_win_file_path_chars| replace:: ``:``, ``*``, ``?``, ``"``, ``<``, ``>``, ``|``, ``\t``, ``\n``, ``\r``, ``\x0b``, ``\x0c`` .. |invalid_filename_chars| replace:: ``/``, ``\\0`` .. |invalid_win_filename_chars| replace:: ``\``, ``:``, ``*``, ``?``, ``"``, ``<``, ``>``, ``|``, ``\t``, ``\n``, ``\r``, ``\x0b``, ``\x0c`` .. |invalid_excel_sheet_chars| replace:: ``[``, ``]``, ``:``, ``*``, ``?``, ``/``, ``\`` .. |raises_sqlite_keywords| replace:: If the ``name`` is equals to `SQLite Keywords `__. """ pathvalidate-3.2.3/docs/index.rst000066400000000000000000000011211473576712500167720ustar00rootroot00000000000000Welcome to pathvalidate's documentation! ======================================== .. raw:: html

.. toctree:: :caption: Table of Contents :maxdepth: 3 :numbered: pages/introduction/index pages/examples/index pages/reference/index pages/changelog_ref pages/links Indices and tables ================== * :ref:`genindex` pathvalidate-3.2.3/docs/make.bat000066400000000000000000000164471473576712500165570ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. epub3 to make an epub3 echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. xml to make Docutils-native XML files echo. pseudoxml to make pseudoxml-XML files for display purposes echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled echo. coverage to run coverage check of the documentation if enabled echo. dummy to check syntax errors of document sources goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) REM Check if sphinx-build is available and fallback to Python version if any %SPHINXBUILD% 1>NUL 2>NUL if errorlevel 9009 goto sphinx_python goto sphinx_ok :sphinx_python set SPHINXBUILD=python -m sphinx.__init__ %SPHINXBUILD% 2> nul if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) :sphinx_ok if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\pathvalidate.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\pathvalidate.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "epub3" ( %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdf" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf cd %~dp0 echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdfja" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf-ja cd %~dp0 echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) if "%1" == "coverage" ( %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage if errorlevel 1 exit /b 1 echo. echo.Testing of coverage in the sources finished, look at the ^ results in %BUILDDIR%/coverage/python.txt. goto end ) if "%1" == "xml" ( %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml if errorlevel 1 exit /b 1 echo. echo.Build finished. The XML files are in %BUILDDIR%/xml. goto end ) if "%1" == "pseudoxml" ( %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml if errorlevel 1 exit /b 1 echo. echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. goto end ) if "%1" == "dummy" ( %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy if errorlevel 1 exit /b 1 echo. echo.Build finished. Dummy builder generates no files. goto end ) :end pathvalidate-3.2.3/docs/make_readme.py000066400000000000000000000046341473576712500177510ustar00rootroot00000000000000#!/usr/bin/env python3 """ .. codeauthor:: Tsuyoshi Hombashi """ import sys from path import Path from readmemaker import ReadmeMaker PROJECT_NAME = "pathvalidate" OUTPUT_DIR = ".." def write_examples(maker: ReadmeMaker) -> None: maker.set_indent_level(0) maker.write_chapter("Examples") example_root = Path("pages").joinpath("examples") maker.inc_indent_level() maker.write_chapter("Sanitize a filename") maker.write_file(example_root.joinpath("sanitize_filename_code.txt")) maker.write_chapter("Sanitize a filepath") maker.write_file(example_root.joinpath("sanitize_filepath_code.txt")) maker.write_chapter("Validate a filename") maker.write_file(example_root.joinpath("validate_filename_code.txt")) maker.write_chapter("Check a filename") maker.write_file(example_root.joinpath("is_valid_filename_code.txt")) maker.write_chapter("filename/filepath validator for ``argparse``") maker.write_file(example_root.joinpath("argparse_validator.txt")) maker.write_chapter("filename/filepath sanitizer for ``argparse``") maker.write_file(example_root.joinpath("argparse_sanitizer.txt")) maker.write_chapter("filename/filepath validator for ``click``") maker.write_file(example_root.joinpath("click_validator.txt")) maker.write_chapter("filename/filepath sanitizer for ``click``") maker.write_file(example_root.joinpath("click_sanitizer.txt")) maker.write_chapter("For more information") maker.write_lines( [ "More examples can be found at ", f"https://{PROJECT_NAME}.rtfd.io/en/latest/pages/examples/index.html", ] ) def main() -> int: maker = ReadmeMaker( PROJECT_NAME, OUTPUT_DIR, is_make_toc=True, project_url=f"https://github.com/thombashi/{PROJECT_NAME}", ) maker.write_chapter("Summary") maker.write_introduction_file("summary.txt") maker.write_introduction_file("badges.txt") maker.write_introduction_file("feature.txt") maker.write_introduction_file("cli.txt") write_examples(maker) maker.write_introduction_file("installation.rst") maker.set_indent_level(0) maker.write_chapter("Documentation") maker.write_lines([f"https://{PROJECT_NAME}.rtfd.io/"]) maker.write_file(maker.doc_page_root_dir_path.joinpath("sponsors.rst")) return 0 if __name__ == "__main__": sys.exit(main()) pathvalidate-3.2.3/docs/pages/000077500000000000000000000000001473576712500162355ustar00rootroot00000000000000pathvalidate-3.2.3/docs/pages/CHANGELOG.md000066400000000000000000000537501473576712500200600ustar00rootroot00000000000000 # [v3.2.2](https://github.com/thombashi/pathvalidate/releases/tag/v3.2.2) - 2025-01-01 - Fix the detection logic of the reservation words for the file name on Windows: [#57](https://github.com/thombashi/pathvalidate/issues/57) (Thanks to [@jplarocque](https://github.com/jplarocque)) - Drop support for Python 3.7/3.8 - Refactor type annotations **Full Changelog**: https://github.com/thombashi/pathvalidate/compare/v3.2.1...v3.2.2 [Changes][v3.2.2] # [v3.2.1](https://github.com/thombashi/pathvalidate/releases/tag/v3.2.1) - 2024-08-23 ## What's Changed * Test Python 3.12 in CIs by [@MatthieuDartiailh](https://github.com/MatthieuDartiailh) in [#40](https://github.com/thombashi/pathvalidate/pull/40) * Bump actions/setup-python from 4 to 5 by [@dependabot](https://github.com/dependabot) in [#36](https://github.com/thombashi/pathvalidate/pull/36) * Bump actions/upload-artifact from 3 to 4 by [@dependabot](https://github.com/dependabot) in [#38](https://github.com/thombashi/pathvalidate/pull/38) * Bump actions/download-artifact from 3 to 4 by [@dependabot](https://github.com/dependabot) in [#37](https://github.com/thombashi/pathvalidate/pull/37) * Fix CI by [@thombashi](https://github.com/thombashi) in [#41](https://github.com/thombashi/pathvalidate/pull/41) * Update the CI workflow to include a job that publishes packages to TestPyPI by [@thombashi](https://github.com/thombashi) in [#42](https://github.com/thombashi/pathvalidate/pull/42) * Fix coverage report by [@thombashi](https://github.com/thombashi) in [#45](https://github.com/thombashi/pathvalidate/pull/45) * Fix `sanitize_filename` truncation by [@7x11x13](https://github.com/7x11x13) in [#48](https://github.com/thombashi/pathvalidate/pull/48) * Fix validation functions of filepaths by [@thombashi](https://github.com/thombashi) in [#55](https://github.com/thombashi/pathvalidate/pull/55) - If `platform` argument is `windows` or `universal`, filepaths ending with a space or a period should be detected as an error - Fix POSIX-style absolute paths were not detected as errors with `platform="windows"` or `platform="universal"` on Python 3.12 and below * Add support for Python 3.13 by [@thombashi](https://github.com/thombashi) in [#56](https://github.com/thombashi/pathvalidate/pull/56) * Improve type annotations * Add a build and publish workflow * Add Sigstore signatures to release assets * Update copyright year to include the last update year: [#54](https://github.com/thombashi/pathvalidate/issues/54) (Thanks to [@Flimm](https://github.com/Flimm)) * Add CHANGELOG ## New Contributors * [@MatthieuDartiailh](https://github.com/MatthieuDartiailh) made their first contribution in [#40](https://github.com/thombashi/pathvalidate/pull/40) * [@dependabot](https://github.com/dependabot) made their first contribution in [#36](https://github.com/thombashi/pathvalidate/pull/36) * [@7x11x13](https://github.com/7x11x13) made their first contribution in [#48](https://github.com/thombashi/pathvalidate/pull/48) **Full Changelog**: https://github.com/thombashi/pathvalidate/compare/v3.2.0...v3.2.1 [Changes][v3.2.1] # [v3.2.0](https://github.com/thombashi/pathvalidate/releases/tag/v3.2.0) - 2023-09-17 - Add `reserved_name_handler` argument to `sanitize_filename` function and `sanitize_filepath` function - Add `NullValueHandler` class and `ReservedNameHandler` class - Add `fs_encoding` property and `byte_count` property to `ValidationError` class - Add `additional_reserved_names` argument to validate/sanitize functions to allow custom reserved names - Modify the return value format of `ValidationError.__str__` method - Improve type annotations **Full Changelog**: https://github.com/thombashi/pathvalidate/compare/v3.1.0...v3.2.0 [Changes][v3.2.0] # [v3.1.0](https://github.com/thombashi/pathvalidate/releases/tag/v3.1.0) - 2023-07-16 - Fix validation error messages to show the `target-platform` properly: [#34](https://github.com/thombashi/pathvalidate/issues/34) (Thanks to [@matanster](https://github.com/matanster)) - Fix README: out of date with the actual error generated by `validate_filename` [#35](https://github.com/thombashi/pathvalidate/issues/35) (Thanks to [@hXtreme](https://github.com/hXtreme)) - Add `description` property to `ErrorReason` class - Add `as_slog` method to `ValidationError` class - Add `docs` extras - Change the type of the return value of `ValidationError.reason` from `Optional[ErrorReason]` to `ErrorReason` - Update `[build-system]` - Drop support for Python 3.6 **Full Changelog**: https://github.com/thombashi/pathvalidate/compare/v3.0.0...v3.1.0 [Changes][v3.1.0] # [v3.0.0](https://github.com/thombashi/pathvalidate/releases/tag/v3.0.0) - 2023-05-22 ## What's Changed - Trim heading spaces in Windows by [@eggplants](https://github.com/eggplants) in [#28](https://github.com/thombashi/pathvalidate/pull/28) - Consider filesystem encoding for length calculations: [#26](https://github.com/thombashi/pathvalidate/issues/26) (Thanks to [@virlos](https://github.com/virlos)) - Fix type model: [#29](https://github.com/thombashi/pathvalidate/issues/29) (Thanks to [@rogalski](https://github.com/rogalski)) - Fix sanitizing of filenames that only consist of whitespaces and periods - Add `validate_unprintable_char` function - Add `validate_after_sanitize` keyword argument to `sanitize_filename` and `sanitize_filepath` functions - Add error codes to `ErrorReason` - Add `zip_safe=False` to `setup` - Modify to accept `pathvalidate.Platform` type as `platform` arguments - Rename type alias from `Handler` to `NullValueHandler` - Remove `InvalidLengthError` to use `ValidationError` - Improve type annotations - Make it possible to import `FileNameValidator` and `FilePathValidator` classes from the package root - Change constructor arguments of `FileNameSanitizer` and `FilePathSanitizer` classes - Remove `min_len` - Add `validator` - Remove deprecated functions - Modify error messages - Refactoring **Full Changelog**: https://github.com/thombashi/pathvalidate/compare/v2.5.2...v3.0.0 [Changes][v3.0.0] # [v2.5.2](https://github.com/thombashi/pathvalidate/releases/tag/v2.5.2) - 2022-08-20 - Add support for Python 3.11: [#22](https://github.com/thombashi/pathvalidate/issues/22) (Thanks to [@hegjon](https://github.com/hegjon)) **Full Changelog**: https://github.com/thombashi/pathvalidate/compare/v2.5.1...v2.5.2 [Changes][v2.5.2] # [v2.5.1](https://github.com/thombashi/pathvalidate/releases/tag/v2.5.1) - 2022-07-31 ## What's Changed * Add `__all__` by [@eggplants](https://github.com/eggplants) in [#24](https://github.com/thombashi/pathvalidate/pull/24) * Add `DeprecationWarning` to deprecated functions ## New Contributors * [@eggplants](https://github.com/eggplants) made their first contribution in [#24](https://github.com/thombashi/pathvalidate/pull/24) **Full Changelog**: https://github.com/thombashi/pathvalidate/compare/v2.5.0...v2.5.1 [Changes][v2.5.1] # [v2.5.0](https://github.com/thombashi/pathvalidate/releases/tag/v2.5.0) - 2021-09-26 - Add support for Python 3.10 - Drop support for Python 3.5 - Add `null_value_handler` argument to `sanitize_filename`/`sanitize_filepath` functions: [#20](https://github.com/thombashi/pathvalidate/issues/20) (Thanks to @ mkbloke) - Add `AbstractSanitizer`/`AbstractValidator` classes to import path - Add `replace_ansi_escape` function - Add `setup-ci` target to `Makefile` - Modify `min_len`/`max_len` to use default values when assigned minus values [Changes][v2.5.0] # [v2.4.1](https://github.com/thombashi/pathvalidate/releases/tag/v2.4.1) - 2021-04-03 - Fix filename validations that include `'\'` (backslash) on other than Windows: [#18](https://github.com/thombashi/pathvalidate/issues/18) (Thanks to [@Traktormaster](https://github.com/Traktormaster)) [Changes][v2.4.1] # [v2.4.0](https://github.com/thombashi/pathvalidate/releases/tag/v2.4.0) - 2021-03-21 - Add `exclude_symbols` argument to `replace_symbol` function - Fix permissions of files included in `sdist` package binary (Thanks to [@hegjon](https://github.com/hegjon)) [Changes][v2.4.0] # [v2.3.2](https://github.com/thombashi/pathvalidate/releases/tag/v2.3.2) - 2021-01-03 - Fix to disallow file name/path that only white spaces for `universal` platform [Changes][v2.3.2] # [v2.3.1](https://github.com/thombashi/pathvalidate/releases/tag/v2.3.1) - 2020-12-13 - Modify to accept file name/path that consists only whitespaces: [#15](https://github.com/thombashi/pathvalidate/issues/15) (Thank to [@Traktormaster](https://github.com/Traktormaster)) [Changes][v2.3.1] # [v2.3.0](https://github.com/thombashi/pathvalidate/releases/tag/v2.3.0) - 2020-05-03 - Change not to process for `"."`/`".."` by sanitization functions: [#13](https://github.com/thombashi/pathvalidate/issues/13) (Thanks to [@ProfElectric](https://github.com/ProfElectric)) - Change to normalize with `sanitize_filepath` in default - Add normalize interface to `sanitize_filepath` [Changes][v2.3.0] # [v2.2.2](https://github.com/thombashi/pathvalidate/releases/tag/v2.2.2) - 2020-03-28 - Improve file path validation for Windows platform: [#12](https://github.com/thombashi/pathvalidate/issues/12) (Thanks to [@bschollnick](https://github.com/bschollnick)) - Fix `__str__` method - Fix to avoid raise an exception when an absolute path includes `"."`/`".."` - Modify an error message - Modify raising exception from `NullNameError` to `ValidationError` of `validate_pathtype` [Changes][v2.2.2] # [v2.2.1](https://github.com/thombashi/pathvalidate/releases/tag/v2.2.1) - 2020-03-20 - Fix to include `py.typed` to the package [Changes][v2.2.1] # [v2.2.0](https://github.com/thombashi/pathvalidate/releases/tag/v2.2.0) - 2020-02-12 - Add `POSIX` as a platform - Add a reserved keyword for macOS - Change platform of `validate_filepath_arg`/`sanitize_filepath_arg` to `'auto'`: [#11](https://github.com/thombashi/pathvalidate/issues/11) (Thanks to [@freelanceAndy](https://github.com/freelanceAndy)) [Changes][v2.2.0] # [v2.1.0](https://github.com/thombashi/pathvalidate/releases/tag/v2.1.0) - 2020-02-01 - Add `check_reserved` argument to validate/sanitize functions - Add `'/'` as a reserved file path for Linux/macOS - Suppress errors when sanitizing null values - Fix `max_len` value check for file names - Include type annotation information to the package - Remove `dev` extras_require - Bug fixes [Changes][v2.1.0] # [v2.0.0](https://github.com/thombashi/pathvalidate/releases/tag/v2.0.0) - 2020-01-13 - Change to be more strict validation for absolute paths - Fix argparse validator/sanitizer failed when empty inputs - Bug fixes [Changes][v2.0.0] # [v1.1.0](https://github.com/thombashi/pathvalidate/releases/tag/v1.1.0) - 2020-01-04 - Modify validate/sanitize functions for `argparse` - Modify validate/sanitize functions for `click` - Update `dev` extras [Changes][v1.1.0] # [v1.0.0](https://github.com/thombashi/pathvalidate/releases/tag/v1.0.0) - 2020-01-03 - Drop Python 2 support - Modify to use Python 3 functionality - Update `extras_require` - Bug fixes - Remove a deprecated property [Changes][v1.0.0] # [v0.29.1](https://github.com/thombashi/pathvalidate/releases/tag/v0.29.1) - 2020-01-02 - Fix file path length validation: [#10](https://github.com/thombashi/pathvalidate/issues/10) (Thanks to [@UncleGoogle](https://github.com/UncleGoogle)) - Add `.asc` files of packages to PyPI [Changes][v0.29.1] # [v0.29.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.29.0) - 2019-06-16 - Add filename/filepath validators for `argparse`/`click` - Modify error messages [Changes][v0.29.0] # [v0.28.2](https://github.com/thombashi/pathvalidate/releases/tag/v0.28.2) - 2019-05-18 - Fix to properly escape special chars for validation error messages: [#9](https://github.com/thombashi/pathvalidate/issues/9) (Thanks to [@UncleGoogle](https://github.com/UncleGoogle)) [Changes][v0.28.2] # [v0.28.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.28.0) - 2019-05-01 - Drop support for Python 3.4 [Changes][v0.28.0] # [v0.26.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.26.0) - 2019-03-15 - Add support for NTFS reserved names - Improve drive letter handling [Changes][v0.26.0] # [v0.25.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.25.0) - 2019-03-14 - Add `CLOCK$` as a reserved filename for Windows platform: [#8](https://github.com/thombashi/pathvalidate/issues/8) (Thanks to [@sparr](https://github.com/sparr)) - Improve reserved name detection - Add `reserved_name` property to `ReservedNameError` class [Changes][v0.25.0] # [v0.24.1](https://github.com/thombashi/pathvalidate/releases/tag/v0.24.1) - 2019-02-12 - Fix improper error messages - Improve error message readability [Changes][v0.24.1] # [v0.24.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.24.0) - 2019-02-03 - Add `is_valid_filename`/`is_valid_filepath` function - Add `FileNameSanitizer`/`FilePathSanitizer` classes - Add minimum length validation support - Remove deprecated functions [Changes][v0.24.0] # [v0.23.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.23.0) - 2019-01-06 - Improve sanitization/validation for files - Add `.` and `..` as reserved keywords for files - Add limit to `max_filename_len` - Fix platform specific sanitization: [#7](https://github.com/thombashi/pathvalidate/issues/7) - Fix reserved keywords sanitization/validation for files - Integrate `InvalidCharWindowsError` into `InvalidCharError` - Change to use `ReservedNameError` instead of `InvalidReservedNameError` - Breaking changes - Rename a property for `FileSanitizer` from `platform_name` to `platform` - Rename methods argument from `platform_name` to `platform` [Changes][v0.23.0] # [v0.22.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.22.0) - 2018-12-23 - Add universal (platform independent) filename/filepath sanitization/validation - Treat ASCII whitespace other than normal space as invalid on Windows [#6](https://github.com/thombashi/pathvalidate/issues/6) (Thanks to [@nyuszika7h](https://github.com/nyuszika7h)) [Changes][v0.22.0] # [v0.21.1](https://github.com/thombashi/pathvalidate/releases/tag/v0.21.1) - 2018-07-28 - Add support for PathLike object - Bug fixes [Changes][v0.21.1] # [v0.18.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.18.0) - 2018-07-07 - Add validations/sanitizations for unprintable characters - Add support for Python 3.7 [Changes][v0.18.0] # [v0.15.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.15.0) - 2017-03-18 - Remove package dependencies - pathvalidate functions are expected to passing unicode strings. [Changes][v0.15.0] # [v0.14.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.14.0) - 2017-02-11 - Change max file name/path length to configurable - Add support for Python 3.6 - Bug fixes [Changes][v0.14.0] # [v0.13.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.13.0) - 2017-01-03 - Add JavaScript validator/sanitizer - Bug fixes [Changes][v0.13.0] # [v0.11.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.11.0) - 2016-12-25 - Add multibyte character validate/sanitize support [Changes][v0.11.0] # [v0.10.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.10.0) - 2016-12-23 - Add LTSV support [Changes][v0.10.0] # [v0.9.1](https://github.com/thombashi/pathvalidate/releases/tag/v0.9.1) - 2016-11-17 - Support UTF8 [Changes][v0.9.1] # [v0.9.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.9.0) - 2016-11-13 - Add validate_symbol function [Changes][v0.9.0] # [v0.8.2](https://github.com/thombashi/pathvalidate/releases/tag/v0.8.2) - 2016-10-27 - Fix Windows path validation - Bug fixes [Changes][v0.8.2] # [v0.6.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.6.0) - 2016-09-19 - Add SQLite name validation function [Changes][v0.6.0] # [v0.5.2](https://github.com/thombashi/pathvalidate/releases/tag/v0.5.2) - 2016-08-20 - Fix validate/sanitize of excel sheet - Subdividing errors - Add file name validation for Windows reserved names and path length [Changes][v0.5.2] # [v0.5.1](https://github.com/thombashi/pathvalidate/releases/tag/v0.5.1) - 2016-07-23 - Modify error handling [Changes][v0.5.1] # [v0.5.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.5.0) - 2016-07-17 - Drop support for Python 2.6 - Add validate_excel_sheet_name function - Add sanitize_excel_sheet_name function [Changes][v0.5.0] # [v0.4.2](https://github.com/thombashi/pathvalidate/releases/tag/v0.4.2) - 2016-06-19 - Make pytest-runner a conditional requirement [Changes][v0.4.2] # [v0.4.1](https://github.com/thombashi/pathvalidate/releases/tag/v0.4.1) - 2016-05-29 - Modify replace_symbol function behavior [Changes][v0.4.1] # [v0.4.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.4.0) - 2016-05-28 - Add validate_file_path/sanitize_file_path functions - Fix validate/sanitize filename functions - Fix validate/sanitize python variable name functions [Changes][v0.4.0] # [v0.3.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.3.0) - 2016-05-22 - Add validate_python_var_name function [Changes][v0.3.0] # [v0.2.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.2.0) - 2016-05-21 - Add sanitize_python_var_name function [Changes][v0.2.0] # [v0.1.0](https://github.com/thombashi/pathvalidate/releases/tag/v0.1.0) - 2016-03-24 [Changes][v0.1.0] [v3.2.2]: https://github.com/thombashi/pathvalidate/compare/v3.2.1...v3.2.2 [v3.2.1]: https://github.com/thombashi/pathvalidate/compare/v3.2.0...v3.2.1 [v3.2.0]: https://github.com/thombashi/pathvalidate/compare/v3.1.0...v3.2.0 [v3.1.0]: https://github.com/thombashi/pathvalidate/compare/v3.0.0...v3.1.0 [v3.0.0]: https://github.com/thombashi/pathvalidate/compare/v2.5.2...v3.0.0 [v2.5.2]: https://github.com/thombashi/pathvalidate/compare/v2.5.1...v2.5.2 [v2.5.1]: https://github.com/thombashi/pathvalidate/compare/v2.5.0...v2.5.1 [v2.5.0]: https://github.com/thombashi/pathvalidate/compare/v2.4.1...v2.5.0 [v2.4.1]: https://github.com/thombashi/pathvalidate/compare/v2.4.0...v2.4.1 [v2.4.0]: https://github.com/thombashi/pathvalidate/compare/v2.3.2...v2.4.0 [v2.3.2]: https://github.com/thombashi/pathvalidate/compare/v2.3.1...v2.3.2 [v2.3.1]: https://github.com/thombashi/pathvalidate/compare/v2.3.0...v2.3.1 [v2.3.0]: https://github.com/thombashi/pathvalidate/compare/v2.2.2...v2.3.0 [v2.2.2]: https://github.com/thombashi/pathvalidate/compare/v2.2.1...v2.2.2 [v2.2.1]: https://github.com/thombashi/pathvalidate/compare/v2.2.0...v2.2.1 [v2.2.0]: https://github.com/thombashi/pathvalidate/compare/v2.1.0...v2.2.0 [v2.1.0]: https://github.com/thombashi/pathvalidate/compare/v2.0.0...v2.1.0 [v2.0.0]: https://github.com/thombashi/pathvalidate/compare/v1.1.0...v2.0.0 [v1.1.0]: https://github.com/thombashi/pathvalidate/compare/v1.0.0...v1.1.0 [v1.0.0]: https://github.com/thombashi/pathvalidate/compare/v0.29.1...v1.0.0 [v0.29.1]: https://github.com/thombashi/pathvalidate/compare/v0.29.0...v0.29.1 [v0.29.0]: https://github.com/thombashi/pathvalidate/compare/v0.28.2...v0.29.0 [v0.28.2]: https://github.com/thombashi/pathvalidate/compare/v0.28.0...v0.28.2 [v0.28.0]: https://github.com/thombashi/pathvalidate/compare/v0.26.0...v0.28.0 [v0.26.0]: https://github.com/thombashi/pathvalidate/compare/v0.25.0...v0.26.0 [v0.25.0]: https://github.com/thombashi/pathvalidate/compare/v0.24.1...v0.25.0 [v0.24.1]: https://github.com/thombashi/pathvalidate/compare/v0.24.0...v0.24.1 [v0.24.0]: https://github.com/thombashi/pathvalidate/compare/v0.23.0...v0.24.0 [v0.23.0]: https://github.com/thombashi/pathvalidate/compare/v0.22.0...v0.23.0 [v0.22.0]: https://github.com/thombashi/pathvalidate/compare/v0.21.1...v0.22.0 [v0.21.1]: https://github.com/thombashi/pathvalidate/compare/v0.18.0...v0.21.1 [v0.18.0]: https://github.com/thombashi/pathvalidate/compare/v0.15.0...v0.18.0 [v0.15.0]: https://github.com/thombashi/pathvalidate/compare/v0.14.0...v0.15.0 [v0.14.0]: https://github.com/thombashi/pathvalidate/compare/v0.13.0...v0.14.0 [v0.13.0]: https://github.com/thombashi/pathvalidate/compare/v0.11.0...v0.13.0 [v0.11.0]: https://github.com/thombashi/pathvalidate/compare/v0.10.0...v0.11.0 [v0.10.0]: https://github.com/thombashi/pathvalidate/compare/v0.9.1...v0.10.0 [v0.9.1]: https://github.com/thombashi/pathvalidate/compare/v0.9.0...v0.9.1 [v0.9.0]: https://github.com/thombashi/pathvalidate/compare/v0.8.2...v0.9.0 [v0.8.2]: https://github.com/thombashi/pathvalidate/compare/v0.6.0...v0.8.2 [v0.6.0]: https://github.com/thombashi/pathvalidate/compare/v0.5.2...v0.6.0 [v0.5.2]: https://github.com/thombashi/pathvalidate/compare/v0.5.1...v0.5.2 [v0.5.1]: https://github.com/thombashi/pathvalidate/compare/v0.5.0...v0.5.1 [v0.5.0]: https://github.com/thombashi/pathvalidate/compare/v0.4.2...v0.5.0 [v0.4.2]: https://github.com/thombashi/pathvalidate/compare/v0.4.1...v0.4.2 [v0.4.1]: https://github.com/thombashi/pathvalidate/compare/v0.4.0...v0.4.1 [v0.4.0]: https://github.com/thombashi/pathvalidate/compare/v0.3.0...v0.4.0 [v0.3.0]: https://github.com/thombashi/pathvalidate/compare/v0.2.0...v0.3.0 [v0.2.0]: https://github.com/thombashi/pathvalidate/compare/v0.1.0...v0.2.0 [v0.1.0]: https://github.com/thombashi/pathvalidate/tree/v0.1.0 pathvalidate-3.2.3/docs/pages/changelog_ref.rst000066400000000000000000000002231473576712500215470ustar00rootroot00000000000000Changelog ================== `Reference `__ .. literalinclude:: CHANGELOG.md pathvalidate-3.2.3/docs/pages/examples/000077500000000000000000000000001473576712500200535ustar00rootroot00000000000000pathvalidate-3.2.3/docs/pages/examples/argparse.rst000066400000000000000000000004151473576712500224110ustar00rootroot00000000000000filename/filepath validator for argparse -------------------------------------------------------- .. include:: argparse_validator.txt filename/filepath sanitizer for argparse -------------------------------------------------------- .. include:: argparse_sanitizer.txt pathvalidate-3.2.3/docs/pages/examples/argparse_sanitizer.txt000066400000000000000000000013601473576712500245100ustar00rootroot00000000000000:Sample Code: .. code-block:: python from argparse import ArgumentParser from pathvalidate.argparse import sanitize_filename_arg, sanitize_filepath_arg parser = ArgumentParser() parser.add_argument("--filename", type=sanitize_filename_arg) parser.add_argument("--filepath", type=sanitize_filepath_arg) options = parser.parse_args() if options.filename: print("filename: {}".format(options.filename)) if options.filepath: print("filepath: {}".format(options.filepath)) :Output: .. code-block:: none $ ./examples/argparse_sanitize.py --filename e/g filename: eg .. note:: ``sanitize_filepath_arg`` is set platform as ``"auto"``. pathvalidate-3.2.3/docs/pages/examples/argparse_validator.txt000066400000000000000000000020461473576712500244670ustar00rootroot00000000000000:Sample Code: .. code-block:: python from argparse import ArgumentParser from pathvalidate.argparse import validate_filename_arg, validate_filepath_arg parser = ArgumentParser() parser.add_argument("--filename", type=validate_filename_arg) parser.add_argument("--filepath", type=validate_filepath_arg) options = parser.parse_args() if options.filename: print(f"filename: {options.filename}") if options.filepath: print(f"filepath: {options.filepath}") :Output: .. code-block:: none $ ./examples/argparse_validate.py --filename eg filename: eg $ ./examples/argparse_validate.py --filename e?g usage: argparse_validate.py [-h] [--filename FILENAME] [--filepath FILEPATH] argparse_validate.py: error: argument --filename: [PV1100] invalid characters found: invalids=(':'), value='e:g', platform=Windows .. note:: ``validate_filepath_arg`` consider ``platform`` as of ``"auto"`` if the input is an absolute file path. pathvalidate-3.2.3/docs/pages/examples/click.rst000066400000000000000000000004121473576712500216670ustar00rootroot00000000000000filename/filepath validator for ``click`` -------------------------------------------------------- .. include:: click_validator.txt filename/filepath sanitizer for ``click`` -------------------------------------------------------- .. include:: click_sanitizer.txt pathvalidate-3.2.3/docs/pages/examples/click_sanitizer.txt000066400000000000000000000012341473576712500237710ustar00rootroot00000000000000:Sample Code: .. code-block:: python import click from pathvalidate.click import sanitize_filename_arg, sanitize_filepath_arg @click.command() @click.option("--filename", callback=sanitize_filename_arg) @click.option("--filepath", callback=sanitize_filepath_arg) def cli(filename, filepath): if filename: click.echo(f"filename: {filename}") if filepath: click.echo(f"filepath: {filepath}") if __name__ == "__main__": cli() :Output: .. code-block:: none $ ./examples/click_sanitize.py --filename a/b filename: ab pathvalidate-3.2.3/docs/pages/examples/click_validator.txt000066400000000000000000000017001473576712500237440ustar00rootroot00000000000000:Sample Code: .. code-block:: python import click from pathvalidate.click import validate_filename_arg, validate_filepath_arg @click.command() @click.option("--filename", callback=validate_filename_arg) @click.option("--filepath", callback=validate_filepath_arg) def cli(filename: str, filepath: str) -> None: if filename: click.echo(f"filename: {filename}") if filepath: click.echo(f"filepath: {filepath}") if __name__ == "__main__": cli() :Output: .. code-block:: none $ ./examples/click_validate.py --filename ab filename: ab $ ./examples/click_validate.py --filepath e?g Usage: click_validate.py [OPTIONS] Try 'click_validate.py --help' for help. Error: Invalid value for '--filename': [PV1100] invalid characters found: invalids=('?'), value='e?g', platform=Windows pathvalidate-3.2.3/docs/pages/examples/index.rst000066400000000000000000000001521473576712500217120ustar00rootroot00000000000000Examples ======== .. toctree:: :maxdepth: 3 sanitize validate is_valid argparse click pathvalidate-3.2.3/docs/pages/examples/is_valid.rst000066400000000000000000000007031473576712500223770ustar00rootroot00000000000000.. _example-is-valid-filename: Check a filename ---------------------------- :py:func:`.is_valid_filename()` function returns |True| if a filename is valid for a specified platform. .. include:: is_valid_filename_code.txt .. _example-is-valid-filepath: Check a filepath ---------------------------- :py:func:`.is_valid_filepath()` function returns |True| if a filepath is valid for a specified platform. .. include:: is_valid_filepath_code.txt pathvalidate-3.2.3/docs/pages/examples/is_valid_filename_code.txt000066400000000000000000000010411473576712500252340ustar00rootroot00000000000000:Sample Code: .. code-block:: python from pathvalidate import is_valid_filename, sanitize_filename fname = "fi:l*e/p\"a?t>h|.th|.th|.th|.th|.t {sanitize_filename(fname)}\n") fname = "\0_a*b:ce%f/(g)h+i_0.txt" print(f"{fname} -> {sanitize_filename(fname)}\n") :Output: .. code-block:: none fi:l*e/p"a?t>h|.t filepath.txt _a*b:ce%f/(g)h+i_0.txt -> _abcde%f(g)h+i_0.txt The default target ``platform`` is ``universal``. i.e. the sanitized file name is valid for any platform. pathvalidate-3.2.3/docs/pages/examples/sanitize_filepath_code.txt000066400000000000000000000006671473576712500253210ustar00rootroot00000000000000:Sample Code: .. code-block:: python from pathvalidate import sanitize_filepath fpath = "fi:l*e/p\"a?t>h|.t {sanitize_filepath(fpath)}\n") fpath = "\0_a*b:ce%f/(g)h+i_0.txt" print(f"{fpath} -> {sanitize_filepath(fpath)}\n") :Output: .. code-block:: none fi:l*e/p"a?t>h|.t file/path.txt _a*b:ce%f/(g)h+i_0.txt -> _abcde%f/(g)h+i_0.txt pathvalidate-3.2.3/docs/pages/examples/sanitize_replace_symbol_code.txt000066400000000000000000000004221473576712500265120ustar00rootroot00000000000000:Sample Code: .. code-block:: python from pathvalidate import replace_symbol name = "\0_a*b:ce%f/(g)h+i_0.txt" print(f"{name} -> {replace_symbol(name)}") :Output: .. code-block:: none _a*b:ce%f/(g)h+i_0.txt -> abcdefghi0txt pathvalidate-3.2.3/docs/pages/examples/sanitize_var_name_code.txt000066400000000000000000000003201473576712500252770ustar00rootroot00000000000000:Sample Code: .. code-block:: python import pathvalidate as pv print(pv.sanitize_python_var_name("_a*b:ce%f/(g)h+i_0.txt")) :Output: .. code-block:: none abcdefghi_0txt pathvalidate-3.2.3/docs/pages/examples/validate.rst000066400000000000000000000007611473576712500224020ustar00rootroot00000000000000.. _example-validate-filename: Validate a filename ---------------------------- The :py:func:`.validate_filename()` function raise ``ValueError`` if the name includes invalid character(s) for a filename. .. include:: validate_filename_code.txt .. _example-validate-file-path: Validate a file path ---------------------------- The :py:func:`.validate_filepath()` function raise ``ValueError`` if the name includes invalid character(s) for a file path. .. include:: validate_filepath_code.txt pathvalidate-3.2.3/docs/pages/examples/validate_filename_code.txt000066400000000000000000000012641473576712500252420ustar00rootroot00000000000000:Sample Code: .. code-block:: python import sys from pathvalidate import ValidationError, validate_filename try: validate_filename("fi:l*e/p\"a?t>h|.th|.t', '|', '<'), value='fi:l*e/p"a?t>h|.te%f/(g)h+i_0.txt") except ValueError: print("invalid variable name!") :Output: .. code-block:: none invalid variable name! pathvalidate-3.2.3/docs/pages/genindex.rst000066400000000000000000000000701473576712500205650ustar00rootroot00000000000000Indices and tables ================== * :ref:`genindex`pathvalidate-3.2.3/docs/pages/introduction/000077500000000000000000000000001473576712500207565ustar00rootroot00000000000000pathvalidate-3.2.3/docs/pages/introduction/badges.txt000066400000000000000000000027351473576712500227530ustar00rootroot00000000000000|PyPI pkg ver| |conda pkg ver| |Supported Python ver| |Supported Python impl| |CI status| |Test coverage| |CodeQL| .. |PyPI pkg ver| image:: https://badge.fury.io/py/pathvalidate.svg :target: https://badge.fury.io/py/pathvalidate :alt: PyPI package version .. |conda pkg ver| image:: https://anaconda.org/conda-forge/pathvalidate/badges/version.svg :target: https://anaconda.org/conda-forge/pathvalidate :alt: conda package version .. |Supported Python ver| image:: https://img.shields.io/pypi/pyversions/pathvalidate.svg :target: https://pypi.org/project/pathvalidate :alt: Supported Python versions .. |Supported Python impl| image:: https://img.shields.io/pypi/implementation/pathvalidate.svg :target: https://pypi.org/project/pathvalidate :alt: Supported Python implementations .. |CI status| image:: https://github.com/thombashi/pathvalidate/actions/workflows/ci.yml/badge.svg :target: https://github.com/thombashi/pathvalidate/actions/workflows/ci.yml :alt: CI status of Linux/macOS/Windows .. |Test coverage| image:: https://coveralls.io/repos/github/thombashi/pathvalidate/badge.svg?branch=master :target: https://coveralls.io/github/thombashi/pathvalidate?branch=master :alt: Test coverage: coveralls .. |CodeQL| image:: https://github.com/thombashi/pathvalidate/actions/workflows/github-code-scanning/codeql/badge.svg :target: https://github.com/thombashi/pathvalidate/actions/workflows/github-code-scanning/codeql :alt: CodeQL pathvalidate-3.2.3/docs/pages/introduction/cli.txt000066400000000000000000000002431473576712500222650ustar00rootroot00000000000000CLI tool --------- You can find this package's command line interface tool at the `pathvalidate-cli `__ repository. pathvalidate-3.2.3/docs/pages/introduction/feature.txt000066400000000000000000000010271473576712500231520ustar00rootroot00000000000000Features --------- - Sanitize/Validate a string as a: - file name - file path - Sanitize will do: - Remove invalid characters for a target platform - Replace reserved names for a target platform - Normalize - Remove unprintable characters - Argument validator/sanitizer for ``argparse`` and ``click`` - Multi platform support: - ``Linux`` - ``Windows`` - ``macOS`` - ``POSIX``: POSIX-compliant systems (Linux, macOS, etc.) - ``universal``: platform independent - Multibyte character support pathvalidate-3.2.3/docs/pages/introduction/index.rst000066400000000000000000000006551473576712500226250ustar00rootroot00000000000000pathvalidate ============= .. include:: badges.txt Summary ------- .. include:: summary.txt .. raw:: html

.. include:: feature.txt .. include:: cli.txt .. include:: installation.rst pathvalidate-3.2.3/docs/pages/introduction/installation.rst000066400000000000000000000006641473576712500242170ustar00rootroot00000000000000Installation ============ Installation: pip ------------------------------ :: pip install pathvalidate Installation: conda ------------------------------ :: conda install conda-forge::pathvalidate Installation: apt ------------------------------ :: sudo add-apt-repository ppa:thombashi/ppa sudo apt update sudo apt install python3-pathvalidate Dependencies ============ Python 3.9+ no external dependencies. pathvalidate-3.2.3/docs/pages/introduction/summary.txt000066400000000000000000000001411473576712500232100ustar00rootroot00000000000000pathvalidate is a Python library to sanitize/validate a string such as filenames/file-paths/etc. pathvalidate-3.2.3/docs/pages/links.rst000066400000000000000000000005461473576712500201140ustar00rootroot00000000000000.. include:: sponsors.rst .. include:: genindex.rst Links ===== - `GitHub repository `__ - `Issue tracker `__ - `pip: A tool for installing Python packages `__ - `pathvalidate-cli `__ pathvalidate-3.2.3/docs/pages/reference/000077500000000000000000000000001473576712500201735ustar00rootroot00000000000000pathvalidate-3.2.3/docs/pages/reference/error.rst000066400000000000000000000035321473576712500220610ustar00rootroot00000000000000Errors --------------- .. autoclass:: pathvalidate.error.ErrorReason :members: :undoc-members: :show-inheritance: .. table:: List of Errors +--------+------------------------+------------------------------------------------------+ | Code | Name | Description | +========+========================+======================================================+ | PV1001 | NULL_NAME | the value must not be an empty string | +--------+------------------------+------------------------------------------------------+ | PV1002 | RESERVED_NAME | found a reserved name by a platform | +--------+------------------------+------------------------------------------------------+ | PV1100 | INVALID_CHARACTER | invalid characters found | +--------+------------------------+------------------------------------------------------+ | PV1101 | INVALID_LENGTH | found an invalid string length | +--------+------------------------+------------------------------------------------------+ | PV1200 | FOUND_ABS_PATH | found an absolute path where must be a relative path | +--------+------------------------+------------------------------------------------------+ | PV1201 | MALFORMED_ABS_PATH | found a malformed absolute path | +--------+------------------------+------------------------------------------------------+ | PV2000 | INVALID_AFTER_SANITIZE | found invalid value after sanitizing | +--------+------------------------+------------------------------------------------------+ .. autoexception:: pathvalidate.error.ValidationError :members: :undoc-members: :show-inheritance: pathvalidate-3.2.3/docs/pages/reference/function.rst000066400000000000000000000013561473576712500225570ustar00rootroot00000000000000Functions --------------- File name validation/sanitization ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. autofunction:: pathvalidate.validate_filename .. autofunction:: pathvalidate.sanitize_filename Check a file name ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. autofunction:: pathvalidate.is_valid_filename File path validation/sanitization ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. autofunction:: pathvalidate.validate_filepath .. autofunction:: pathvalidate.sanitize_filepath Check a file path ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. autofunction:: pathvalidate.is_valid_filepath Symbol validation/sanitization ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. autofunction:: pathvalidate.validate_symbol .. autofunction:: pathvalidate.replace_symbol pathvalidate-3.2.3/docs/pages/reference/handler.rst000066400000000000000000000006071473576712500223450ustar00rootroot00000000000000Handlers --------------- Reserved name handlers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. autoclass:: pathvalidate.handler.ReservedNameHandler :members: Null value handlers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. autoclass:: pathvalidate.handler.NullValueHandler :members: Other handlers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. autofunction:: pathvalidate.handler.raise_error pathvalidate-3.2.3/docs/pages/reference/index.rst000066400000000000000000000001471473576712500220360ustar00rootroot00000000000000Reference ========= .. toctree:: :maxdepth: 3 error function types handler tips pathvalidate-3.2.3/docs/pages/reference/platform.txt000066400000000000000000000005421473576712500225610ustar00rootroot00000000000000Valid specifiers are as follows (case-insensitive): - ``"Linux"`` - ``"Windows"`` - ``"macOS"`` - ``"auto"``: automatically detect the execution platform - ``"POSIX"``: POSIX-compliant systems (Linux, macOS, etc.) - ``"universal"``/|None|: platform independent. note that absolute paths cannot specify this. Defaults to |None|. pathvalidate-3.2.3/docs/pages/reference/tips.rst000066400000000000000000000025521473576712500217100ustar00rootroot00000000000000Tips ------------ Sanitize dot-files or dot-directories ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When you process filenames or filepaths containing ``.`` or ``..`` with the ``sanitize_filename`` function or the ``sanitize_filepath`` function, by default, ``sanitize_filename`` does nothing, and ``sanitize_filepath`` normalizes the filepaths: .. code-block:: python print(sanitize_filename(".")) print(sanitize_filepath("hoge/./foo")) .. code-block:: console . hoge/foo If you would like to replace ``.`` and ``..`` like other reserved words, you need to specify the arguments as follows: .. code-block:: python from pathvalidate import sanitize_filepath, sanitize_filename from pathvalidate.error import ValidationError def always_add_trailing_underscore(e: ValidationError) -> str: if e.reusable_name: return e.reserved_name return f"{e.reserved_name}_" print( sanitize_filename( ".", reserved_name_handler=always_add_trailing_underscore, additional_reserved_names=[".", ".."], ) ) print( sanitize_filepath( "hoge/./foo", normalize=False, reserved_name_handler=always_add_trailing_underscore, additional_reserved_names=[".", ".."], ) ) .. code-block:: console ._ hoge/._/foo pathvalidate-3.2.3/docs/pages/reference/types.rst000066400000000000000000000001651473576712500220730ustar00rootroot00000000000000Types --------------- .. autoclass:: pathvalidate.Platform :members: :undoc-members: :show-inheritance: pathvalidate-3.2.3/docs/pages/sponsors.rst000066400000000000000000000020101473576712500206460ustar00rootroot00000000000000Sponsors ==================================== |chasbecker| |shiguredo| |b4tman| |Arturi0| |github| .. |chasbecker| image:: https://avatars.githubusercontent.com/u/44389260?s=48&u=6da7176e51ae2654bcfd22564772ef8a3bb22318&v=4 :target: https://github.com/chasbecker :alt: ex-sponsor: Charles Becker (chasbecker) .. |shiguredo| image:: https://avatars.githubusercontent.com/u/2549434?s=48&v=4 :target: https://github.com/shiguredo :alt: ex-sponsor: 時雨堂 (shiguredo) .. |b4tman| image:: https://avatars.githubusercontent.com/u/3658062?s=48&v=4 :target: https://github.com/b4tman :alt: onetime: Dmitry Belyaev (b4tman) .. |Arturi0| image:: https://avatars.githubusercontent.com/u/46711571?s=48&u=57687c0e02d5d6e8eeaf9177f7b7af4c9f275eb5&v=4 :target: https://github.com/Arturi0 :alt: onetime: Arturi0 .. |github| image:: https://avatars.githubusercontent.com/u/9919?s=48&v=4 :target: https://github.com/github :alt: onetime: GitHub (github) `Become a sponsor `__ pathvalidate-3.2.3/examples/000077500000000000000000000000001473576712500160245ustar00rootroot00000000000000pathvalidate-3.2.3/examples/README.rst000066400000000000000000000001401473576712500175060ustar00rootroot00000000000000https://nbviewer.jupyter.org/github/thombashi/pathvalidate/tree/master/ipynb/pathvalidate.ipynb pathvalidate-3.2.3/examples/argparse_sanitize.py000077500000000000000000000007041473576712500221140ustar00rootroot00000000000000#!/usr/bin/env python3 from argparse import ArgumentParser from pathvalidate.argparse import sanitize_filename_arg, sanitize_filepath_arg parser = ArgumentParser() parser.add_argument("--filename", type=sanitize_filename_arg) parser.add_argument("--filepath", type=sanitize_filepath_arg) options = parser.parse_args() if options.filename: print(f"filename: {options.filename}") if options.filepath: print(f"filepath: {options.filepath}") pathvalidate-3.2.3/examples/argparse_validate.py000077500000000000000000000007041473576712500220570ustar00rootroot00000000000000#!/usr/bin/env python3 from argparse import ArgumentParser from pathvalidate.argparse import validate_filename_arg, validate_filepath_arg parser = ArgumentParser() parser.add_argument("--filename", type=validate_filename_arg) parser.add_argument("--filepath", type=validate_filepath_arg) options = parser.parse_args() if options.filename: print(f"filename: {options.filename}") if options.filepath: print(f"filepath: {options.filepath}") pathvalidate-3.2.3/examples/click_sanitize.py000077500000000000000000000007411473576712500213760ustar00rootroot00000000000000#!/usr/bin/env python3 import click from pathvalidate.click import sanitize_filename_arg, sanitize_filepath_arg @click.command() @click.option("--filename", type=str, callback=sanitize_filename_arg) @click.option("--filepath", type=str, callback=sanitize_filepath_arg) def cli(filename: str, filepath: str) -> None: if filename: click.echo(f"filename: {filename}") if filepath: click.echo(f"filepath: {filepath}") if __name__ == "__main__": cli() pathvalidate-3.2.3/examples/click_validate.py000077500000000000000000000007151473576712500213420ustar00rootroot00000000000000#!/usr/bin/env python3 import click from pathvalidate.click import validate_filename_arg, validate_filepath_arg @click.command() @click.option("--filename", callback=validate_filename_arg) @click.option("--filepath", callback=validate_filepath_arg) def cli(filename: str, filepath: str) -> None: if filename: click.echo(f"filename: {filename}") if filepath: click.echo(f"filepath: {filepath}") if __name__ == "__main__": cli() pathvalidate-3.2.3/examples/pathvalidate_examples.ipynb000066400000000000000000000147431473576712500234440ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "name = \"\\0_a*b:ce%f/(g)h+i_0.txt\"" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "[PV1100] invalid characters found: invalids=('/'), value='fi:l*e/p\"a?t>h|.th|.t', '|', '<'), value='fi:l*e/p\"a?t>h|.th|.th|.t filepath.txt\n", "\n", "\u0000_a*b:ce%f/(g)h+i_0.txt -> _abcde%f(g)h+i_0.txt\n", "\n" ] } ], "source": [ "from pathvalidate import sanitize_filename\n", "\n", "fname = 'fi:l*e/p\"a?t>h|.t {sanitize_filename(fname)}\\n\")\n", "\n", "fname = \"\\0_a*b:ce%f/(g)h+i_0.txt\"\n", "print(f\"{fname} -> {sanitize_filename(fname)}\\n\")" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "fi:l*e/p\"a?t>h|.t file/path.txt\n", "\n", "\u0000_a*b:ce%f/(g)h+i_0.txt -> _abcde%f/(g)h+i_0.txt\n", "\n" ] } ], "source": [ "from pathvalidate import sanitize_filepath\n", "\n", "fpath = 'fi:l*e/p\"a?t>h|.t {sanitize_filepath(fpath)}\\n\")\n", "\n", "fpath = \"\\0_a*b:ce%f/(g)h+i_0.txt\"\n", "print(f\"{fpath} -> {sanitize_filepath(fpath)}\\n\")" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u0000_a*b:ce%f/(g)h+i_0.txt -> abcdefghi0txt\n" ] } ], "source": [ "from pathvalidate import replace_symbol\n", "\n", "name = \"\\0_a*b:ce%f/(g)h+i_0.txt\"\n", "print(f\"{name} -> {replace_symbol(name)}\")" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "is_valid_filename('fi:l*e/p\"a?t>h|.th|.t str:\n", " if e.reusable_name:\n", " return e.reserved_name\n", "\n", " return f\"{e.reserved_name}_\"\n", "\n", "\n", "sanitize_filename(\n", " \".\", reserved_name_handler=add_trailing_underscore, additional_reserved_names=[\".\"]\n", ")" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3.8.12 64-bit ('3.8.12')", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.4" }, "vscode": { "interpreter": { "hash": "c07eda5d3696c8b68c933d32ea433f84251ada7607722ea42bf003255d06d542" } } }, "nbformat": 4, "nbformat_minor": 1 } pathvalidate-3.2.3/pathvalidate/000077500000000000000000000000001473576712500166545ustar00rootroot00000000000000pathvalidate-3.2.3/pathvalidate/__init__.py000066400000000000000000000036061473576712500207720ustar00rootroot00000000000000""" .. codeauthor:: Tsuyoshi Hombashi """ from .__version__ import __author__, __copyright__, __email__, __license__, __version__ from ._base import AbstractSanitizer, AbstractValidator from ._common import ( ascii_symbols, normalize_platform, replace_ansi_escape, replace_unprintable_char, unprintable_ascii_chars, validate_pathtype, validate_unprintable_char, ) from ._const import Platform from ._filename import ( FileNameSanitizer, FileNameValidator, is_valid_filename, sanitize_filename, validate_filename, ) from ._filepath import ( FilePathSanitizer, FilePathValidator, is_valid_filepath, sanitize_filepath, validate_filepath, ) from ._ltsv import sanitize_ltsv_label, validate_ltsv_label from ._symbol import replace_symbol, validate_symbol from .error import ( ErrorReason, InvalidCharError, InvalidReservedNameError, NullNameError, ReservedNameError, ValidationError, ValidReservedNameError, ) __all__ = ( "__author__", "__copyright__", "__email__", "__license__", "__version__", "AbstractSanitizer", "AbstractValidator", "Platform", "ascii_symbols", "normalize_platform", "replace_ansi_escape", "replace_unprintable_char", "unprintable_ascii_chars", "validate_pathtype", "validate_unprintable_char", "FileNameSanitizer", "FileNameValidator", "is_valid_filename", "sanitize_filename", "validate_filename", "FilePathSanitizer", "FilePathValidator", "is_valid_filepath", "sanitize_filepath", "validate_filepath", "sanitize_ltsv_label", "validate_ltsv_label", "replace_symbol", "validate_symbol", "ErrorReason", "InvalidCharError", "InvalidReservedNameError", "NullNameError", "ReservedNameError", "ValidationError", "ValidReservedNameError", ) pathvalidate-3.2.3/pathvalidate/__version__.py000066400000000000000000000004141473576712500215060ustar00rootroot00000000000000from typing import Final __author__: Final = "Tsuyoshi Hombashi" __copyright__: Final = f"Copyright 2016-2025, {__author__}" __license__: Final = "MIT License" __version__ = "3.2.3" __maintainer__: Final = __author__ __email__: Final = "tsuyoshi.hombashi@gmail.com" pathvalidate-3.2.3/pathvalidate/_base.py000066400000000000000000000171441473576712500203060ustar00rootroot00000000000000""" .. codeauthor:: Tsuyoshi Hombashi """ import abc import os import re import sys from collections.abc import Sequence from typing import Final, Optional from ._common import normalize_platform, unprintable_ascii_chars from ._const import DEFAULT_MIN_LEN, Platform from ._types import PathType, PlatformType from .error import ReservedNameError, ValidationError from .handler import NullValueHandler, ReservedNameHandler, ValidationErrorHandler class BaseFile: _INVALID_PATH_CHARS: Final[str] = "".join(unprintable_ascii_chars) _INVALID_FILENAME_CHARS: Final[str] = _INVALID_PATH_CHARS + "/" _INVALID_WIN_PATH_CHARS: Final[str] = _INVALID_PATH_CHARS + ':*?"<>|\t\n\r\x0b\x0c' _INVALID_WIN_FILENAME_CHARS: Final[str] = ( _INVALID_FILENAME_CHARS + _INVALID_WIN_PATH_CHARS + "\\" ) @property def platform(self) -> Platform: return self.__platform @property def reserved_keywords(self) -> tuple[str, ...]: return self._additional_reserved_names @property def max_len(self) -> int: return self._max_len def __init__( self, max_len: int, fs_encoding: Optional[str], additional_reserved_names: Optional[Sequence[str]] = None, platform_max_len: Optional[int] = None, platform: Optional[PlatformType] = None, ) -> None: if additional_reserved_names is None: additional_reserved_names = tuple() self._additional_reserved_names = tuple(n.upper() for n in additional_reserved_names) self.__platform = normalize_platform(platform) if platform_max_len is None: platform_max_len = self._get_default_max_path_len() if max_len <= 0: self._max_len = platform_max_len else: self._max_len = max_len self._max_len = min(self._max_len, platform_max_len) if fs_encoding: self._fs_encoding = fs_encoding else: self._fs_encoding = sys.getfilesystemencoding() def _is_posix(self) -> bool: return self.platform == Platform.POSIX def _is_universal(self) -> bool: return self.platform == Platform.UNIVERSAL def _is_linux(self, include_universal: bool = False) -> bool: if include_universal: return self.platform in (Platform.UNIVERSAL, Platform.LINUX) return self.platform == Platform.LINUX def _is_windows(self, include_universal: bool = False) -> bool: if include_universal: return self.platform in (Platform.UNIVERSAL, Platform.WINDOWS) return self.platform == Platform.WINDOWS def _is_macos(self, include_universal: bool = False) -> bool: if include_universal: return self.platform in (Platform.UNIVERSAL, Platform.MACOS) return self.platform == Platform.MACOS def _get_default_max_path_len(self) -> int: if self._is_linux(): return 4096 if self._is_windows(): return 260 if self._is_posix() or self._is_macos(): return 1024 return 260 # universal class AbstractValidator(BaseFile, metaclass=abc.ABCMeta): def __init__( self, max_len: int, fs_encoding: Optional[str], check_reserved: bool, additional_reserved_names: Optional[Sequence[str]] = None, platform_max_len: Optional[int] = None, platform: Optional[PlatformType] = None, ) -> None: self._check_reserved = check_reserved super().__init__( max_len, fs_encoding, additional_reserved_names=additional_reserved_names, platform_max_len=platform_max_len, platform=platform, ) @property @abc.abstractmethod def min_len(self) -> int: # pragma: no cover pass @abc.abstractmethod def validate(self, value: PathType) -> None: # pragma: no cover pass def is_valid(self, value: PathType) -> bool: try: self.validate(value) except (TypeError, ValidationError): return False return True def _is_reserved_keyword(self, value: str) -> bool: return value.upper() in self.reserved_keywords class AbstractSanitizer(BaseFile, metaclass=abc.ABCMeta): def __init__( self, validator: AbstractValidator, max_len: int, fs_encoding: Optional[str], validate_after_sanitize: bool, null_value_handler: Optional[ValidationErrorHandler] = None, reserved_name_handler: Optional[ValidationErrorHandler] = None, additional_reserved_names: Optional[Sequence[str]] = None, platform_max_len: Optional[int] = None, platform: Optional[PlatformType] = None, ) -> None: super().__init__( max_len=max_len, fs_encoding=fs_encoding, additional_reserved_names=additional_reserved_names, platform_max_len=platform_max_len, platform=platform, ) if null_value_handler is None: null_value_handler = NullValueHandler.return_null_string self._null_value_handler = null_value_handler if reserved_name_handler is None: reserved_name_handler = ReservedNameHandler.add_trailing_underscore self._reserved_name_handler = reserved_name_handler self._validate_after_sanitize = validate_after_sanitize self._validator = validator @abc.abstractmethod def sanitize(self, value: PathType, replacement_text: str = "") -> PathType: # pragma: no cover pass class BaseValidator(AbstractValidator): __RE_ROOT_NAME: Final = re.compile(r"([^\.]+)") __RE_REPEAD_DOT: Final = re.compile(r"^\.{3,}") @property def min_len(self) -> int: return self._min_len def __init__( self, min_len: int, max_len: int, fs_encoding: Optional[str], check_reserved: bool, additional_reserved_names: Optional[Sequence[str]] = None, platform_max_len: Optional[int] = None, platform: Optional[PlatformType] = None, ) -> None: if min_len <= 0: min_len = DEFAULT_MIN_LEN self._min_len = max(min_len, 1) super().__init__( max_len=max_len, fs_encoding=fs_encoding, check_reserved=check_reserved, additional_reserved_names=additional_reserved_names, platform_max_len=platform_max_len, platform=platform, ) self._validate_max_len() def _validate_reserved_keywords(self, name: str) -> None: if not self._check_reserved: return root_name = self.__extract_root_name(name) base_name = os.path.basename(name) for name in (root_name, base_name): if self._is_reserved_keyword(name): raise ReservedNameError( f"'{root_name}' is a reserved name", reusable_name=False, reserved_name=root_name, platform=self.platform, ) def _validate_max_len(self) -> None: if self.max_len < 1: raise ValueError("max_len must be greater or equal to one") if self.min_len > self.max_len: raise ValueError("min_len must be lower than max_len") @classmethod def __extract_root_name(cls, path: str) -> str: if path in (".", ".."): return path if cls.__RE_REPEAD_DOT.search(path): return path match = cls.__RE_ROOT_NAME.match(os.path.basename(path)) if match is None: return "" return match.group(1) pathvalidate-3.2.3/pathvalidate/_common.py000066400000000000000000000077261473576712500206710ustar00rootroot00000000000000""" .. codeauthor:: Tsuyoshi Hombashi """ import ntpath import platform import re import string import sys from pathlib import PurePath from typing import Any, Final, Optional from ._const import Platform from ._types import PathType, PlatformType _re_whitespaces: Final = re.compile(r"^[\s]+$") def validate_pathtype( text: PathType, allow_whitespaces: bool = False, error_msg: Optional[str] = None ) -> None: from .error import ErrorReason, ValidationError if _is_not_null_string(text) or isinstance(text, PurePath): return if allow_whitespaces and _re_whitespaces.search(str(text)): return if is_null_string(text): raise ValidationError(reason=ErrorReason.NULL_NAME) raise TypeError(f"text must be a string: actual={type(text)}") def to_str(name: PathType) -> str: if isinstance(name, PurePath): return str(name) return name def is_nt_abspath(value: str) -> bool: ver_info = sys.version_info[:2] if ver_info <= (3, 10): if value.startswith("\\\\"): return True elif ver_info >= (3, 13): return ntpath.isabs(value) drive, _tail = ntpath.splitdrive(value) return ntpath.isabs(value) and len(drive) > 0 def is_null_string(value: Any) -> bool: if value is None: return True try: return len(value.strip()) == 0 except AttributeError: return False def _is_not_null_string(value: Any) -> bool: try: return len(value.strip()) > 0 except AttributeError: return False def _get_unprintable_ascii_chars() -> list[str]: return [chr(c) for c in range(128) if chr(c) not in string.printable] unprintable_ascii_chars: Final = tuple(_get_unprintable_ascii_chars()) def _get_ascii_symbols() -> list[str]: symbol_list: list[str] = [] for i in range(128): c = chr(i) if c in unprintable_ascii_chars or c in string.digits + string.ascii_letters: continue symbol_list.append(c) return symbol_list ascii_symbols: Final = tuple(_get_ascii_symbols()) __RE_UNPRINTABLE_CHARS: Final = re.compile( "[{}]".format(re.escape("".join(unprintable_ascii_chars))), re.UNICODE ) __RE_ANSI_ESCAPE: Final = re.compile( r"(?:\x1B[@-Z\\-_]|[\x80-\x9A\x9C-\x9F]|(?:\x1B\[|\x9B)[0-?]*[ -/]*[@-~])" ) def validate_unprintable_char(text: str) -> None: from .error import InvalidCharError match_list = __RE_UNPRINTABLE_CHARS.findall(to_str(text)) if match_list: raise InvalidCharError(f"unprintable character found: {match_list}") def replace_unprintable_char(text: str, replacement_text: str = "") -> str: try: return __RE_UNPRINTABLE_CHARS.sub(replacement_text, text) except (TypeError, AttributeError): raise TypeError("text must be a string") def replace_ansi_escape(text: str, replacement_text: str = "") -> str: try: return __RE_ANSI_ESCAPE.sub(replacement_text, text) except (TypeError, AttributeError): raise TypeError("text must be a string") def normalize_platform(name: Optional[PlatformType]) -> Platform: if isinstance(name, Platform): return name if not name: return Platform.UNIVERSAL platform_str = name.strip().casefold() if platform_str == "posix": return Platform.POSIX if platform_str == "auto": platform_str = platform.system().casefold() if platform_str in ["linux"]: return Platform.LINUX if platform_str and platform_str.startswith("win"): return Platform.WINDOWS if platform_str in ["mac", "macos", "darwin"]: return Platform.MACOS return Platform.UNIVERSAL def findall_to_str(match: list[Any]) -> str: return ", ".join([repr(text) for text in match]) def truncate_str(text: str, encoding: str, max_bytes: int) -> str: str_bytes = text.encode(encoding) str_bytes = str_bytes[:max_bytes] # last char might be malformed, ignore it return str_bytes.decode(encoding, "ignore") pathvalidate-3.2.3/pathvalidate/_const.py000066400000000000000000000013341473576712500205140ustar00rootroot00000000000000import enum from typing import Final DEFAULT_MIN_LEN: Final = 1 INVALID_CHAR_ERR_MSG_TMPL: Final = "invalids=({invalid}), value={value}" _NTFS_RESERVED_FILE_NAMES: Final = ( "$Mft", "$MftMirr", "$LogFile", "$Volume", "$AttrDef", "$Bitmap", "$Boot", "$BadClus", "$Secure", "$Upcase", "$Extend", "$Quota", "$ObjId", "$Reparse", ) # Only in root directory @enum.unique class Platform(enum.Enum): """ Platform specifier enumeration. """ #: POSIX compatible platform. POSIX = "POSIX" #: platform independent. note that absolute paths cannot specify this. UNIVERSAL = "universal" LINUX = "Linux" WINDOWS = "Windows" MACOS = "macOS" pathvalidate-3.2.3/pathvalidate/_filename.py000066400000000000000000000424341473576712500211540ustar00rootroot00000000000000""" .. codeauthor:: Tsuyoshi Hombashi """ import itertools import posixpath import re import warnings from collections.abc import Sequence from pathlib import Path, PurePath from re import Pattern from typing import Final, Optional from ._base import AbstractSanitizer, AbstractValidator, BaseFile, BaseValidator from ._common import findall_to_str, is_nt_abspath, to_str, truncate_str, validate_pathtype from ._const import DEFAULT_MIN_LEN, INVALID_CHAR_ERR_MSG_TMPL, Platform from ._types import PathType, PlatformType from .error import ErrorAttrKey, ErrorReason, InvalidCharError, ValidationError from .handler import ReservedNameHandler, ValidationErrorHandler _DEFAULT_MAX_FILENAME_LEN: Final = 255 _RE_INVALID_FILENAME: Final = re.compile( f"[{re.escape(BaseFile._INVALID_FILENAME_CHARS):s}]", re.UNICODE ) _RE_INVALID_WIN_FILENAME: Final = re.compile( f"[{re.escape(BaseFile._INVALID_WIN_FILENAME_CHARS):s}]", re.UNICODE ) class FileNameSanitizer(AbstractSanitizer): def __init__( self, max_len: int = _DEFAULT_MAX_FILENAME_LEN, fs_encoding: Optional[str] = None, platform: Optional[PlatformType] = None, null_value_handler: Optional[ValidationErrorHandler] = None, reserved_name_handler: Optional[ValidationErrorHandler] = None, additional_reserved_names: Optional[Sequence[str]] = None, validate_after_sanitize: bool = False, validator: Optional[AbstractValidator] = None, ) -> None: if validator: fname_validator = validator else: fname_validator = FileNameValidator( min_len=DEFAULT_MIN_LEN, max_len=max_len, fs_encoding=fs_encoding, check_reserved=True, additional_reserved_names=additional_reserved_names, platform=platform, ) super().__init__( max_len=max_len, fs_encoding=fs_encoding, null_value_handler=null_value_handler, reserved_name_handler=reserved_name_handler, additional_reserved_names=additional_reserved_names, platform=platform, validate_after_sanitize=validate_after_sanitize, validator=fname_validator, ) self._sanitize_regexp = self._get_sanitize_regexp() def sanitize(self, value: PathType, replacement_text: str = "") -> PathType: try: validate_pathtype(value, allow_whitespaces=not self._is_windows(include_universal=True)) except ValidationError as e: if e.reason == ErrorReason.NULL_NAME: if isinstance(value, PurePath): raise return self._null_value_handler(e) # type: ignore raise sanitized_filename = self._sanitize_regexp.sub(replacement_text, str(value)) sanitized_filename = truncate_str(sanitized_filename, self._fs_encoding, self.max_len) try: self._validator.validate(sanitized_filename) except ValidationError as e: if e.reason == ErrorReason.RESERVED_NAME: replacement_word = self._reserved_name_handler(e) if e.reserved_name != replacement_word: sanitized_filename = re.sub( re.escape(e.reserved_name), replacement_word, sanitized_filename ) elif e.reason == ErrorReason.INVALID_CHARACTER and self._is_windows( include_universal=True ): # Do not start a file or directory name with a space sanitized_filename = sanitized_filename.lstrip(" ") # Do not end a file or directory name with a space or a period sanitized_filename = sanitized_filename.rstrip(" ") if sanitized_filename not in (".", ".."): sanitized_filename = sanitized_filename.rstrip(" .") elif e.reason == ErrorReason.NULL_NAME: sanitized_filename = self._null_value_handler(e) if self._validate_after_sanitize: try: self._validator.validate(sanitized_filename) except ValidationError as e: raise ValidationError( description=str(e), reason=ErrorReason.INVALID_AFTER_SANITIZE, platform=self.platform, ) if isinstance(value, PurePath): return Path(sanitized_filename) # type: ignore return sanitized_filename # type: ignore def _get_sanitize_regexp(self) -> Pattern[str]: if self._is_windows(include_universal=True): return _RE_INVALID_WIN_FILENAME return _RE_INVALID_FILENAME class FileNameValidator(BaseValidator): _WINDOWS_RESERVED_FILE_NAMES: Final = ( ("CON", "PRN", "AUX", "CLOCK$", "NUL") + tuple(f"{name:s}{num:d}" for name, num in itertools.product(("COM", "LPT"), range(0, 10))) + tuple( f"{name:s}{ssd:s}" for name, ssd in itertools.product( ("COM", "LPT"), ("\N{SUPERSCRIPT ONE}", "\N{SUPERSCRIPT TWO}", "\N{SUPERSCRIPT THREE}"), ) ) ) _MACOS_RESERVED_FILE_NAMES: Final = (":",) @property def reserved_keywords(self) -> tuple[str, ...]: common_keywords = super().reserved_keywords if self._is_universal(): word_set = set( common_keywords + self._WINDOWS_RESERVED_FILE_NAMES + self._MACOS_RESERVED_FILE_NAMES ) elif self._is_windows(): word_set = set(common_keywords + self._WINDOWS_RESERVED_FILE_NAMES) elif self._is_posix() or self._is_macos(): word_set = set(common_keywords + self._MACOS_RESERVED_FILE_NAMES) else: word_set = set(common_keywords) return tuple(sorted(word_set)) def __init__( self, min_len: int = DEFAULT_MIN_LEN, max_len: int = _DEFAULT_MAX_FILENAME_LEN, fs_encoding: Optional[str] = None, platform: Optional[PlatformType] = None, check_reserved: bool = True, additional_reserved_names: Optional[Sequence[str]] = None, ) -> None: super().__init__( min_len=min_len, max_len=max_len, fs_encoding=fs_encoding, check_reserved=check_reserved, additional_reserved_names=additional_reserved_names, platform=platform, ) def validate(self, value: PathType) -> None: validate_pathtype(value, allow_whitespaces=not self._is_windows(include_universal=True)) unicode_filename = to_str(value) byte_ct = len(unicode_filename.encode(self._fs_encoding)) self.validate_abspath(unicode_filename) err_kwargs = { ErrorAttrKey.REASON: ErrorReason.INVALID_LENGTH, ErrorAttrKey.PLATFORM: self.platform, ErrorAttrKey.FS_ENCODING: self._fs_encoding, ErrorAttrKey.BYTE_COUNT: byte_ct, } if byte_ct > self.max_len: raise ValidationError( [ f"filename is too long: expected<={self.max_len:d} bytes, actual={byte_ct:d} bytes" ], **err_kwargs, ) if byte_ct < self.min_len: raise ValidationError( [ f"filename is too short: expected>={self.min_len:d} bytes, actual={byte_ct:d} bytes" ], **err_kwargs, ) self._validate_reserved_keywords(unicode_filename) self.__validate_universal_filename(unicode_filename) if self._is_windows(include_universal=True): self.__validate_win_filename(unicode_filename) def validate_abspath(self, value: str) -> None: err = ValidationError( description=f"found an absolute path ({value}), expected a filename", platform=self.platform, reason=ErrorReason.FOUND_ABS_PATH, ) if self._is_windows(include_universal=True): if is_nt_abspath(value): raise err if posixpath.isabs(value): raise err def __validate_universal_filename(self, unicode_filename: str) -> None: match = _RE_INVALID_FILENAME.findall(unicode_filename) if match: raise InvalidCharError( INVALID_CHAR_ERR_MSG_TMPL.format( invalid=findall_to_str(match), value=repr(unicode_filename) ), platform=Platform.UNIVERSAL, ) def __validate_win_filename(self, unicode_filename: str) -> None: match = _RE_INVALID_WIN_FILENAME.findall(unicode_filename) if match: raise InvalidCharError( INVALID_CHAR_ERR_MSG_TMPL.format( invalid=findall_to_str(match), value=repr(unicode_filename) ), platform=Platform.WINDOWS, ) if unicode_filename in (".", ".."): return KB2829981_err_tmpl = "{}. Refer: https://learn.microsoft.com/en-us/troubleshoot/windows-client/shell-experience/file-folder-name-whitespace-characters" # noqa: E501 if unicode_filename[-1] in (" ", "."): raise InvalidCharError( INVALID_CHAR_ERR_MSG_TMPL.format( invalid=re.escape(unicode_filename[-1]), value=repr(unicode_filename) ), platform=Platform.WINDOWS, description=KB2829981_err_tmpl.format( "Do not end a file or directory name with a space or a period" ), ) if unicode_filename[0] in (" "): raise InvalidCharError( INVALID_CHAR_ERR_MSG_TMPL.format( invalid=re.escape(unicode_filename[0]), value=repr(unicode_filename) ), platform=Platform.WINDOWS, description=KB2829981_err_tmpl.format( "Do not start a file or directory name with a space" ), ) def validate_filename( filename: PathType, platform: Optional[PlatformType] = None, min_len: int = DEFAULT_MIN_LEN, max_len: int = _DEFAULT_MAX_FILENAME_LEN, fs_encoding: Optional[str] = None, check_reserved: bool = True, additional_reserved_names: Optional[Sequence[str]] = None, ) -> None: """Verifying whether the ``filename`` is a valid file name or not. Args: filename: Filename to validate. platform: Target platform name of the filename. .. include:: platform.txt min_len: Minimum byte length of the ``filename``. The value must be greater or equal to one. Defaults to ``1``. max_len: Maximum byte length of the ``filename``. The value must be lower than: - ``Linux``: 4096 - ``macOS``: 1024 - ``Windows``: 260 - ``universal``: 260 Defaults to ``255``. fs_encoding: Filesystem encoding that is used to calculate the byte length of the filename. If |None|, get the encoding from the execution environment. check_reserved: If |True|, check the reserved names of the ``platform``. additional_reserved_names: Additional reserved names to check. Case insensitive. Raises: ValidationError (ErrorReason.INVALID_LENGTH): If the ``filename`` is longer than ``max_len`` characters. ValidationError (ErrorReason.INVALID_CHARACTER): If the ``filename`` includes invalid character(s) for a filename: |invalid_filename_chars|. The following characters are also invalid for Windows platforms: |invalid_win_filename_chars|. ValidationError (ErrorReason.RESERVED_NAME): If the ``filename`` equals the reserved name by OS. Windows reserved name is as follows: ``"CON"``, ``"PRN"``, ``"AUX"``, ``"NUL"``, ``"COM[1-9]"``, ``"LPT[1-9]"``. Example: :ref:`example-validate-filename` See Also: `Naming Files, Paths, and Namespaces - Win32 apps | Microsoft Docs `__ """ FileNameValidator( platform=platform, min_len=min_len, max_len=max_len, fs_encoding=fs_encoding, check_reserved=check_reserved, additional_reserved_names=additional_reserved_names, ).validate(filename) def is_valid_filename( filename: PathType, platform: Optional[PlatformType] = None, min_len: int = DEFAULT_MIN_LEN, max_len: Optional[int] = None, fs_encoding: Optional[str] = None, check_reserved: bool = True, additional_reserved_names: Optional[Sequence[str]] = None, ) -> bool: """Check whether the ``filename`` is a valid name or not. Args: filename: A filename to be checked. platform: Target platform name of the filename. Example: :ref:`example-is-valid-filename` See Also: :py:func:`.validate_filename()` """ return FileNameValidator( platform=platform, min_len=min_len, max_len=-1 if max_len is None else max_len, fs_encoding=fs_encoding, check_reserved=check_reserved, additional_reserved_names=additional_reserved_names, ).is_valid(filename) def sanitize_filename( filename: PathType, replacement_text: str = "", platform: Optional[PlatformType] = None, max_len: Optional[int] = _DEFAULT_MAX_FILENAME_LEN, fs_encoding: Optional[str] = None, check_reserved: Optional[bool] = None, null_value_handler: Optional[ValidationErrorHandler] = None, reserved_name_handler: Optional[ValidationErrorHandler] = None, additional_reserved_names: Optional[Sequence[str]] = None, validate_after_sanitize: bool = False, ) -> PathType: """Make a valid filename from a string. To make a valid filename, the function does the following: - Replace invalid characters as file names included in the ``filename`` with the ``replacement_text``. Invalid characters are: - unprintable characters - |invalid_filename_chars| - for Windows (or universal) only: |invalid_win_filename_chars| - Replace a value if a sanitized value is a reserved name by operating systems with a specified handler by ``reserved_name_handler``. Args: filename: Filename to sanitize. replacement_text: Replacement text for invalid characters. Defaults to ``""``. platform: Target platform name of the filename. .. include:: platform.txt max_len: Maximum byte length of the ``filename``. Truncate the name length if the ``filename`` length exceeds this value. Defaults to ``255``. fs_encoding: Filesystem encoding that is used to calculate the byte length of the filename. If |None|, get the encoding from the execution environment. check_reserved: [Deprecated] Use 'reserved_name_handler' instead. null_value_handler: Function called when a value after sanitization is an empty string. You can specify predefined handlers: - :py:func:`~.handler.NullValueHandler.return_null_string` - :py:func:`~.handler.NullValueHandler.return_timestamp` - :py:func:`~.handler.raise_error` Defaults to :py:func:`.handler.NullValueHandler.return_null_string` that just return ``""``. reserved_name_handler: Function called when a value after sanitization is a reserved name. You can specify predefined handlers: - :py:meth:`~.handler.ReservedNameHandler.add_leading_underscore` - :py:meth:`~.handler.ReservedNameHandler.add_trailing_underscore` - :py:meth:`~.handler.ReservedNameHandler.as_is` - :py:func:`~.handler.raise_error` Defaults to :py:func:`.handler.add_trailing_underscore`. additional_reserved_names: Additional reserved names to sanitize. Case insensitive. validate_after_sanitize: Execute validation after sanitization to the file name. Returns: Same type as the ``filename`` (str or PathLike object): Sanitized filename. Raises: ValueError: If the ``filename`` is an invalid filename. Example: :ref:`example-sanitize-filename` """ if check_reserved is not None: warnings.warn( "'check_reserved' is deprecated. Use 'reserved_name_handler' instead.", DeprecationWarning, ) if check_reserved is False: reserved_name_handler = ReservedNameHandler.as_is return FileNameSanitizer( platform=platform, max_len=-1 if max_len is None else max_len, fs_encoding=fs_encoding, null_value_handler=null_value_handler, reserved_name_handler=reserved_name_handler, additional_reserved_names=additional_reserved_names, validate_after_sanitize=validate_after_sanitize, ).sanitize(filename, replacement_text) pathvalidate-3.2.3/pathvalidate/_filepath.py000066400000000000000000000450451473576712500211710ustar00rootroot00000000000000""" .. codeauthor:: Tsuyoshi Hombashi """ import ntpath import os.path import posixpath import re import warnings from collections.abc import Sequence from pathlib import Path, PurePath from re import Pattern from typing import Final, Optional from ._base import AbstractSanitizer, AbstractValidator, BaseFile, BaseValidator from ._common import findall_to_str, is_nt_abspath, to_str, validate_pathtype from ._const import _NTFS_RESERVED_FILE_NAMES, DEFAULT_MIN_LEN, INVALID_CHAR_ERR_MSG_TMPL, Platform from ._filename import FileNameSanitizer, FileNameValidator from ._types import PathType, PlatformType from .error import ErrorAttrKey, ErrorReason, InvalidCharError, ReservedNameError, ValidationError from .handler import ReservedNameHandler, ValidationErrorHandler _RE_INVALID_PATH: Final = re.compile(f"[{re.escape(BaseFile._INVALID_PATH_CHARS):s}]", re.UNICODE) _RE_INVALID_WIN_PATH: Final = re.compile( f"[{re.escape(BaseFile._INVALID_WIN_PATH_CHARS):s}]", re.UNICODE ) class FilePathSanitizer(AbstractSanitizer): def __init__( self, max_len: int = -1, fs_encoding: Optional[str] = None, platform: Optional[PlatformType] = None, null_value_handler: Optional[ValidationErrorHandler] = None, reserved_name_handler: Optional[ValidationErrorHandler] = None, additional_reserved_names: Optional[Sequence[str]] = None, normalize: bool = True, validate_after_sanitize: bool = False, validator: Optional[AbstractValidator] = None, ) -> None: if validator: fpath_validator = validator else: fpath_validator = FilePathValidator( min_len=DEFAULT_MIN_LEN, max_len=max_len, fs_encoding=fs_encoding, check_reserved=True, additional_reserved_names=additional_reserved_names, platform=platform, ) super().__init__( max_len=max_len, fs_encoding=fs_encoding, validator=fpath_validator, null_value_handler=null_value_handler, reserved_name_handler=reserved_name_handler, additional_reserved_names=additional_reserved_names, platform=platform, validate_after_sanitize=validate_after_sanitize, ) self._sanitize_regexp = self._get_sanitize_regexp() self.__fname_sanitizer = FileNameSanitizer( max_len=self.max_len, fs_encoding=fs_encoding, null_value_handler=null_value_handler, reserved_name_handler=reserved_name_handler, additional_reserved_names=additional_reserved_names, platform=self.platform, validate_after_sanitize=validate_after_sanitize, ) self.__normalize = normalize if self._is_windows(include_universal=True): self.__split_drive = ntpath.splitdrive else: self.__split_drive = posixpath.splitdrive def sanitize(self, value: PathType, replacement_text: str = "") -> PathType: try: validate_pathtype(value, allow_whitespaces=not self._is_windows(include_universal=True)) except ValidationError as e: if e.reason == ErrorReason.NULL_NAME: if isinstance(value, PurePath): raise return self._null_value_handler(e) # type: ignore raise unicode_filepath = to_str(value) drive, unicode_filepath = self.__split_drive(unicode_filepath) unicode_filepath = self._sanitize_regexp.sub(replacement_text, unicode_filepath) if self.__normalize and unicode_filepath: unicode_filepath = os.path.normpath(unicode_filepath) sanitized_path = unicode_filepath sanitized_entries: list[str] = [] if drive: sanitized_entries.append(drive) for entry in sanitized_path.replace("\\", "/").split("/"): if entry in _NTFS_RESERVED_FILE_NAMES: sanitized_entries.append(f"{entry}_") continue sanitized_entry = str( self.__fname_sanitizer.sanitize(entry, replacement_text=replacement_text) ) if not sanitized_entry: if not sanitized_entries: sanitized_entries.append("") continue sanitized_entries.append(sanitized_entry) sanitized_path = self.__get_path_separator().join(sanitized_entries) try: self._validator.validate(sanitized_path) except ValidationError as e: if e.reason == ErrorReason.NULL_NAME: sanitized_path = self._null_value_handler(e) if self._validate_after_sanitize: self._validator.validate(sanitized_path) if isinstance(value, PurePath): return Path(sanitized_path) # type: ignore return sanitized_path # type: ignore def _get_sanitize_regexp(self) -> Pattern[str]: if self._is_windows(include_universal=True): return _RE_INVALID_WIN_PATH return _RE_INVALID_PATH def __get_path_separator(self) -> str: if self._is_windows(): return "\\" return "/" class FilePathValidator(BaseValidator): _RE_NTFS_RESERVED: Final = re.compile( "|".join(f"^/{re.escape(pattern)}$" for pattern in _NTFS_RESERVED_FILE_NAMES), re.IGNORECASE, ) _MACOS_RESERVED_FILE_PATHS: Final = ("/", ":") @property def reserved_keywords(self) -> tuple[str, ...]: common_keywords = super().reserved_keywords if any([self._is_universal(), self._is_posix(), self._is_macos()]): return common_keywords + self._MACOS_RESERVED_FILE_PATHS if self._is_linux(): return common_keywords + ("/",) return common_keywords def __init__( self, min_len: int = DEFAULT_MIN_LEN, max_len: int = -1, fs_encoding: Optional[str] = None, platform: Optional[PlatformType] = None, check_reserved: bool = True, additional_reserved_names: Optional[Sequence[str]] = None, ) -> None: super().__init__( min_len=min_len, max_len=max_len, fs_encoding=fs_encoding, check_reserved=check_reserved, additional_reserved_names=additional_reserved_names, platform=platform, ) self.__fname_validator = FileNameValidator( min_len=min_len, max_len=self.max_len, fs_encoding=fs_encoding, check_reserved=check_reserved, additional_reserved_names=additional_reserved_names, platform=platform, ) if self._is_windows(include_universal=True): self.__split_drive = ntpath.splitdrive else: self.__split_drive = posixpath.splitdrive def validate(self, value: PathType) -> None: validate_pathtype(value, allow_whitespaces=not self._is_windows(include_universal=True)) self.validate_abspath(value) _drive, tail = self.__split_drive(value) if not tail: return unicode_filepath = to_str(tail) byte_ct = len(unicode_filepath.encode(self._fs_encoding)) err_kwargs = { ErrorAttrKey.REASON: ErrorReason.INVALID_LENGTH, ErrorAttrKey.PLATFORM: self.platform, ErrorAttrKey.FS_ENCODING: self._fs_encoding, ErrorAttrKey.BYTE_COUNT: byte_ct, } if byte_ct > self.max_len: raise ValidationError( [ f"file path is too long: expected<={self.max_len:d} bytes, actual={byte_ct:d} bytes" ], **err_kwargs, ) if byte_ct < self.min_len: raise ValidationError( [ "file path is too short: expected>={:d} bytes, actual={:d} bytes".format( self.min_len, byte_ct ) ], **err_kwargs, ) self._validate_reserved_keywords(unicode_filepath) unicode_filepath = unicode_filepath.replace("\\", "/") for entry in unicode_filepath.split("/"): if not entry or entry in (".", ".."): continue self.__fname_validator.validate(entry) if self._is_windows(include_universal=True): self.__validate_win_filepath(unicode_filepath) else: self.__validate_unix_filepath(unicode_filepath) def validate_abspath(self, value: PathType) -> None: is_posix_abs = posixpath.isabs(value) is_nt_abs = is_nt_abspath(to_str(value)) if any([self._is_windows() and is_nt_abs, self._is_posix() and is_posix_abs]): return if self._is_universal() and any([is_nt_abs, is_posix_abs]): ValidationError( "platform-independent absolute file path is not supported", platform=self.platform, reason=ErrorReason.MALFORMED_ABS_PATH, ) err_object = ValidationError( description=( "an invalid absolute file path ({}) for the platform ({}).".format( value, self.platform.value ) + " to avoid the error, specify an appropriate platform corresponding to" + " the path format or 'auto'." ), platform=self.platform, reason=ErrorReason.MALFORMED_ABS_PATH, ) if self._is_windows(include_universal=True) and is_posix_abs: raise err_object if not self._is_windows(): drive, _tail = ntpath.splitdrive(value) if drive and is_nt_abs: raise err_object def __validate_unix_filepath(self, unicode_filepath: str) -> None: match = _RE_INVALID_PATH.findall(unicode_filepath) if match: raise InvalidCharError( INVALID_CHAR_ERR_MSG_TMPL.format( invalid=findall_to_str(match), value=repr(unicode_filepath) ) ) def __validate_win_filepath(self, unicode_filepath: str) -> None: match = _RE_INVALID_WIN_PATH.findall(unicode_filepath) if match: raise InvalidCharError( INVALID_CHAR_ERR_MSG_TMPL.format( invalid=findall_to_str(match), value=repr(unicode_filepath) ), platform=Platform.WINDOWS, ) _drive, value = self.__split_drive(unicode_filepath) if value: match_reserved = self._RE_NTFS_RESERVED.search(value) if match_reserved: reserved_name = match_reserved.group() raise ReservedNameError( f"'{reserved_name}' is a reserved name", reusable_name=False, reserved_name=reserved_name, platform=self.platform, ) def validate_filepath( file_path: PathType, platform: Optional[PlatformType] = None, min_len: int = DEFAULT_MIN_LEN, max_len: Optional[int] = None, fs_encoding: Optional[str] = None, check_reserved: bool = True, additional_reserved_names: Optional[Sequence[str]] = None, ) -> None: """Verifying whether the ``file_path`` is a valid file path or not. Args: file_path (PathType): File path to be validated. platform (Optional[PlatformType], optional): Target platform name of the file path. .. include:: platform.txt min_len (int, optional): Minimum byte length of the ``file_path``. The value must be greater or equal to one. Defaults to ``1``. max_len (Optional[int], optional): Maximum byte length of the ``file_path``. If the value is |None| or minus, automatically determined by the ``platform``: - ``Linux``: 4096 - ``macOS``: 1024 - ``Windows``: 260 - ``universal``: 260 fs_encoding (Optional[str], optional): Filesystem encoding that is used to calculate the byte length of the file path. If |None|, get the encoding from the execution environment. check_reserved (bool, optional): If |True|, check the reserved names of the ``platform``. Defaults to |True|. additional_reserved_names (Optional[Sequence[str]], optional): Additional reserved names to check. Raises: ValidationError (ErrorReason.INVALID_CHARACTER): If the ``file_path`` includes invalid char(s): |invalid_file_path_chars|. The following characters are also invalid for Windows platforms: |invalid_win_file_path_chars| ValidationError (ErrorReason.INVALID_LENGTH): If the ``file_path`` is longer than ``max_len`` characters. ValidationError: If ``file_path`` includes invalid values. Example: :ref:`example-validate-file-path` See Also: `Naming Files, Paths, and Namespaces - Win32 apps | Microsoft Docs `__ """ FilePathValidator( platform=platform, min_len=min_len, max_len=-1 if max_len is None else max_len, fs_encoding=fs_encoding, check_reserved=check_reserved, additional_reserved_names=additional_reserved_names, ).validate(file_path) def is_valid_filepath( file_path: PathType, platform: Optional[PlatformType] = None, min_len: int = DEFAULT_MIN_LEN, max_len: Optional[int] = None, fs_encoding: Optional[str] = None, check_reserved: bool = True, additional_reserved_names: Optional[Sequence[str]] = None, ) -> bool: """Check whether the ``file_path`` is a valid name or not. Args: file_path: A filepath to be checked. platform: Target platform name of the file path. Example: :ref:`example-is-valid-filepath` See Also: :py:func:`.validate_filepath()` """ return FilePathValidator( platform=platform, min_len=min_len, max_len=-1 if max_len is None else max_len, fs_encoding=fs_encoding, check_reserved=check_reserved, additional_reserved_names=additional_reserved_names, ).is_valid(file_path) def sanitize_filepath( file_path: PathType, replacement_text: str = "", platform: Optional[PlatformType] = None, max_len: Optional[int] = None, fs_encoding: Optional[str] = None, check_reserved: Optional[bool] = None, null_value_handler: Optional[ValidationErrorHandler] = None, reserved_name_handler: Optional[ValidationErrorHandler] = None, additional_reserved_names: Optional[Sequence[str]] = None, normalize: bool = True, validate_after_sanitize: bool = False, ) -> PathType: """Make a valid file path from a string. To make a valid file path, the function does the following: - Replace invalid characters for a file path within the ``file_path`` with the ``replacement_text``. Invalid characters are as follows: - unprintable characters - |invalid_file_path_chars| - for Windows (or universal) only: |invalid_win_file_path_chars| - Replace a value if a sanitized value is a reserved name by operating systems with a specified handler by ``reserved_name_handler``. Args: file_path: File path to sanitize. replacement_text: Replacement text for invalid characters. Defaults to ``""``. platform: Target platform name of the file path. .. include:: platform.txt max_len: Maximum byte length of the file path. Truncate the path if the value length exceeds the `max_len`. If the value is |None| or minus, ``max_len`` will automatically determined by the ``platform``: - ``Linux``: 4096 - ``macOS``: 1024 - ``Windows``: 260 - ``universal``: 260 fs_encoding: Filesystem encoding that is used to calculate the byte length of the file path. If |None|, get the encoding from the execution environment. check_reserved: [Deprecated] Use 'reserved_name_handler' instead. null_value_handler: Function called when a value after sanitization is an empty string. You can specify predefined handlers: - :py:func:`.handler.NullValueHandler.return_null_string` - :py:func:`.handler.NullValueHandler.return_timestamp` - :py:func:`.handler.raise_error` Defaults to :py:func:`.handler.NullValueHandler.return_null_string` that just return ``""``. reserved_name_handler: Function called when a value after sanitization is one of the reserved names. You can specify predefined handlers: - :py:meth:`~.handler.ReservedNameHandler.add_leading_underscore` - :py:meth:`~.handler.ReservedNameHandler.add_trailing_underscore` - :py:meth:`~.handler.ReservedNameHandler.as_is` - :py:func:`~.handler.raise_error` Defaults to :py:func:`.handler.add_trailing_underscore`. additional_reserved_names: Additional reserved names to sanitize. Case insensitive. normalize: If |True|, normalize the the file path. validate_after_sanitize: Execute validation after sanitization to the file path. Returns: Same type as the argument (str or PathLike object): Sanitized filepath. Raises: ValueError: If the ``file_path`` is an invalid file path. Example: :ref:`example-sanitize-file-path` """ if check_reserved is not None: warnings.warn( "'check_reserved' is deprecated. Use 'reserved_name_handler' instead.", DeprecationWarning, ) if check_reserved is False: reserved_name_handler = ReservedNameHandler.as_is return FilePathSanitizer( platform=platform, max_len=-1 if max_len is None else max_len, fs_encoding=fs_encoding, normalize=normalize, null_value_handler=null_value_handler, reserved_name_handler=reserved_name_handler, additional_reserved_names=additional_reserved_names, validate_after_sanitize=validate_after_sanitize, ).sanitize(file_path, replacement_text) pathvalidate-3.2.3/pathvalidate/_ltsv.py000066400000000000000000000023231473576712500203550ustar00rootroot00000000000000""" .. codeauthor:: Tsuyoshi Hombashi """ import re from typing import Final from ._common import to_str, validate_pathtype from .error import InvalidCharError __RE_INVALID_LTSV_LABEL: Final = re.compile("[^0-9A-Za-z_.-]", re.UNICODE) def validate_ltsv_label(label: str) -> None: """ Verifying whether ``label`` is a valid `Labeled Tab-separated Values (LTSV) `__ label or not. :param label: Label to validate. :raises pathvalidate.ValidationError: If invalid character(s) found in the ``label`` for a LTSV format label. """ validate_pathtype(label, allow_whitespaces=False) match_list = __RE_INVALID_LTSV_LABEL.findall(to_str(label)) if match_list: raise InvalidCharError(f"invalid character found for a LTSV format label: {match_list}") def sanitize_ltsv_label(label: str, replacement_text: str = "") -> str: """ Replace all of the symbols in text. :param label: Input text. :param replacement_text: Replacement text. :return: A replacement string. :rtype: str """ validate_pathtype(label, allow_whitespaces=False) return __RE_INVALID_LTSV_LABEL.sub(replacement_text, to_str(label)) pathvalidate-3.2.3/pathvalidate/_symbol.py000066400000000000000000000045051473576712500206760ustar00rootroot00000000000000""" .. codeauthor:: Tsuyoshi Hombashi """ import re from collections.abc import Sequence from typing import Final from ._common import ascii_symbols, to_str, unprintable_ascii_chars from .error import InvalidCharError __RE_SYMBOL: Final = re.compile( "[{}]".format(re.escape("".join(ascii_symbols + unprintable_ascii_chars))), re.UNICODE ) def validate_symbol(text: str) -> None: """ Verifying whether symbol(s) included in the ``text`` or not. Args: text: Input text to validate. Raises: ValidationError (ErrorReason.INVALID_CHARACTER): If symbol(s) included in the ``text``. """ match_list = __RE_SYMBOL.findall(to_str(text)) if match_list: raise InvalidCharError(f"invalid symbols found: {match_list}") def replace_symbol( text: str, replacement_text: str = "", exclude_symbols: Sequence[str] = [], is_replace_consecutive_chars: bool = False, is_strip: bool = False, ) -> str: """ Replace all of the symbols in the ``text``. Args: text: Input text. replacement_text: Replacement text. exclude_symbols: Symbols that were excluded from the replacement. is_replace_consecutive_chars: If |True|, replace consecutive multiple ``replacement_text`` characters to a single character. is_strip: If |True|, strip ``replacement_text`` from the beginning/end of the replacement text. Returns: A replacement string. Example: :ref:`example-sanitize-symbol` """ if exclude_symbols: regexp = re.compile( "[{}]".format( re.escape( "".join(set(ascii_symbols + unprintable_ascii_chars) - set(exclude_symbols)) ) ), re.UNICODE, ) else: regexp = __RE_SYMBOL try: new_text = regexp.sub(replacement_text, to_str(text)) except TypeError: raise TypeError("text must be a string") if not replacement_text: return new_text if is_replace_consecutive_chars: new_text = re.sub(f"{re.escape(replacement_text)}+", replacement_text, new_text) if is_strip: new_text = new_text.strip(replacement_text) return new_text pathvalidate-3.2.3/pathvalidate/_types.py000066400000000000000000000002641473576712500205330ustar00rootroot00000000000000from pathlib import Path from typing import TypeVar from ._const import Platform PathType = TypeVar("PathType", str, Path) PlatformType = TypeVar("PlatformType", str, Platform) pathvalidate-3.2.3/pathvalidate/argparse.py000066400000000000000000000017121473576712500210330ustar00rootroot00000000000000""" .. codeauthor:: Tsuyoshi Hombashi """ from argparse import ArgumentTypeError from ._filename import sanitize_filename, validate_filename from ._filepath import sanitize_filepath, validate_filepath from .error import ValidationError def validate_filename_arg(value: str) -> str: if not value: return "" try: validate_filename(value) except ValidationError as e: raise ArgumentTypeError(e) return value def validate_filepath_arg(value: str) -> str: if not value: return "" try: validate_filepath(value, platform="auto") except ValidationError as e: raise ArgumentTypeError(e) return value def sanitize_filename_arg(value: str) -> str: if not value: return "" return sanitize_filename(value) def sanitize_filepath_arg(value: str) -> str: if not value: return "" return sanitize_filepath(value, platform="auto") pathvalidate-3.2.3/pathvalidate/click.py000066400000000000000000000022351473576712500203150ustar00rootroot00000000000000""" .. codeauthor:: Tsuyoshi Hombashi """ from typing import Union import click from click import Context, Option, Parameter from ._filename import sanitize_filename, validate_filename from ._filepath import sanitize_filepath, validate_filepath from .error import ValidationError def validate_filename_arg(ctx: Context, param: Union[Option, Parameter], value: str) -> str: if not value: return "" try: validate_filename(value) except ValidationError as e: raise click.BadParameter(str(e)) return value def validate_filepath_arg(ctx: Context, param: Union[Option, Parameter], value: str) -> str: if not value: return "" try: validate_filepath(value) except ValidationError as e: raise click.BadParameter(str(e)) return value def sanitize_filename_arg(ctx: Context, param: Union[Option, Parameter], value: str) -> str: if not value: return "" return sanitize_filename(value) def sanitize_filepath_arg(ctx: Context, param: Union[Option, Parameter], value: str) -> str: if not value: return "" return sanitize_filepath(value) pathvalidate-3.2.3/pathvalidate/error.py000066400000000000000000000166441473576712500203720ustar00rootroot00000000000000""" .. codeauthor:: Tsuyoshi Hombashi """ import enum from typing import Final, Optional from ._const import Platform def _to_error_code(code: int) -> str: return f"PV{code:04d}" class ErrorAttrKey: BYTE_COUNT: Final = "byte_count" DESCRIPTION: Final = "description" FS_ENCODING: Final = "fs_encoding" PLATFORM: Final = "platform" REASON: Final = "reason" RESERVED_NAME: Final = "reserved_name" REUSABLE_NAME: Final = "reusable_name" @enum.unique class ErrorReason(enum.Enum): """ Validation error reasons. """ NULL_NAME = (_to_error_code(1001), "NULL_NAME", "the value must not be an empty string") RESERVED_NAME = ( _to_error_code(1002), "RESERVED_NAME", "found a reserved name by a platform", ) INVALID_CHARACTER = ( _to_error_code(1100), "INVALID_CHARACTER", "invalid characters found", ) INVALID_LENGTH = ( _to_error_code(1101), "INVALID_LENGTH", "found an invalid string length", ) FOUND_ABS_PATH = ( _to_error_code(1200), "FOUND_ABS_PATH", "found an absolute path where must be a relative path", ) MALFORMED_ABS_PATH = ( _to_error_code(1201), "MALFORMED_ABS_PATH", "found a malformed absolute path", ) INVALID_AFTER_SANITIZE = ( _to_error_code(2000), "INVALID_AFTER_SANITIZE", "found invalid value after sanitizing", ) @property def code(self) -> str: """str: Error code.""" return self.__code @property def name(self) -> str: """str: Error reason name.""" return self.__name @property def description(self) -> str: """str: Error reason description.""" return self.__description def __init__(self, code: str, name: str, description: str) -> None: self.__name = name self.__code = code self.__description = description def __str__(self) -> str: return f"[{self.__code}] {self.__description}" class ValidationError(ValueError): """ Exception class of validation errors. """ @property def platform(self) -> Optional[Platform]: """ :py:class:`~pathvalidate.Platform`: Platform information. """ return self.__platform @property def reason(self) -> ErrorReason: """ :py:class:`~pathvalidate.error.ErrorReason`: The cause of the error. """ return self.__reason @property def description(self) -> Optional[str]: """Optional[str]: Error description.""" return self.__description @property def reserved_name(self) -> str: """str: Reserved name.""" return self.__reserved_name @property def reusable_name(self) -> Optional[bool]: """Optional[bool]: Whether the name is reusable or not.""" return self.__reusable_name @property def fs_encoding(self) -> Optional[str]: """Optional[str]: File system encoding.""" return self.__fs_encoding @property def byte_count(self) -> Optional[int]: """Optional[int]: Byte count of the path.""" return self.__byte_count def __init__(self, *args, **kwargs) -> None: # type: ignore if ErrorAttrKey.REASON not in kwargs: raise ValueError(f"{ErrorAttrKey.REASON} must be specified") self.__reason: ErrorReason = kwargs.pop(ErrorAttrKey.REASON) self.__byte_count: Optional[int] = kwargs.pop(ErrorAttrKey.BYTE_COUNT, None) self.__platform: Optional[Platform] = kwargs.pop(ErrorAttrKey.PLATFORM, None) self.__description: Optional[str] = kwargs.pop(ErrorAttrKey.DESCRIPTION, None) self.__reserved_name: str = kwargs.pop(ErrorAttrKey.RESERVED_NAME, "") self.__reusable_name: Optional[bool] = kwargs.pop(ErrorAttrKey.REUSABLE_NAME, None) self.__fs_encoding: Optional[str] = kwargs.pop(ErrorAttrKey.FS_ENCODING, None) try: super().__init__(*args[0], **kwargs) except IndexError: super().__init__(*args, **kwargs) def as_slog(self) -> dict[str, str]: """Return a dictionary representation of the error. Returns: Dict[str, str]: A dictionary representation of the error. """ slog: dict[str, str] = { "code": self.reason.code, ErrorAttrKey.DESCRIPTION: self.reason.description, } if self.platform: slog[ErrorAttrKey.PLATFORM] = self.platform.value if self.description: slog[ErrorAttrKey.DESCRIPTION] = self.description if self.__reusable_name is not None: slog[ErrorAttrKey.REUSABLE_NAME] = str(self.__reusable_name) if self.__fs_encoding: slog[ErrorAttrKey.FS_ENCODING] = self.__fs_encoding if self.__byte_count: slog[ErrorAttrKey.BYTE_COUNT] = str(self.__byte_count) return slog def __str__(self) -> str: item_list = [] header = str(self.reason) if Exception.__str__(self): item_list.append(Exception.__str__(self)) if self.platform: item_list.append(f"{ErrorAttrKey.PLATFORM}={self.platform.value}") if self.description: item_list.append(f"{ErrorAttrKey.DESCRIPTION}={self.description}") if self.__reusable_name is not None: item_list.append(f"{ErrorAttrKey.REUSABLE_NAME}={self.reusable_name}") if self.__fs_encoding: item_list.append(f"{ErrorAttrKey.FS_ENCODING}={self.__fs_encoding}") if self.__byte_count is not None: item_list.append(f"{ErrorAttrKey.BYTE_COUNT}={self.__byte_count:,d}") if item_list: header += ": " return header + ", ".join(item_list).strip() def __repr__(self) -> str: return self.__str__() class NullNameError(ValidationError): """[Deprecated] Exception raised when a name is empty. """ def __init__(self, *args, **kwargs) -> None: # type: ignore kwargs[ErrorAttrKey.REASON] = ErrorReason.NULL_NAME super().__init__(args, **kwargs) class InvalidCharError(ValidationError): """ Exception raised when includes invalid character(s) within a string. """ def __init__(self, *args, **kwargs) -> None: # type: ignore[no-untyped-def] kwargs[ErrorAttrKey.REASON] = ErrorReason.INVALID_CHARACTER super().__init__(args, **kwargs) class ReservedNameError(ValidationError): """ Exception raised when a string matched a reserved name. """ def __init__(self, *args, **kwargs) -> None: # type: ignore[no-untyped-def] kwargs[ErrorAttrKey.REASON] = ErrorReason.RESERVED_NAME super().__init__(args, **kwargs) class ValidReservedNameError(ReservedNameError): """[Deprecated] Exception raised when a string matched a reserved name. However, it can be used as a name. """ def __init__(self, *args, **kwargs) -> None: # type: ignore[no-untyped-def] kwargs[ErrorAttrKey.REUSABLE_NAME] = True super().__init__(args, **kwargs) class InvalidReservedNameError(ReservedNameError): """[Deprecated] Exception raised when a string matched a reserved name. Moreover, the reserved name is invalid as a name. """ def __init__(self, *args, **kwargs) -> None: # type: ignore[no-untyped-def] kwargs[ErrorAttrKey.REUSABLE_NAME] = False super().__init__(args, **kwargs) pathvalidate-3.2.3/pathvalidate/handler.py000066400000000000000000000063031473576712500206450ustar00rootroot00000000000000""" .. codeauthor:: Tsuyoshi Hombashi """ import warnings from datetime import datetime from typing import Callable from .error import ValidationError ValidationErrorHandler = Callable[[ValidationError], str] def return_null_string(e: ValidationError) -> str: """Null value handler that always returns an empty string. Args: e (ValidationError): A validation error. Returns: str: An empty string. """ warnings.warn( "'return_null_string' is deprecated. Use 'NullValueHandler.return_null_string' instead.", DeprecationWarning, ) return "" def return_timestamp(e: ValidationError) -> str: """Null value handler that returns a timestamp of when the function was called. Args: e (ValidationError): A validation error. Returns: str: A timestamp. """ warnings.warn( "'return_timestamp' is deprecated. Use 'NullValueHandler.reserved_name_handler' instead.", DeprecationWarning, ) return str(datetime.now().timestamp()) def raise_error(e: ValidationError) -> str: """Null value handler that always raises an exception. Args: e (ValidationError): A validation error. Raises: ValidationError: Always raised. """ raise e class NullValueHandler: @classmethod def return_null_string(cls, e: ValidationError) -> str: """Null value handler that always returns an empty string. Args: e (ValidationError): A validation error. Returns: str: An empty string. """ return "" @classmethod def return_timestamp(cls, e: ValidationError) -> str: """Null value handler that returns a timestamp of when the function was called. Args: e (ValidationError): A validation error. Returns: str: A timestamp. """ return str(datetime.now().timestamp()) class ReservedNameHandler: @classmethod def add_leading_underscore(cls, e: ValidationError) -> str: """Reserved name handler that adds a leading underscore (``"_"``) to the name except for ``"."`` and ``".."``. Args: e (ValidationError): A reserved name error. Returns: str: The converted name. """ if e.reserved_name in (".", "..") or e.reusable_name: return e.reserved_name return f"_{e.reserved_name}" @classmethod def add_trailing_underscore(cls, e: ValidationError) -> str: """Reserved name handler that adds a trailing underscore (``"_"``) to the name except for ``"."`` and ``".."``. Args: e (ValidationError): A reserved name error. Returns: str: The converted name. """ if e.reserved_name in (".", "..") or e.reusable_name: return e.reserved_name return f"{e.reserved_name}_" @classmethod def as_is(cls, e: ValidationError) -> str: """Reserved name handler that returns the name as is. Args: e (ValidationError): A reserved name error. Returns: str: The name as is. """ return e.reserved_name pathvalidate-3.2.3/pathvalidate/py.typed000066400000000000000000000000001473576712500203410ustar00rootroot00000000000000pathvalidate-3.2.3/pyproject.toml000066400000000000000000000030131473576712500171170ustar00rootroot00000000000000[build-system] build-backend = "setuptools.build_meta" requires = [ "setuptools>=64", "setuptools_scm>=8", ] [tool.setuptools_scm] version_scheme = "guess-next-dev" local_scheme = "no-local-version" [tool.black] exclude = ''' /( \.eggs | \.git | \.mypy_cache | \.tox | \.pytype | \.venv | _build | buck-out | build | dist )/ | docs/conf.py ''' line-length = 100 target-version = ['py39', 'py310', 'py311', 'py312', 'py313'] [tool.isort] include_trailing_comma = true known_third_party = [ 'allpairspy', 'path', 'pytest', 'readmemaker', 'sphinx_rtd_theme', ] line_length = 100 lines_after_imports = 2 multi_line_output = 3 skip_glob = [ '*/.eggs/*', '*/.pytype/*', '*/.tox/*', ] [tool.coverage.run] branch = true source = ['pathvalidate'] [tool.coverage.report] exclude_lines = [ 'except ImportError', 'raise NotImplementedError', 'pass', 'ABCmeta', 'abstractmethod', 'warnings.warn', ] precision = 1 show_missing = true [tool.pyright] exclude = [ "**/node_modules", "**/__pycache__", ".tox", ".venv", "_build", "_sandbox", "build", "dist" ] pythonVersion = "3.9" [tool.pytest.ini_options] testpaths = [ "test", ] md_report = true md_report_color = "auto" md_report_verbose = 1 md_report_exclude_outcomes = [ "passed", ] discord_verbose = 1 [tool.ruff] line-length = 100 target-version = "py39" exclude = [ ".eggs/", ".tox/", "_sandbox/*", "build/", "docs/conf.py", "examples/pathvalidate_examples.py" ] pathvalidate-3.2.3/requirements/000077500000000000000000000000001473576712500167315ustar00rootroot00000000000000pathvalidate-3.2.3/requirements/docs_requirements.txt000066400000000000000000000000561473576712500232260ustar00rootroot00000000000000sphinx_rtd_theme>=1.2.2 Sphinx>=2.4 urllib3<2 pathvalidate-3.2.3/requirements/requirements.txt000066400000000000000000000000011473576712500222040ustar00rootroot00000000000000 pathvalidate-3.2.3/requirements/test_requirements.txt000066400000000000000000000001141473576712500232500ustar00rootroot00000000000000allpairspy>=2 click>=6.2 Faker>=1.0.8 pytest>=6.0.1 pytest-md-report>=0.6.2 pathvalidate-3.2.3/setup.py000066400000000000000000000061221473576712500157210ustar00rootroot00000000000000""" .. codeauthor:: Tsuyoshi Hombashi """ import os.path from typing import Final import setuptools MODULE_NAME: Final = "pathvalidate" REPOSITORY_URL: Final = f"https://github.com/thombashi/{MODULE_NAME:s}" REQUIREMENT_DIR: Final = "requirements" ENCODING: Final = "utf8" pkg_info: dict[str, str] = {} def get_release_command_class() -> dict[str, type[setuptools.Command]]: try: from releasecmd import ReleaseCommand except ImportError: return {} return {"release": ReleaseCommand} with open(os.path.join(MODULE_NAME, "__version__.py")) as f: exec(f.read(), pkg_info) with open("README.rst", encoding=ENCODING) as fp: long_description = fp.read() with open(os.path.join("docs", "pages", "introduction", "summary.txt"), encoding=ENCODING) as f: summary = f.read().strip() with open(os.path.join(REQUIREMENT_DIR, "test_requirements.txt")) as f: TESTS_REQUIRES = [line.strip() for line in f if line.strip()] with open(os.path.join(REQUIREMENT_DIR, "docs_requirements.txt")) as f: docs_requires = [line.strip() for line in f if line.strip()] setuptools.setup( name=MODULE_NAME, url=REPOSITORY_URL, author=pkg_info["__author__"], author_email=pkg_info["__email__"], description=summary, keywords=["file", "path", "validation", "validator", "sanitization", "sanitizer"], license=pkg_info["__license__"], long_description=long_description, long_description_content_type="text/x-rst", include_package_data=True, packages=setuptools.find_packages(exclude=["test*"]), package_data={MODULE_NAME: ["py.typed"]}, project_urls={ "Changelog": f"{REPOSITORY_URL:s}/blob/master/CHANGELOG.md", "Documentation": f"https://{MODULE_NAME:s}.rtfd.io/", "Source": REPOSITORY_URL, "Tracker": f"{REPOSITORY_URL:s}/issues", }, python_requires=">=3.9", extras_require={ "docs": docs_requires, "readme": [ "readmemaker>=1.2.0", "path>=13,<18", ], "test": TESTS_REQUIRES, }, classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Intended Audience :: Information Technology", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: System :: Filesystems", "Topic :: Text Processing", "Typing :: Typed", ], zip_safe=False, cmdclass=get_release_command_class(), ) pathvalidate-3.2.3/test/000077500000000000000000000000001473576712500151655ustar00rootroot00000000000000pathvalidate-3.2.3/test/__init__.py000066400000000000000000000000001473576712500172640ustar00rootroot00000000000000pathvalidate-3.2.3/test/_common.py000066400000000000000000000041171473576712500171710ustar00rootroot00000000000000""" .. codeauthor:: Tsuyoshi Hombashi """ import random import string from itertools import product alphanum_chars = tuple(x for x in string.digits + string.ascii_letters) INVALID_PATH_CHARS = ("\0",) INVALID_FILENAME_CHARS = ("/",) INVALID_WIN_PATH_CHARS = (":", "*", "?", '"', "<", ">", "|") + INVALID_PATH_CHARS INVALID_WIN_FILENAME_CHARS = INVALID_WIN_PATH_CHARS + INVALID_FILENAME_CHARS + ("\\",) VALID_FILENAME_CHARS = ( "!", "#", "$", "&", "'", "_", "=", "~", "^", "@", "`", "[", "]", "+", "-", ";", "{", "}", ",", ".", "(", ")", "%", ) VALID_PATH_CHARS = VALID_FILENAME_CHARS + ("/",) VALID_PLATFORM_NAMES = ["universal", "linux", "windows", "macos"] INVALID_JS_VAR_CHARS = INVALID_WIN_FILENAME_CHARS + ( "!", "#", "&", "'", "=", "~", "^", "@", "`", "[", "]", "+", "-", ";", "{", "}", ",", ".", "(", ")", "%", " ", "\t", "\n", "\r", "\f", "\v", ) INVALID_PYTHON_VAR_CHARS = INVALID_JS_VAR_CHARS + ("$",) WIN_RESERVED_FILE_NAMES = [ "CON", "con", "PRN", "prn", "AUX", "aux", "CLOCK$", "clock$", "NUL", "nul", ] + [ f"{name:s}{num}" for name, num in product( ["COM", "com", "LPT", "lpt"], list(range(0, 10)) + ["\N{SUPERSCRIPT ONE}", "\N{SUPERSCRIPT TWO}", "\N{SUPERSCRIPT THREE}"], ) ] NTFS_RESERVED_FILE_NAMES = [ "$Mft", "$MftMirr", "$LogFile", "$Volume", "$AttrDef", "$Bitmap", "$Boot", "$BadClus", "$Secure", "$Upcase", "$Extend", "$Quota", "$ObjId", "$Reparse", ] def is_faker_installed(): try: import faker # noqa except ImportError: return False return True def randstr(length, char_list=alphanum_chars): return "".join([random.choice(char_list) for _i in range(length)]) def platform_macos(): return "macos" def platform_windows(): return "windows" def platform_linux(): return "linux" pathvalidate-3.2.3/test/test_argparse.py000066400000000000000000000102561473576712500204060ustar00rootroot00000000000000import platform from argparse import ArgumentError, ArgumentParser import pytest from pathvalidate.argparse import ( sanitize_filename_arg, sanitize_filepath_arg, validate_filename_arg, validate_filepath_arg, ) class Test_validate_filename_arg: @pytest.mark.parametrize( ["value"], [ ["abc"], ["abc.txt"], [""], ], ) def test_normal(self, value): parser = ArgumentParser() parser.add_argument("filename", type=validate_filename_arg) assert parser.parse_args([value]).filename == value @pytest.mark.parametrize( ["value"], [ ["foo/abc"], ["a?c"], ["COM1"], ["a" * 8000], ], ) def test_exception(self, value): parser = ArgumentParser() parser.add_argument("filename", type=validate_filename_arg) try: parser.parse_args([value]) except SystemExit as e: assert isinstance(e.__context__, ArgumentError) else: raise RuntimeError() class Test_validate_filepath_arg: @pytest.mark.parametrize( ["value"], [ ["foo/abc"], ["foo/abc.txt"], [""], ], ) def test_normal(self, value): parser = ArgumentParser() parser.add_argument("filepath", type=validate_filepath_arg) assert parser.parse_args([value]).filepath == value @pytest.mark.skipif(platform.system() == "Windows", reason="platform dependent tests") @pytest.mark.parametrize( ["value"], [ ["a" * 8000], ], ) def test_exception_posix(self, value): parser = ArgumentParser() parser.add_argument("filepath", type=validate_filepath_arg) try: parser.parse_args([value]) except SystemExit as e: assert isinstance(e.__context__, ArgumentError) else: raise RuntimeError() @pytest.mark.skipif(platform.system() != "Windows", reason="platform dependent tests") @pytest.mark.parametrize( ["value"], [ ["foo/a?c"], ["COM1"], ], ) def test_exception_windows(self, value): parser = ArgumentParser() parser.add_argument("filepath", type=validate_filepath_arg) try: parser.parse_args([value]) except SystemExit as e: assert isinstance(e.__context__, ArgumentError) else: raise RuntimeError() class Test_sanitize_filename_arg: @pytest.mark.parametrize( ["value", "expected"], [ ["", ""], ["abc", "abc"], ["abc.txt", "abc.txt"], ["foo/abc", "fooabc"], ["a?c", "ac"], ["COM1", "COM1_"], ], ) def test_normal(self, value, expected): parser = ArgumentParser() parser.add_argument("filename", type=sanitize_filename_arg) assert parser.parse_args([value]).filename == expected class Test_sanitize_filepath_arg: @pytest.mark.skipif(platform.system() == "Windows", reason="platform dependent tests") @pytest.mark.parametrize( ["value", "expected"], [ ["", ""], ["abc", "abc"], ["abc.txt", "abc.txt"], ["foo/abc", "foo/abc"], ["a?c", "a?c"], ["COM1", "COM1"], ], ) def test_normal_posix(self, value, expected): parser = ArgumentParser() parser.add_argument("filepath", type=sanitize_filepath_arg) assert parser.parse_args([value]).filepath == expected @pytest.mark.skipif(platform.system() != "Windows", reason="platform dependent tests") @pytest.mark.parametrize( ["value", "expected"], [ ["", ""], ["abc", "abc"], ["abc.txt", "abc.txt"], ["foo/abc", "foo\\abc"], ["a?c", "ac"], ["COM1", "COM1_"], ], ) def test_normal_windows(self, value, expected): parser = ArgumentParser() parser.add_argument("filepath", type=sanitize_filepath_arg) assert parser.parse_args([value]).filepath == expected pathvalidate-3.2.3/test/test_click.py000066400000000000000000000044251473576712500176700ustar00rootroot00000000000000import click import pytest from click.testing import CliRunner from pathvalidate.click import ( sanitize_filename_arg, sanitize_filepath_arg, validate_filename_arg, validate_filepath_arg, ) @click.command() @click.option("--filename", callback=validate_filename_arg) @click.option("--filepath", callback=validate_filepath_arg) def cli_validate(filename, filepath): if filename: click.echo(filename) if filepath: click.echo(filepath) @click.command() @click.option("--filename", callback=sanitize_filename_arg) @click.option("--filepath", callback=sanitize_filepath_arg) def cli_sanitize(filename, filepath): if filename: click.echo(filename) if filepath: click.echo(filepath) class Test_validate: @pytest.mark.parametrize( ["value", "expected"], [ ["ab", 0], ["", 0], ["a/b", 2], ["a/?b", 2], ], ) def test_normal_filename(self, value, expected): runner = CliRunner() result = runner.invoke(cli_validate, ["--filename", value]) assert result.exit_code == expected @pytest.mark.parametrize( ["value", "expected"], [ ["ab", 0], ["", 0], ["a/b", 0], ["a/?b", 2], ], ) def test_normal_filepath(self, value, expected): runner = CliRunner() result = runner.invoke(cli_validate, ["--filepath", value]) assert result.exit_code == expected class Test_sanitize: @pytest.mark.parametrize( ["value", "expected"], [ ["ab", "ab"], ["", ""], ["a/b", "ab"], ["a/?b", "ab"], ], ) def test_normal_filename(self, value, expected): runner = CliRunner() result = runner.invoke(cli_sanitize, ["--filename", value]) assert result.output.strip() == expected @pytest.mark.parametrize( ["value", "expected"], [ ["ab", "ab"], ["", ""], ["a/b", "a/b"], ["a/?b", "a/b"], ], ) def test_normal_filepath(self, value, expected): runner = CliRunner() result = runner.invoke(cli_sanitize, ["--filepath", value]) assert result.output.strip() == expected pathvalidate-3.2.3/test/test_common.py000066400000000000000000000030411473576712500200640ustar00rootroot00000000000000""" .. codeauthor:: Tsuyoshi Hombashi """ import itertools import pytest from tcolorpy import tcolor from pathvalidate import ( ascii_symbols, replace_ansi_escape, replace_unprintable_char, unprintable_ascii_chars, ) from ._common import alphanum_chars class Test_replace_unprintable_char: TARGET_CHARS = unprintable_ascii_chars NOT_TARGET_CHARS = alphanum_chars + ascii_symbols REPLACE_TEXT_LIST = ["", "_"] @pytest.mark.parametrize( ["value", "replace_text", "expected"], [ ["A" + c + "B", rep, "A" + rep + "B"] for c, rep in itertools.product(TARGET_CHARS, REPLACE_TEXT_LIST) ] + [ ["A" + c + "B", rep, "A" + c + "B"] for c, rep in itertools.product(NOT_TARGET_CHARS, REPLACE_TEXT_LIST) ] + [ ["", "", ""], ], ) def test_normal(self, value, replace_text, expected): assert replace_unprintable_char(value, replace_text) == expected @pytest.mark.parametrize( ["value", "expected"], [ [None, TypeError], [1, TypeError], [True, TypeError], ], ) def test_abnormal(self, value, expected): with pytest.raises(expected): replace_unprintable_char(value) class Test_replace_ansi_escape: def test_normal(self): value = "test" ansi_value = tcolor(value, color="ffffff", bg_color="111111", styles=["bold"]) assert replace_ansi_escape(ansi_value) == value pathvalidate-3.2.3/test/test_error.py000066400000000000000000000025411473576712500177310ustar00rootroot00000000000000import pytest from pathvalidate import Platform from pathvalidate.error import ErrorReason, ValidationError, _to_error_code class Test_to_error_code: @pytest.mark.parametrize( ["value", "expected"], [ [1, "PV0001"], ], ) def test_normal(self, value, expected): assert _to_error_code(value) == expected class Test_str: @pytest.mark.parametrize( ["value", "expected"], [ [ ValidationError( description="hoge", platform=Platform.UNIVERSAL, reason=ErrorReason.INVALID_CHARACTER, ), "[PV1100] invalid characters found: platform=universal, description=hoge", ], ], ) def test_normal(self, value, expected): assert str(value) == expected class Test_as_slog: @pytest.mark.parametrize( ["value", "expected"], [ [ ValidationError( description="hoge", platform=Platform.UNIVERSAL, reason=ErrorReason.INVALID_CHARACTER, ), {"code": "PV1100", "description": "hoge", "platform": "universal"}, ], ], ) def test_normal(self, value, expected): assert value.as_slog() == expected pathvalidate-3.2.3/test/test_filename.py000066400000000000000000000670201473576712500203630ustar00rootroot00000000000000# type: ignore """ .. codeauthor:: Tsuyoshi Hombashi """ import platform as m_platform import random import sys from collections import OrderedDict from itertools import chain, product from pathlib import Path, PurePosixPath, PureWindowsPath import pytest from allpairspy import AllPairs from pathvalidate import ( ErrorReason, Platform, ValidationError, is_valid_filename, sanitize_filename, validate_filename, ) from pathvalidate._common import unprintable_ascii_chars from pathvalidate._filename import FileNameSanitizer, FileNameValidator from pathvalidate.handler import NullValueHandler, ReservedNameHandler, raise_error from ._common import ( INVALID_FILENAME_CHARS, INVALID_PATH_CHARS, INVALID_WIN_FILENAME_CHARS, INVALID_WIN_PATH_CHARS, NTFS_RESERVED_FILE_NAMES, VALID_FILENAME_CHARS, VALID_PLATFORM_NAMES, WIN_RESERVED_FILE_NAMES, is_faker_installed, platform_linux, platform_macos, platform_windows, randstr, ) nan = float("nan") inf = float("inf") random.seed(0) VALID_MULTIBYTE_NAMES = ["新しいテキスト ドキュメント.txt", "新規 Microsoft Excel Worksheet.xlsx"] class Test_FileSanitizer: @pytest.mark.parametrize( ["test_platform", "expected"], [ ["windows", Platform.WINDOWS], ["linux", Platform.LINUX], ["macos", Platform.MACOS], ], ) def test_normal_platform_auto(self, monkeypatch, test_platform, expected): if test_platform == "windows": patch = platform_windows elif test_platform == "linux": patch = platform_linux elif test_platform == "macos": patch = platform_macos else: raise ValueError(f"unexpected test platform: {test_platform}") monkeypatch.setattr(m_platform, "system", patch) assert FileNameSanitizer(255, platform="auto").platform == expected def test_normal_additional_reserved_names(self): sanitizer = FileNameSanitizer(additional_reserved_names=["abc"]) assert sanitizer.reserved_keywords == ("ABC",) class Test_FileNameValidator: @pytest.mark.parametrize( ["test_platform", "expected"], [ [ "windows", ( "AUX", "CLOCK$", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "COM²", "COM³", "COM¹", "CON", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", "LPT²", "LPT³", "LPT¹", "NUL", "PRN", ), ], ["linux", ()], ["macos", (":",)], ], ) def test_normal_reserved_keywords(self, test_platform, expected): assert FileNameValidator(255, platform=test_platform).reserved_keywords == expected def test_normal_additional_reserved_names(self): sanitizer = FileNameValidator(additional_reserved_names=["abc", "efg.txt"]) assert "ABC" in sanitizer.reserved_keywords assert "EFG.TXT" in sanitizer.reserved_keywords sanitizer = FileNameValidator(platform="windows", additional_reserved_names=["CON"]) assert ( sanitizer.reserved_keywords == FileNameValidator(platform="windows").reserved_keywords ) class Test_validate_filename: VALID_CHARS = VALID_FILENAME_CHARS INVALID_CHARS = INVALID_WIN_FILENAME_CHARS + unprintable_ascii_chars @pytest.mark.parametrize( ["value", "platform"], chain.from_iterable( [ [ args for args in product( ["{0}{1}{0}".format(randstr(64), valid_c)], VALID_PLATFORM_NAMES ) ] for valid_c in VALID_CHARS ] + [ [args for args in product([filename], VALID_PLATFORM_NAMES)] for filename in NTFS_RESERVED_FILE_NAMES ] ), ) def test_normal(self, value, platform): validate_filename(value, platform) assert is_valid_filename(value, platform=platform) @pytest.mark.parametrize( ["platform"], [ ["linux"], ["macos"], ["posix"], ], ) def test_normal_only_whitespaces(self, platform): value = " " validate_filename(value, platform) assert is_valid_filename(value, platform=platform) @pytest.mark.parametrize( ["platform"], [ ["windows"], ["universal"], ], ) def test_abnormal_only_whitespaces(self, platform): value = " " with pytest.raises(ValidationError) as e: validate_filename(value, platform=platform) assert e.value.reason == ErrorReason.NULL_NAME assert not is_valid_filename(value, platform) @pytest.mark.parametrize( ["value", "platform"], chain.from_iterable( [ [args for args in product([multibyte_name], VALID_PLATFORM_NAMES)] for multibyte_name in VALID_MULTIBYTE_NAMES ] ), ) def test_normal_multibyte(self, value, platform): validate_filename(value, platform) assert is_valid_filename(value, platform=platform) @pytest.mark.parametrize( ["value", "min_len", "expected"], [ ["lower than one", -1, None], ["valid", 5, None], ["invalid_length", 200, ErrorReason.INVALID_LENGTH], ], ) def test_min_len(self, value, min_len, expected): if expected is None: validate_filename(value, min_len=min_len) assert is_valid_filename(value, min_len=min_len) return with pytest.raises(ValidationError) as e: validate_filename(value, min_len=min_len) assert e.value.reason == expected assert e.value.fs_encoding assert e.value.byte_count > 0 @pytest.mark.parametrize( ["value", "platform", "max_len", "expected"], [ ["a" * 255, None, -1, None], ["a" * 5000, None, 10000, ErrorReason.INVALID_LENGTH], ["valid_length", "universal", 255, None], ["valid_length", Platform.UNIVERSAL, 255, None], ["invalid_length", None, 2, ErrorReason.INVALID_LENGTH], ], ) def test_max_len(self, value, platform, max_len, expected): if expected is None: validate_filename(value, platform=platform, max_len=max_len) assert is_valid_filename(value, platform=platform, max_len=max_len) return with pytest.raises(ValidationError) as e: validate_filename(value, platform=platform, max_len=max_len) assert e.value.reason == expected assert e.value.fs_encoding assert e.value.byte_count > 0 @pytest.mark.parametrize( ["value", "platform", "fs_encoding", "max_len", "expected"], [ ["あ" * 85, "universal", "utf-8", 255, None], ["あ" * 86, "universal", "utf-8", 255, ErrorReason.INVALID_LENGTH], ["あ" * 126, "universal", "utf-16", 255, None], ["あ" * 127, "universal", "utf-16", 255, ErrorReason.INVALID_LENGTH], ], ) def test_max_len_fs_encoding(self, value, platform, fs_encoding, max_len, expected): kwargs = { "platform": platform, "max_len": max_len, "fs_encoding": fs_encoding, } if expected is None: validate_filename(value, **kwargs) assert is_valid_filename(value, **kwargs) return with pytest.raises(ValidationError) as e: validate_filename(value, **kwargs) assert e.value.reason == expected assert e.value.fs_encoding assert e.value.byte_count > 0 @pytest.mark.parametrize( ["value", "min_len", "max_len", "expected"], [ ["minux max_len", 1, -1, None], ["zero max_len", 1, 0, None], ["valid length", 1, 255, None], ["eq min max", 10, 10, None], ["inversion", 100, 1, ValueError], ], ) def test_minmax_len(self, value, min_len, max_len, expected): if expected is None: validate_filename(value, min_len=min_len, max_len=max_len) assert is_valid_filename(value, min_len=min_len, max_len=max_len) return with pytest.raises(expected): validate_filename(value, min_len=min_len, max_len=max_len) @pytest.mark.skipif(not is_faker_installed(), reason="requires faker") @pytest.mark.parametrize(["locale"], [[None], ["ja_JP"]]) def test_locale_ja(self, locale): from faker import Factory fake = Factory.create(locale=locale, seed=1) for _ in range(100): filename = fake.file_name() validate_filename(filename) assert is_valid_filename(filename) @pytest.mark.parametrize( ["value", "platform"], chain.from_iterable( [ [ args for args in product( ["{0}{1}{0}".format(randstr(64), invalid_c)], VALID_PLATFORM_NAMES ) ] for invalid_c in INVALID_FILENAME_CHARS ] ), ) def test_exception_invalid_char(self, value, platform): with pytest.raises(ValidationError) as e: validate_filename(value, platform) assert e.value.reason == ErrorReason.INVALID_CHARACTER assert not is_valid_filename(value, platform=platform) @pytest.mark.parametrize( ["value", "platform"], [ ["a/b", Platform.UNIVERSAL], ["a*b", Platform.WINDOWS], [PurePosixPath("a/b"), Platform.UNIVERSAL], [PureWindowsPath("a/b"), Platform.WINDOWS], ], ) def test_exception_invalid_char_specific_target_platform(self, value, platform): with pytest.raises(ValidationError) as e: validate_filename(value, platform) assert e.value.reason == ErrorReason.INVALID_CHARACTER assert e.value.platform == platform @pytest.mark.parametrize( ["value", "platform"], [ ["{0}{1}{0}".format(randstr(64), invalid_c), platform] for invalid_c, platform in product( set(INVALID_WIN_PATH_CHARS).difference( set(INVALID_PATH_CHARS + INVALID_FILENAME_CHARS + unprintable_ascii_chars) ), ["windows", "universal"], ) ], ) def test_exception_win_invalid_char(self, value, platform): with pytest.raises(ValidationError) as e: validate_filename(value, platform=platform) assert e.value.reason == ErrorReason.INVALID_CHARACTER assert not is_valid_filename(value, platform=platform) @pytest.mark.parametrize( ["value", "platform", "expected"], [ [reserved_keyword, platform, ValidationError] for reserved_keyword, platform in product( WIN_RESERVED_FILE_NAMES, ["windows", "universal"] ) ] + [ [f"{reserved_keyword}.txt", platform, ValidationError] for reserved_keyword, platform in product( WIN_RESERVED_FILE_NAMES, ["windows", "universal"] ) ] + [ [f"{reserved_keyword}.tar.gz", platform, ValidationError] for reserved_keyword, platform in product( WIN_RESERVED_FILE_NAMES, ["windows", "universal"] ) ] + [ [reserved_keyword, platform, None] for reserved_keyword, platform in product([".", ".."], ["posix", "linux", "macos"]) ] + [ [":", "posix", ValidationError], [":", "macos", ValidationError], ], ) def test_reserved_name(self, value, platform, expected): if expected is None: validate_filename(value, platform=platform) else: with pytest.raises(expected) as e: validate_filename(value, platform=platform) assert e.value.reason == ErrorReason.RESERVED_NAME assert e.value.reserved_name assert e.value.reusable_name is False assert not is_valid_filename(value, platform=platform) @pytest.mark.parametrize( ["value", "arn", "expected"], [ ["abc", [], True], ["abc", ["abc"], False], ["Abc", ["abc"], False], ["ABC", ["abc"], False], ["abc.txt", ["abc.txt"], False], [".", [".", ".."], False], ["..", [".", ".."], False], ], ) def test_normal_additional_reserved_names(self, value, arn, expected): assert is_valid_filename(value, additional_reserved_names=arn) == expected @pytest.mark.parametrize( ["platform", "value", "expected", "reason"], [ [win_abspath, platform, None, None] for win_abspath, platform in product( ["linux", "macos", "posix"], ["\\", "\\\\", "\\ ", "C:\\", "c:\\", "\\xyz", "\\xyz "], ) ] + [ [win_abspath, platform, ValidationError, ErrorReason.FOUND_ABS_PATH] for win_abspath, platform in product(["windows", "universal"], ["\\\\", "C:\\", "c:\\"]) ] + [ [win_abspath, platform, ValidationError, ErrorReason.INVALID_CHARACTER] for win_abspath, platform in product( ["windows", "universal"], ["\\", "\\ ", "\\xyz", "\\xyz "] ) ], ) def test_win_abs_path(self, platform, value, expected, reason): if expected is None: validate_filename(value, platform=platform) else: with pytest.raises(expected) as e: validate_filename(value, platform=platform) assert e.value.reason == reason @pytest.mark.parametrize( ["value", "platform"], [ [value, platform] for value, platform in product( ["a/b.txt", "/a/b.txt", "c:\\Users"], ["windows", "universal"] ) ], ) def test_exception_filepath(self, value, platform): with pytest.raises(ValidationError) as e: validate_filename(value, platform=platform) assert e.value.reason in [ErrorReason.FOUND_ABS_PATH, ErrorReason.INVALID_CHARACTER] assert not is_valid_filename(value, platform=platform) @pytest.mark.parametrize( ["value", "platform", "expected"], [ [value, platform, ErrorReason.INVALID_CHARACTER] for value, platform in product(["asdf\rsdf"], ["windows", "universal"]) ], ) def test_exception_escape_err_msg(self, value, platform, expected): with pytest.raises(ValidationError) as e: print(platform, repr(value)) validate_filename(value, platform=platform) assert e.value.reason == ErrorReason.INVALID_CHARACTER assert str(e.value) == ( r"[PV1100] invalid characters found: invalids=('\r'), value='asdf\rsdf', " "platform=Windows" ) # noqa @pytest.mark.parametrize( ["value", "expected"], [ [None, ValueError], ["", ValidationError], ], ) def test_exception_null_value(self, value, expected): with pytest.raises(expected): validate_filename(value) assert not is_valid_filename(value) class Test_sanitize_filename: SANITIZE_CHARS = INVALID_WIN_FILENAME_CHARS + unprintable_ascii_chars NOT_SANITIZE_CHARS = VALID_FILENAME_CHARS REPLACE_TEXT_LIST = ["", "_"] @pytest.mark.parametrize( ["platform", "value", "replace_text", "expected"], [ ["universal", "A" + c + "B", rep, "A" + rep + "B"] for c, rep in product( INVALID_WIN_FILENAME_CHARS + unprintable_ascii_chars, REPLACE_TEXT_LIST ) ] + [ ["universal", "A" + c + "B", rep, "A" + c + "B"] for c, rep in product(NOT_SANITIZE_CHARS, REPLACE_TEXT_LIST) ] + [ [pair.platform, "A" + pair.c + "B", pair.repl, "A" + pair.repl + "B"] for pair in AllPairs( OrderedDict( { "platform": [ "posix", "linux", "macos", Platform.POSIX, Platform.LINUX, Platform.MACOS, ], "c": INVALID_PATH_CHARS + unprintable_ascii_chars, "repl": REPLACE_TEXT_LIST, } ) ) ] + [ [pair.platform, "A" + pair.c + "B", pair.repl, "A" + pair.c + "B"] for pair in AllPairs( OrderedDict( { "platform": [ "posix", "linux", "macos", Platform.POSIX, Platform.LINUX, Platform.MACOS, ], "c": [":", "*", "?", '"', "<", ">", "|"], "repl": REPLACE_TEXT_LIST, } ) ) ], ) def test_normal_str(self, platform, value, replace_text, expected): sanitized_name = sanitize_filename(value, platform=platform, replacement_text=replace_text) assert sanitized_name == expected assert isinstance(sanitized_name, str) validate_filename(sanitized_name, platform=platform) assert is_valid_filename(sanitized_name, platform=platform) @pytest.mark.parametrize( ["value", "replace_text", "expected"], [ [Path("A" + c + "B"), rep, Path("A" + rep + "B")] for c, rep in product(SANITIZE_CHARS, REPLACE_TEXT_LIST) ] + [ [Path("A" + c + "B"), rep, Path("A" + c + "B")] for c, rep in product(NOT_SANITIZE_CHARS, REPLACE_TEXT_LIST) ], ) def test_normal_pathlike(self, value, replace_text, expected): sanitized_name = sanitize_filename(value, replace_text) assert sanitized_name == expected assert isinstance(sanitized_name, Path) validate_filename(sanitized_name) assert is_valid_filename(sanitized_name) @pytest.mark.parametrize( ["value"], [ [None], [""], ["/"], ["//"], ["?"], ], ) def test_normal_null_value_handler(self, value): assert ( sanitize_filename(value, null_value_handler=NullValueHandler.return_null_string) == "" ) assert sanitize_filename(value, null_value_handler=NullValueHandler.return_timestamp) != "" with pytest.raises(ValidationError): sanitize_filename(value, null_value_handler=raise_error) @pytest.mark.parametrize( ["value", "replace_text", "expected"], [ ["あい/うえお.txt", "", "あいうえお.txt"], ["属/性.txt", "-", "属-性.txt"], ], ) def test_normal_multibyte(self, value, replace_text, expected): sanitized_name = sanitize_filename(value, replace_text) assert sanitized_name == expected validate_filename(sanitized_name) assert is_valid_filename(sanitized_name) @pytest.mark.parametrize( ["value", "max_len", "expected"], [ ["a" * 10, 255, 10], ["invalid_length" * 100, 255, 255], ["invalid_length" * 100, 10, 10], ], ) def test_normal_max_len(self, value, max_len, expected): filename = sanitize_filename(value, max_len=max_len) assert len(filename) == expected assert is_valid_filename(filename, max_len=max_len) @pytest.mark.parametrize( ["value", "test_platform", "expected"], [ [reserved.lower(), "windows", reserved.lower() + "_"] for reserved in WIN_RESERVED_FILE_NAMES ] + [ [f"{reserved_keyword}.txt", platform, f"{reserved_keyword}_.txt"] for reserved_keyword, platform in product( WIN_RESERVED_FILE_NAMES, ["windows", "universal"] ) if reserved_keyword not in [".", ".."] ] + [ [reserved.upper(), "windows", reserved.upper() + "_"] for reserved in WIN_RESERVED_FILE_NAMES ] + [ [reserved_keyword, platform, reserved_keyword] for reserved_keyword, platform in product([".", ".."], ["windows", "universal"]) ], ) def test_normal_reserved_name(self, value, test_platform, expected): filename = sanitize_filename(value, platform=test_platform) assert filename == expected assert is_valid_filename(filename, platform=test_platform) @pytest.mark.parametrize( ["value", "reserved_name_handler", "expected"], [ ["CON", ReservedNameHandler.add_trailing_underscore, "CON_"], ["CON", ReservedNameHandler.add_leading_underscore, "_CON"], ["CON", ReservedNameHandler.as_is, "CON"], ], ) def test_normal_reserved_name_handler(self, value, reserved_name_handler, expected): for platform in ["windows", "universal"]: assert ( sanitize_filename( value, platform=platform, reserved_name_handler=reserved_name_handler ) == expected ) @pytest.mark.parametrize( ["value", "test_platform", "arn", "expected"], [ [".", "windows", [".", ".."], "._"], [".", "universal", [".", ".."], "._"], ["..", "windows", [".", ".."], ".._"], ["..", "universal", [".", ".."], ".._"], ["...", "linux", [".", ".."], "..."], ], ) def test_normal_custom_reserved_name_handler_for_dot_files( self, value, test_platform, arn, expected ): def always_add_trailing_underscore(e: ValidationError) -> str: if e.reusable_name: return e.reserved_name return f"{e.reserved_name}_" assert ( sanitize_filename( value, platform=test_platform, reserved_name_handler=always_add_trailing_underscore, additional_reserved_names=arn, ) == expected ) def test_exception_reserved_name_handler(self): for platform in ["windows", "universal"]: with pytest.raises(ValidationError) as e: sanitize_filename("CON", platform=platform, reserved_name_handler=raise_error) assert e.value.reason == ErrorReason.RESERVED_NAME @pytest.mark.parametrize( ["value", "arn", "expected"], [ ["abc", [], "abc"], ["abc", ["abc"], "abc_"], ], ) def test_normal_additional_reserved_names(self, value, arn, expected): for platform in ["windows", "universal"]: assert ( sanitize_filename( value, platform=platform, additional_reserved_names=arn, ) == expected ), platform @pytest.mark.parametrize( ["value", "check_reserved", "expected"], [ ["CON", True, "CON_"], ["CON", False, "CON"], ], ) def test_normal_check_reserved(self, value, check_reserved, expected): for platform in ["windows", "universal"]: assert ( sanitize_filename(value, platform=platform, check_reserved=check_reserved) == expected ) @pytest.mark.parametrize( ["platform", "value", "expected"], [ ["windows", "period.", "period"], ["windows", "space ", "space"], ["windows", " space ", "space"], ["windows", "space_and_period .", "space_and_period"], ["windows", "space_and_period. ", "space_and_period"], ["windows", " .space_and_period", ".space_and_period"], ["windows", ". space_and_period", ". space_and_period"], ["windows", ". ", "."], ["windows", " .", "."], ["windows", " . ", "."], ["windows", ".. ", ".."], ["linux", "period.", "period."], ["linux", "space ", "space "], ["linux", "space_and_period. ", "space_and_period. "], ["linux", "...", "..."], ["universal", "period.", "period"], ["universal", "space ", "space"], ["universal", "space_and_period .", "space_and_period"], ["universal", ". ", "."], ["universal", " .", "."], ["universal", " . ", "."], ["universal", ".. ", ".."], ], ) def test_normal_space_or_period_at_tail(self, platform, value, expected): filename = sanitize_filename(value, platform=platform) assert filename == expected assert is_valid_filename(filename, platform=platform) @pytest.mark.parametrize( ["platform", "value"], [ [platform, value] for platform, value in product( ["windows", "universal"], [ "\a\r", ], ) ], ) def test_exception_invalid_after_sanitize(self, platform, value): kwargs = { "platform": platform, "replacement_text": "", "validate_after_sanitize": False, } print(f"'{sanitize_filename(value, **kwargs)}'", file=sys.stderr) kwargs["validate_after_sanitize"] = True with pytest.raises(ValidationError) as e: sanitize_filename(value, **kwargs) assert e.value.reason == ErrorReason.INVALID_AFTER_SANITIZE @pytest.mark.parametrize( ["value", "expected"], [ [1, TypeError], [True, TypeError], ], ) def test_exception_type(self, value, expected): with pytest.raises(expected): sanitize_filename(value) assert not is_valid_filename(value) @pytest.mark.parametrize( ["value", "platform", "fs_encoding", "max_len", "expected"], [ ["あ" * 85, "universal", "utf-8", 255, "あ" * 85], ["あ" * 86, "universal", "utf-8", 255, "あ" * 85], ["あ" * 126, "universal", "utf-16", 255, "あ" * 126], ["あ" * 127, "universal", "utf-16", 255, "あ" * 126], ], ) def test_max_len_fs_encoding(self, value, platform, fs_encoding, max_len, expected): kwargs = { "platform": platform, "max_len": max_len, "fs_encoding": fs_encoding, } assert sanitize_filename(value, **kwargs) == expected pathvalidate-3.2.3/test/test_filepath.py000066400000000000000000001025301473576712500203730ustar00rootroot00000000000000# type: ignore """ .. codeauthor:: Tsuyoshi Hombashi """ import platform as m_platform import random import sys from collections import OrderedDict from itertools import chain, product from pathlib import Path import pytest from allpairspy import AllPairs from pathvalidate import ( ErrorReason, Platform, ValidationError, is_valid_filepath, sanitize_filepath, validate_filepath, ) from pathvalidate._common import unprintable_ascii_chars from pathvalidate._filepath import FilePathSanitizer, FilePathValidator from pathvalidate.handler import NullValueHandler, ReservedNameHandler, raise_error from ._common import ( INVALID_PATH_CHARS, INVALID_WIN_PATH_CHARS, NTFS_RESERVED_FILE_NAMES, VALID_PATH_CHARS, WIN_RESERVED_FILE_NAMES, is_faker_installed, platform_linux, platform_macos, platform_windows, randstr, ) nan = float("nan") inf = float("inf") random.seed(0) class Test_FileSanitizer: @pytest.mark.parametrize( ["test_platform", "expected"], [ ["windows", Platform.WINDOWS], ["linux", Platform.LINUX], ["macos", Platform.MACOS], ], ) def test_normal_platform_auto(self, monkeypatch, test_platform, expected): if test_platform == "windows": patch = platform_windows elif test_platform == "linux": patch = platform_linux elif test_platform == "macos": patch = platform_macos else: raise ValueError(f"unexpected test platform: {test_platform}") monkeypatch.setattr(m_platform, "system", patch) assert FilePathSanitizer(255, platform="auto").platform == expected def test_normal_additional_reserved_names(self): sanitizer = FilePathSanitizer(additional_reserved_names=["abc"]) assert sanitizer.reserved_keywords == ("ABC",) class Test_FilePathValidator: @pytest.mark.parametrize( ["test_platform", "expected"], [ ["windows", tuple()], ["posix", ("/", ":")], ["linux", ("/",)], ["macos", ("/", ":")], ], ) def test_normal_reserved_keywords(self, test_platform, expected): assert FilePathValidator(255, platform=test_platform).reserved_keywords == expected def test_normal_additional_reserved_names(self): sanitizer = FilePathValidator(additional_reserved_names=["abc"]) assert "ABC" in sanitizer.reserved_keywords class Test_validate_filepath: VALID_CHARS = VALID_PATH_CHARS VALID_MULTIBYTE_PATHS = [ "c:\\Users\\新しいフォルダー\\あいうえお.txt", "D:\\新しいフォルダー\\ユーザ属性.txt", ] WIN_VALID_PATHS = [ r"C:\Program Files (x86)\Microsoft", "D:\\Users\\\\est\\AppData\\Local\\Temp\\pytest-of-test\\pytest-0\\\\hoge.csv", "D:/Users/test/AppData/Local/Temp/pytest-of-test/pytest-0/test_exception__hoge_csv_heade1/hoge.csv", # noqa "C:\\Users\\est\\AppData/Local\\Temp/pytest-of-test\\pytest-0/\\hoge.csv", "C:\\Users", "C:\\", "\\Users", r"\\fileserver\public", ] @pytest.mark.parametrize( ["value", "platform"], chain.from_iterable( [ [ args for args in product( ["/{0}/{1}{0}".format(randstr(64), valid_c)], ["linux", "macos"] ) ] for valid_c in VALID_CHARS ] ), ) def test_normal(self, value, platform): validate_filepath(value, platform) assert is_valid_filepath(value, platform=platform) @pytest.mark.parametrize( ["platform"], [ ["linux"], ["macos"], ["posix"], ], ) def test_normal_only_whitespaces(self, platform): value = " " validate_filepath(value, platform) assert is_valid_filepath(value, platform=platform) @pytest.mark.parametrize( ["platform"], [ ["windows"], ["universal"], ], ) def test_abnormal_only_whitespaces(self, platform): value = " " with pytest.raises(ValidationError) as e: validate_filepath(value, platform=platform) assert e.value.reason == ErrorReason.NULL assert not is_valid_filepath(value, platform) @pytest.mark.parametrize( ["value", "platform"], chain.from_iterable( [ [args for args in product([valid_path], ["windows"])] for valid_path in VALID_MULTIBYTE_PATHS ] ), ) def test_normal_multibyte(self, value, platform): validate_filepath(value, platform) assert is_valid_filepath(value, platform=platform) @pytest.mark.parametrize(["value"], [[valid_path] for valid_path in WIN_VALID_PATHS]) def test_normal_win(self, value): platform = "windows" validate_filepath(value, platform=platform) assert is_valid_filepath(value, platform=platform) @pytest.mark.parametrize( ["value", "min_len", "expected"], [ ["lower than one", -1, None], ["valid", 5, None], ["invalid_length", 200, ErrorReason.INVALID_LENGTH], ], ) def test_normal_min_len(self, value, min_len, expected): if expected is None: validate_filepath(value, min_len=min_len) assert is_valid_filepath(value, min_len=min_len) return with pytest.raises(ValidationError) as e: validate_filepath(value, min_len=min_len) assert e.value.reason == expected assert e.value.fs_encoding assert e.value.byte_count assert e.value.byte_count > 0 @pytest.mark.parametrize( ["value", "platform", "max_len", "expected"], [ ["a" * 4096, "linux", None, None], ["a" * 4097, "linux", None, ErrorReason.INVALID_LENGTH], ["a" * 1024, "posix", None, None], ["a" * 1025, "posix", None, ErrorReason.INVALID_LENGTH], ["a" * 4097, Platform.LINUX, None, ErrorReason.INVALID_LENGTH], ["a" * 255, "linux", 100, ErrorReason.INVALID_LENGTH], ["a" * 5000, "windows", 10000, ErrorReason.INVALID_LENGTH], ["a" * 260, "windows", None, None], ["a" * 300, "windows", 1024, ErrorReason.INVALID_LENGTH], ["a" * 261, Platform.WINDOWS, None, ErrorReason.INVALID_LENGTH], ["a" * 261, "windows", None, ErrorReason.INVALID_LENGTH], ["a" * 260, "universal", None, None], ["a" * 261, "universal", None, ErrorReason.INVALID_LENGTH], ["a" * 300, "universal", 1024, ErrorReason.INVALID_LENGTH], ["a" * 261, Platform.UNIVERSAL, None, ErrorReason.INVALID_LENGTH], ], ) def test_normal_max_len(self, value, platform, max_len, expected): kwargs = { "platform": platform, "max_len": max_len, } if expected is None: validate_filepath(value, **kwargs) assert is_valid_filepath(value, **kwargs) return with pytest.raises(ValidationError) as e: validate_filepath(value, **kwargs) assert e.value.reason == ErrorReason.INVALID_LENGTH assert e.value.fs_encoding assert e.value.byte_count assert e.value.byte_count > 0 @pytest.mark.parametrize( ["value", "platform", "fs_encoding", "max_len", "expected"], [ ["/tmp/" + "あ" * 83, "linux", "utf-8", 255, None], ["/tmp/" + "あ" * 84, "linux", "utf-8", 255, ErrorReason.INVALID_LENGTH], ["/tmp/" + "あ" * 121, "linux", "utf-16", 255, None], ["/tmp/" + "あ" * 122, "linux", "utf-16", 255, ErrorReason.INVALID_LENGTH], ], ) def test_max_len_fs_encoding(self, value, platform, fs_encoding, max_len, expected): kwargs = { "platform": platform, "max_len": max_len, "fs_encoding": fs_encoding, } if expected is None: validate_filepath(value, **kwargs) assert is_valid_filepath(value, **kwargs) return with pytest.raises(ValidationError) as e: validate_filepath(value, **kwargs) assert e.value.reason == expected assert e.value.fs_encoding assert e.value.byte_count assert e.value.byte_count > 0 @pytest.mark.parametrize( ["value", "min_len", "max_len", "expected"], [ ["valid length", 1, 255, None], ["minus min_len", -2, 100, None], ["minus max_len", -3, -2, None], ["zero max_len", -2, 0, None], ["eq min max", 10, 10, None], ["inversion", 100, 1, ValueError], ], ) def test_minmax_len(self, value, min_len, max_len, expected): kwargs = { "min_len": min_len, "max_len": max_len, } if expected is None: validate_filepath(value, **kwargs) assert is_valid_filepath(value, **kwargs) return with pytest.raises(expected): validate_filepath(value, **kwargs) @pytest.mark.parametrize( ["test_platform", "value", "expected"], [ ["linux", "/a/b/c.txt", None], ["linux", "C:\\a\\b\\c.txt", ValidationError], ["windows", "/a/b/c.txt", ValidationError], ["windows", "C:\\a\\b\\c.txt", None], ["universal", "/a/b/c.txt", ValidationError], ["universal", "C:\\a\\b\\c.txt", ValidationError], ], ) def test_abs_path(self, test_platform, value, expected): if expected is None: validate_filepath(value, platform=test_platform) assert is_valid_filepath(value, platform=test_platform) return with pytest.raises(expected): validate_filepath(value, platform=test_platform) @pytest.mark.skipif(m_platform.system() != "Windows", reason="platform dependent tests") @pytest.mark.parametrize( ["value", "expected"], [ ["C:\\a\\b\\c.txt", None], ], ) def test_auto_platform_win(self, value, expected): if expected is None: validate_filepath(value, platform="auto") assert is_valid_filepath(value, platform="auto") return with pytest.raises(expected): validate_filepath(value, platform="auto") @pytest.mark.skipif(m_platform.system() != "Linux", reason="platform dependent tests") @pytest.mark.parametrize( ["value", "expected"], [ ["/a/b/c.txt", None], ["C:\\a\\b\\c.txt", ValidationError], ], ) def test_auto_platform_linux(self, value, expected): if expected is None: validate_filepath(value, platform="auto") assert is_valid_filepath(value, platform="auto") return with pytest.raises(expected): validate_filepath(value, platform="auto") @pytest.mark.parametrize( ["test_platform", "value", "expected"], [ ["linux", "a/b/c.txt", None], ["linux", "a//b?/c.txt", None], ["linux", "../a/./../b/c.txt", None], ["windows", "a/b/c.txt", None], ["windows", "a//b?/c.txt", ValidationError], ["windows", "../a/./../b/c.txt", None], ["universal", "a/b/c.txt", None], ["universal", "./a/b/c.txt", None], ["universal", "../a/./../b/c.txt", None], ["universal", "a//b?/c.txt", ValidationError], ], ) def test_relative_path(self, test_platform, value, expected): if expected is None: validate_filepath(value, platform=test_platform) assert is_valid_filepath(value, platform=test_platform) else: with pytest.raises(expected): validate_filepath(value, platform=test_platform) @pytest.mark.parametrize( ["platform", "value"], [ ["linux", "period."], ["linux", "space "], ["linux", "space_and_period. "], ], ) def test_normal_space_or_period_at_tail(self, platform, value): validate_filepath(value, platform=platform) assert is_valid_filepath(value, platform=platform) @pytest.mark.parametrize( ["platform", "value"], [ ["windows", "period."], ["windows", "space "], ["windows", "space_and_period ."], ["windows", "space_and_period. "], ["universal", "period."], ["universal", "space "], ["universal", "space_and_period ."], ], ) def test_exception_space_or_period_at_tail(self, platform, value): with pytest.raises(ValidationError): validate_filepath(value, platform=platform) assert not is_valid_filepath(value, platform=platform) @pytest.mark.skipif(not is_faker_installed(), reason="requires faker") @pytest.mark.parametrize( ["locale"], [ [None], ["ja_JP"], ], ) def test_locale_jp(self, locale): from faker import Factory fake = Factory.create(locale=locale, seed=1) for _ in range(100): filepath = fake.file_path() validate_filepath(filepath, platform="linux") assert is_valid_filepath(filepath, platform="linux") @pytest.mark.parametrize( ["value"], [["{0}{1}{0}".format(randstr(64), invalid_c)] for invalid_c in INVALID_PATH_CHARS], ) def test_exception_invalid_char(self, value): with pytest.raises(ValidationError) as e: validate_filepath(value) assert e.value.reason == ErrorReason.INVALID_CHARACTER assert not is_valid_filepath(value) @pytest.mark.parametrize( ["value", "platform"], [ ["{0}/{1}{0}".format(randstr(64), invalid_c), platform] for invalid_c, platform in product( set(INVALID_WIN_PATH_CHARS + unprintable_ascii_chars).difference( set(INVALID_PATH_CHARS) ), ["windows", "universal"], ) ], ) def test_exception_invalid_win_char(self, value, platform): with pytest.raises(ValidationError) as e: validate_filepath(value, platform=platform) assert e.value.reason == ErrorReason.INVALID_CHARACTER assert not is_valid_filepath(value, platform=platform) @pytest.mark.parametrize( ["value", "platform"], [ [f"/foo/abc/{reserved_keyword}.txt", platform] for reserved_keyword, platform in product(WIN_RESERVED_FILE_NAMES, ["linux", "macos"]) if reserved_keyword not in [".", ".."] ] + [ [f"{drive}\\{filename}_", platform] for drive, platform, filename in product( ["C:", "D:"], ["windows"], NTFS_RESERVED_FILE_NAMES ) ] + [ [f"{drive}\\abc\\{filename}", platform] for drive, platform, filename in product( ["C:", "D:"], ["windows"], NTFS_RESERVED_FILE_NAMES ) ], ) def test_normal_reserved_name_used_valid_place(self, value, platform): validate_filepath(value, platform=platform) assert is_valid_filepath(value, platform=platform) @pytest.mark.parametrize( ["value", "platform", "expected"], [ [f"abc\\{reserved_keyword}\\xyz", platform, ValidationError] for reserved_keyword, platform in product( WIN_RESERVED_FILE_NAMES, ["windows", "universal"] ) if reserved_keyword not in [".", ".."] ] + [ [f"foo/abc/{reserved_keyword}.txt", platform, ValidationError] for reserved_keyword, platform in product(WIN_RESERVED_FILE_NAMES, ["universal"]) if reserved_keyword not in [".", ".."] ] + [ [f"{reserved_keyword}", platform, ValidationError] for reserved_keyword, platform in product( WIN_RESERVED_FILE_NAMES, ["windows", "universal"] ) if reserved_keyword not in [".", ".."] ] + [ [f"{drive}\\{filename}", platform, ValidationError] for drive, platform, filename in product( ["C:", "D:"], ["windows"], NTFS_RESERVED_FILE_NAMES ) ], ) def test_exception_reserved_name(self, value, platform, expected): with pytest.raises(expected) as e: print(platform, value) validate_filepath(value, platform=platform) assert e.value.reason == ErrorReason.RESERVED_NAME assert e.value.reusable_name is False assert e.value.reserved_name assert not is_valid_filepath(value, platform=platform) @pytest.mark.parametrize( ["value", "arn", "expected"], [ ["abc/efg.txt", ["abc"], False], ["abc/efg.txt", ["efg.txt"], False], ], ) def test_normal_additional_reserved_names(self, value, arn, expected): assert is_valid_filepath(value, additional_reserved_names=arn) == expected @pytest.mark.parametrize( ["value", "platform", "expected"], [ [value, platform, ErrorReason.INVALID_CHARACTER] for value, platform in product(["asdf\rsdf"], ["windows", "universal"]) ], ) def test_exception_escape_err_msg(self, value, platform, expected): with pytest.raises(ValidationError) as e: print(platform, repr(value)) validate_filepath(value, platform=platform) assert e.value.reason == expected assert str(e.value) == ( r"[PV1100] invalid characters found: invalids=('\r'), value='asdf\rsdf', " "platform=Windows" ) # noqa @pytest.mark.parametrize( ["value", "expected"], [ [None, ValueError], ["", ValidationError], [1, TypeError], [True, TypeError], ], ) def test_exception(self, value, expected): with pytest.raises(expected): validate_filepath(value) assert not is_valid_filepath(value) class Test_validate_win_file_path: VALID_CHARS = VALID_PATH_CHARS @pytest.mark.parametrize( ["value"], [ ["C:\\Users\\est\\AppData\\Local\\Temp\\pytest-of-test\\pytest-0\\\\hoge.csv"], ["Z:\\Users\\est\\AppData\\Local\\Temp\\pytest-of-test\\pytest-0\\hoge.csv"], [ "C:/Users/est/AppData/Local/Temp/pytest-of-test/pytest-0/test_exception__hoge_csv_heade1/hoge.csv" # noqa ], ["C:\\Users/est\\AppData/Local\\Temp/pytest-of-test\\pytest-0/hoge.csv"], ["C:\\Users"], ["C:\\"], ["\\Users"], ], ) def test_normal(self, value): validate_filepath(value, platform="windows") assert is_valid_filepath(value, platform="windows") @pytest.mark.parametrize( ["value", "platform", "expected"], [ [r"C:\Users\a", "universal", ErrorReason.MALFORMED_ABS_PATH], [r"C:\Users:a", "universal", ErrorReason.MALFORMED_ABS_PATH], ["C:\\Users\\" + "a" * 1024, "windows", ErrorReason.INVALID_LENGTH], [r"C:\Users:a", "windows", ErrorReason.INVALID_CHARACTER], ], ) def test_exception(self, value, platform, expected): with pytest.raises(ValidationError) as e: validate_filepath(value, platform=platform) assert e.value.reason == expected assert not is_valid_filepath(value, platform=platform) class Test_sanitize_filepath: SANITIZE_CHARS = INVALID_WIN_PATH_CHARS + unprintable_ascii_chars NOT_SANITIZE_CHARS = VALID_PATH_CHARS REPLACE_TEXTS = ["", "_"] @pytest.mark.parametrize( ["platform", "value", "replace_text", "expected"], [ ["universal", "AA" + c + "B", rep, "AA" + rep + "B"] for c, rep in product(SANITIZE_CHARS, REPLACE_TEXTS) ] + [ ["universal", "A" + c + "B", rep, "A" + c + "B"] for c, rep in product(NOT_SANITIZE_CHARS, REPLACE_TEXTS) ] + [ ["universal", "あ" + c + "い", rep, "あ" + c + "い"] for c, rep in product(NOT_SANITIZE_CHARS, REPLACE_TEXTS) ] + [ [pair.platform, "A" + pair.c + "B", pair.repl, "A" + pair.repl + "B"] for pair in AllPairs( OrderedDict( { "platform": [ "posix", "linux", "macos", Platform.POSIX, Platform.LINUX, Platform.MACOS, ], "c": INVALID_PATH_CHARS + unprintable_ascii_chars, "repl": REPLACE_TEXTS, } ) ) ] + [ [pair.platform, "A" + pair.c + "B", pair.repl, "A" + pair.c + "B"] for pair in AllPairs( OrderedDict( { "platform": [ "posix", "linux", "macos", Platform.POSIX, Platform.LINUX, Platform.MACOS, ], "c": [":", "*", "?", '"', "<", ">", "|"], "repl": REPLACE_TEXTS, } ) ) ], ) def test_normal_str(self, platform, value, replace_text, expected): sanitized_name = sanitize_filepath(value, platform=platform, replacement_text=replace_text) assert sanitized_name == expected assert isinstance(sanitized_name, str) validate_filepath(sanitized_name, platform=platform) assert is_valid_filepath(sanitized_name, platform=platform) @pytest.mark.parametrize( ["value", "test_platform", "expected"], [ [ f"abc/{reserved_keyword}/xyz", platform, f"abc/{reserved_keyword}_/xyz", ] for reserved_keyword, platform in product(WIN_RESERVED_FILE_NAMES, ["universal"]) ] + [ [ f"/abc/{reserved_keyword}/xyz", platform, f"/abc/{reserved_keyword}/xyz", ] for reserved_keyword, platform in product(WIN_RESERVED_FILE_NAMES, ["linux"]) ] + [ [ f"abc/{reserved_keyword}.txt", platform, f"abc/{reserved_keyword}_.txt", ] for reserved_keyword, platform in product(WIN_RESERVED_FILE_NAMES, ["universal"]) ] + [ [ f"/abc/{reserved_keyword}.txt", platform, f"/abc/{reserved_keyword}.txt", ] for reserved_keyword, platform in product(WIN_RESERVED_FILE_NAMES, ["linux"]) ] + [ [ f"C:\\abc\\{reserved_keyword}.txt", platform, f"C:\\abc\\{reserved_keyword}_.txt", ] for reserved_keyword, platform in product(WIN_RESERVED_FILE_NAMES, ["windows"]) ] + [ [f"{drive}\\{filename}", platform, f"{drive}\\{filename}_"] for drive, platform, filename in product( ["C:", "D:"], ["windows"], NTFS_RESERVED_FILE_NAMES ) ], ) def test_normal_reserved_name(self, value, test_platform, expected): filename = sanitize_filepath(value, platform=test_platform) assert filename == expected assert is_valid_filepath(filename, platform=test_platform) @pytest.mark.parametrize( ["value", "reserved_name_handler", "expected"], [ ["CON", ReservedNameHandler.add_trailing_underscore, "CON_"], ["hoge/CON", ReservedNameHandler.add_trailing_underscore, "hoge\\CON_"], ["CON", ReservedNameHandler.add_leading_underscore, "_CON"], ["hoge/CON", ReservedNameHandler.add_leading_underscore, "hoge\\_CON"], ["CON", ReservedNameHandler.as_is, "CON"], ], ) def test_normal_reserved_name_handler(self, value, reserved_name_handler, expected): assert ( sanitize_filepath( value, platform="windows", reserved_name_handler=reserved_name_handler ) == expected ) @pytest.mark.parametrize( ["value", "expected"], [ ["hoge/.", "hoge\\._"], ["hoge/./foo", "hoge\\._\\foo"], ["hoge/..", "hoge\\.._"], ], ) def test_normal_custom_reserved_name_handler_for_dot_files(self, value, expected): def always_add_trailing_underscore(e: ValidationError) -> str: if e.reusable_name: return e.reserved_name return f"{e.reserved_name}_" assert ( sanitize_filepath( value, platform="windows", reserved_name_handler=always_add_trailing_underscore, additional_reserved_names=[".", ".."], normalize=False, ) == expected ) def test_exception_reserved_name_handler(self): for platform in ["windows", "universal"]: with pytest.raises(ValidationError) as e: sanitize_filepath("CON", platform=platform, reserved_name_handler=raise_error) assert e.value.reason == ErrorReason.RESERVED_NAME @pytest.mark.parametrize( ["value", "check_reserved", "expected"], [ ["CON", True, "CON_"], ["CON", False, "CON"], ], ) def test_normal_check_reserved(self, value, check_reserved, expected): assert ( sanitize_filepath(value, platform="windows", check_reserved=check_reserved) == expected ) @pytest.mark.parametrize( ["value", "arn", "expected"], [ ["abc", ["abc"], "abc_"], ], ) def test_normal_additional_reserved_names(self, value, arn, expected): for platform in ["windows", "universal"]: assert ( sanitize_filepath( value, platform=platform, additional_reserved_names=arn, ) == expected ) @pytest.mark.parametrize( ["value", "replace_text", "expected"], [ [Path("AA" + c + "B"), rep, Path("AA" + rep + "B")] for c, rep in product(SANITIZE_CHARS, REPLACE_TEXTS) ] + [ [Path("A" + c + "B"), rep, Path("A" + c + "B")] for c, rep in product(NOT_SANITIZE_CHARS, REPLACE_TEXTS) ] + [ [Path("あ" + c + "い"), rep, Path("あ" + c + "い")] for c, rep in product(NOT_SANITIZE_CHARS, REPLACE_TEXTS) ], ) def test_normal_pathlike(self, value, replace_text, expected): sanitized_name = sanitize_filepath(value, replace_text) assert sanitized_name == expected assert isinstance(sanitized_name, Path) validate_filepath(sanitized_name) assert is_valid_filepath(sanitized_name) @pytest.mark.parametrize( ["test_platform", "value", "expected"], [ ["linux", "a/b/c.txt", "a/b/c.txt"], ["linux", "a//b?/c.txt", "a/b?/c.txt"], ["linux", "../a/./../b/c.txt", "../b/c.txt"], ["windows", "a/b/c.txt", "a\\b\\c.txt"], ["windows", "a//b?/c.txt", "a\\b\\c.txt"], ["windows", "../a/./../b/c.txt", "..\\b\\c.txt"], ["universal", "a/b/c.txt", "a/b/c.txt"], ["universal", "./", "."], ["universal", "./a/b/c.txt", "a/b/c.txt"], ["universal", "../a/./../b/c.txt", "../b/c.txt"], ["universal", "a//b?/c.txt", "a/b/c.txt"], ], ) def test_normal_relative_path(self, test_platform, value, expected): assert sanitize_filepath(value, platform=test_platform) == expected @pytest.mark.parametrize( ["test_platform", "value", "expected"], [ ["linux", "a/b/c.txt", "a/b/c.txt"], ["linux", "a//b?/c.txt", "a/b?/c.txt"], ["linux", "../a/./../b/c.txt", "../a/./../b/c.txt"], ["windows", "a/b/c.txt", "a\\b\\c.txt"], ["windows", "a//b?/c.txt", "a\\b\\c.txt"], ["windows", "../a/./../b/c.txt", "..\\a\\.\\..\\b\\c.txt"], ["universal", "a/b/c.txt", "a/b/c.txt"], ["universal", "./", "."], ["universal", "./a/b/c.txt", "./a/b/c.txt"], ["universal", "../a/./../b/c.txt", "../a/./../b/c.txt"], ["universal", "a//b?/c.txt", "a/b/c.txt"], ], ) def test_normal_not_normalize(self, test_platform, value, expected): assert sanitize_filepath(value, platform=test_platform, normalize=False) == expected @pytest.mark.parametrize( ["value"], [ [None], [""], ["?"], ], ) def test_normal_null_value_handler(self, value): assert ( sanitize_filepath(value, null_value_handler=NullValueHandler.return_null_string) == "" ) assert sanitize_filepath(value, null_value_handler=NullValueHandler.return_timestamp) != "" with pytest.raises(ValidationError): sanitize_filepath(value, null_value_handler=raise_error) @pytest.mark.parametrize( ["test_platform", "value", "replace_text", "expected"], [ ["linux", "/tmp/あいう\0えお.txt", "", "/tmp/あいうえお.txt"], ["linux", "/tmp/属\0性.txt", "-", "/tmp/属-性.txt"], ["universal", "tmp/あいう\0えお.txt", "", "tmp/あいうえお.txt"], ["universal", "tmp/属\0性.txt", "-", "tmp/属-性.txt"], ], ) def test_normal_multibyte(self, test_platform, value, replace_text, expected): sanitized_name = sanitize_filepath(value, replace_text, platform=test_platform) assert sanitized_name == expected validate_filepath(sanitized_name, platform=test_platform) assert is_valid_filepath(sanitized_name, platform=test_platform) @pytest.mark.parametrize( ["platform", "value", "expected"], [ ["windows", "a/b", "a\\b"], ["windows", "a\\b", "a\\b"], ["windows", "a\\\\b", "a\\b"], ["linux", "a/b", "a/b"], ["linux", "a//b", "a/b"], ["linux", "a\\b", "a/b"], ["linux", "a\\\\b", "a/b"], ["universal", "a/b", "a/b"], ["universal", "a//b", "a/b"], ["universal", "a\\b", "a/b"], ["universal", "a\\\\b", "a/b"], ], ) def test_normal_path_separator(self, platform, value, expected): sanitized = sanitize_filepath(value, platform=platform) assert sanitized == expected assert is_valid_filepath(sanitized, platform=platform) @pytest.mark.skipif(m_platform.system() != "Windows", reason="platform dependent tests") @pytest.mark.parametrize( ["value", "expected"], [ ["C:\\a\\b|c.txt", "C:\\a\\bc.txt"], ], ) def test_auto_platform_win(self, value, expected): if isinstance(expected, str): sanitized = sanitize_filepath(value, platform="auto") assert is_valid_filepath(sanitized, platform="auto") else: with pytest.raises(expected): sanitize_filepath(value, platform="auto") @pytest.mark.skipif(m_platform.system() != "Linux", reason="platform dependent tests") @pytest.mark.parametrize( ["value", "expected"], [ ["C:\\a\\b\\c.txt", ValidationError], ], ) def test_auto_platform_linux(self, value, expected): kwargs = { "platform": "auto", "validate_after_sanitize": True, } if isinstance(expected, str): sanitized = sanitize_filepath(value, **kwargs) assert is_valid_filepath(sanitized, platform="auto") return with pytest.raises(expected) as e: sanitize_filepath(value, **kwargs) assert e.value.reason == ErrorReason.MALFORMED_ABS_PATH @pytest.mark.parametrize( ["platform", "value"], [ ["windows", "CON \r"], ], ) def test_exception_invalid_after_sanitize(self, platform, value): print( "'{}'".format( sanitize_filepath(value, platform=platform, validate_after_sanitize=False) ), file=sys.stderr, ) with pytest.raises(ValidationError) as e: sanitize_filepath(value, platform=platform, validate_after_sanitize=True) assert e.value.reason == ErrorReason.INVALID_AFTER_SANITIZE @pytest.mark.parametrize( ["value", "expected"], [ [1, TypeError], [True, TypeError], [nan, TypeError], [inf, TypeError], ], ) def test_exception_type(self, value, expected): with pytest.raises(expected): sanitize_filepath(value) assert not is_valid_filepath(value) pathvalidate-3.2.3/test/test_handler.py000066400000000000000000000142301473576712500202130ustar00rootroot00000000000000import re import pytest from pathvalidate import ErrorReason, ValidationError from pathvalidate.handler import NullValueHandler, ReservedNameHandler, raise_error timstamp_regexp = re.compile(r"^\d+\.\d+$") class Test_raise_error: @pytest.mark.parametrize( ["exception"], [ [ ValidationError( description="hoge", reason=ErrorReason.INVALID_CHARACTER, ) ], [ ValidationError( description="foo", reason=ErrorReason.INVALID_AFTER_SANITIZE, ) ], ], ) def test_normal(self, exception): with pytest.raises(ValidationError) as e: raise_error(exception) assert exception == e.value class Test_NullValueHandler: @pytest.mark.parametrize( ["exception"], [ [ ValidationError( description="hoge", reason=ErrorReason.INVALID_CHARACTER, ) ], [ ValidationError( description="foo", reason=ErrorReason.INVALID_AFTER_SANITIZE, ) ], ], ) def test_return_null_string(self, exception): assert NullValueHandler.return_null_string(exception) == "" @pytest.mark.parametrize( ["exception"], [ [ ValidationError( description="hoge", reason=ErrorReason.INVALID_CHARACTER, ) ], [ ValidationError( description="foo", reason=ErrorReason.INVALID_AFTER_SANITIZE, ) ], ], ) def test_return_timestamp(self, exception): assert timstamp_regexp.search(NullValueHandler.return_timestamp(exception)) is not None class Test_ReservedNameHandler: @pytest.mark.parametrize( ["exception", "expected"], [ [ ValidationError( description="not reusable reserved name", reason=ErrorReason.RESERVED_NAME, reusable_name=False, reserved_name="hoge", ), "_hoge", ], [ ValidationError( description="do nothing to reusable reserved name", reason=ErrorReason.RESERVED_NAME, reusable_name=True, reserved_name="hoge", ), "hoge", ], [ ValidationError( description="do nothing to dot", reason=ErrorReason.RESERVED_NAME, reusable_name=False, reserved_name=".", ), ".", ], [ ValidationError( description="do nothing to double dot", reason=ErrorReason.RESERVED_NAME, reusable_name=False, reserved_name="..", ), "..", ], ], ) def test_add_leading_underscore(self, exception, expected): assert ReservedNameHandler.add_leading_underscore(exception) == expected @pytest.mark.parametrize( ["exception", "expected"], [ [ ValidationError( description="not reusable reserved name", reason=ErrorReason.RESERVED_NAME, reusable_name=False, reserved_name="hoge", ), "hoge_", ], [ ValidationError( description="do nothing to reusable reserved name", reason=ErrorReason.RESERVED_NAME, reusable_name=True, reserved_name="hoge", ), "hoge", ], [ ValidationError( description="do nothing to dot", reason=ErrorReason.RESERVED_NAME, reusable_name=False, reserved_name=".", ), ".", ], [ ValidationError( description="do nothing to double dot", reason=ErrorReason.RESERVED_NAME, reusable_name=False, reserved_name="..", ), "..", ], ], ) def test_add_trailing_underscore(self, exception, expected): assert ReservedNameHandler.add_trailing_underscore(exception) == expected @pytest.mark.parametrize( ["exception", "expected"], [ [ ValidationError( description="not reusable reserved name", reason=ErrorReason.RESERVED_NAME, reusable_name=False, reserved_name="hoge", ), "hoge", ], [ ValidationError( description="reusable reserved name", reason=ErrorReason.RESERVED_NAME, reusable_name=True, reserved_name="hoge", ), "hoge", ], [ ValidationError( description="dot", reason=ErrorReason.RESERVED_NAME, reusable_name=False, reserved_name=".", ), ".", ], [ ValidationError( description="double dot", reason=ErrorReason.RESERVED_NAME, reusable_name=False, reserved_name="..", ), "..", ], ], ) def test_as_is(self, exception, expected): assert ReservedNameHandler.as_is(exception) == expected pathvalidate-3.2.3/test/test_ltsv.py000066400000000000000000000047041473576712500175730ustar00rootroot00000000000000""" .. codeauthor:: Tsuyoshi Hombashi """ import itertools import pytest from pathvalidate import sanitize_ltsv_label, validate_ltsv_label from pathvalidate.error import ErrorReason, ValidationError from ._common import INVALID_WIN_FILENAME_CHARS, alphanum_chars VALID_LABEL_CHARS = alphanum_chars + ("_", ".", "-") INVALID_LABEL_CHARS = INVALID_WIN_FILENAME_CHARS + ( "!", "#", "$", "&", "'", "=", "~", "^", "@", "`", "[", "]", "+", ";", "{", "}", ",", "(", ")", "%", " ", "\t", "\n", "\r", "\f", "\v", ) class Test_validate_ltsv_label: VALID_CHARS = alphanum_chars INVALID_CHARS = INVALID_LABEL_CHARS @pytest.mark.parametrize( ["value"], [["abc" + valid_char + "hoge123"] for valid_char in VALID_CHARS] ) def test_normal(self, value): validate_ltsv_label(value) @pytest.mark.parametrize( ["value"], [["abc" + invalid_char + "hoge123"] for invalid_char in INVALID_CHARS] + [["あいうえお"], ["ラベル"]], ) def test_exception_invalid_char(self, value): with pytest.raises(ValidationError) as e: validate_ltsv_label(value) assert e.value.reason == ErrorReason.INVALID_CHARACTER class Test_sanitize_ltsv_label: TARGET_CHARS = INVALID_LABEL_CHARS NOT_TARGET_CHARS = alphanum_chars REPLACE_TEXT_LIST = ["", "_"] @pytest.mark.parametrize( ["value", "replace_text", "expected"], [ ["A" + c + "B", rep, "A" + rep + "B"] for c, rep in itertools.product(TARGET_CHARS, REPLACE_TEXT_LIST) ] + [ ["A" + c + "B", rep, "A" + c + "B"] for c, rep in itertools.product(NOT_TARGET_CHARS, REPLACE_TEXT_LIST) ], ) def test_normal(self, value, replace_text, expected): assert sanitize_ltsv_label(value, replace_text) == expected @pytest.mark.parametrize(["value", "expected"], [["aあいbうえcお", "abc"]]) def test_normal_multibyte(self, value, expected): sanitize_ltsv_label(value) @pytest.mark.parametrize( ["value", "expected"], [ ["", ValidationError], [None, ValidationError], [1, TypeError], [True, TypeError], ], ) def test_abnormal(self, value, expected): with pytest.raises(expected): sanitize_ltsv_label(value) pathvalidate-3.2.3/test/test_symbol.py000066400000000000000000000103661473576712500201110ustar00rootroot00000000000000""" .. codeauthor:: Tsuyoshi Hombashi """ import itertools import pytest from pathvalidate import ( ascii_symbols, replace_symbol, unprintable_ascii_chars, validate_symbol, validate_unprintable_char, ) from pathvalidate.error import ErrorReason, ValidationError from ._common import alphanum_chars class Test_validate_symbol: VALID_CHARS = alphanum_chars INVALID_CHARS = ascii_symbols @pytest.mark.parametrize( ["value"], [["abc" + valid_char + "hoge123"] for valid_char in VALID_CHARS] ) def test_normal(self, value): validate_symbol(value) @pytest.mark.parametrize(["value"], [["あいうえお"], ["シート"]]) def test_normal_multibyte(self, value): pytest.skip("TODO") validate_symbol(value) @pytest.mark.parametrize( ["value"], [ ["abc" + invalid_char + "hoge123"] for invalid_char in INVALID_CHARS + unprintable_ascii_chars ], ) def test_exception_invalid_char(self, value): with pytest.raises(ValidationError) as e: validate_symbol(value) assert e.value.reason == ErrorReason.INVALID_CHARACTER class Test_replace_symbol: TARGET_CHARS = ascii_symbols NOT_TARGET_CHARS = alphanum_chars REPLACE_TEXT_LIST = ["", "_"] @pytest.mark.parametrize( ["value", "replace_text", "expected"], [ ["A" + c + "B", rep, "A" + rep + "B"] for c, rep in itertools.product(TARGET_CHARS, REPLACE_TEXT_LIST) ] + [ ["A" + c + "B", rep, "A" + c + "B"] for c, rep in itertools.product(NOT_TARGET_CHARS, REPLACE_TEXT_LIST) ] + [["", "", ""]], ) def test_normal(self, value, replace_text, expected): assert replace_symbol(value, replace_text) == expected @pytest.mark.parametrize( ["value", "exclude_symbols", "expected"], [ ["/tmp/h!o|g$e.txt", ["/", "."], "/tmp/hoge.txt"], ["/tmp/h!o|g$e.txt", [], "tmphogetxt"], ["/tmp/h!o|g$e.txt", ["n", "o", "p"], "tmphogetxt"], ], ) def test_normal_exclude_symbols(self, value, exclude_symbols, expected): assert replace_symbol(value, exclude_symbols=exclude_symbols) == expected @pytest.mark.parametrize( ["value", "replace_text", "is_replace_consecutive_chars", "is_strip", "expected"], [ ["!a##b$$$c((((d]]]])", "_", True, True, "a_b_c_d"], ["!a##b$$$c((((d]]]])", "_", True, False, "_a_b_c_d_"], ["!a##b$$$c((((d]]]])", "_", False, True, "a__b___c____d"], ["!a##b$$$c((((d]]]])", "_", False, False, "_a__b___c____d_____"], ], ) def test_normal_consecutive( self, value, replace_text, is_replace_consecutive_chars, is_strip, expected ): assert ( replace_symbol( value, replace_text, is_replace_consecutive_chars=is_replace_consecutive_chars, is_strip=is_strip, ) == expected ) @pytest.mark.parametrize( ["value", "expected"], [ [None, TypeError], [1, TypeError], [True, TypeError], ], ) def test_abnormal(self, value, expected): with pytest.raises(expected): replace_symbol(value) class Test_validate_unprintable_char: VALID_CHARS = alphanum_chars INVALID_CHARS = unprintable_ascii_chars @pytest.mark.parametrize( ["value"], [["abc" + valid_char + "hoge123"] for valid_char in VALID_CHARS] ) def test_normal(self, value): validate_unprintable_char(value) @pytest.mark.parametrize(["value"], [["あいうえお"], ["シート"]]) def test_normal_multibyte(self, value): pytest.skip("TODO") validate_unprintable_char(value) @pytest.mark.parametrize( ["value"], [ ["abc" + invalid_char + "hoge123"] for invalid_char in INVALID_CHARS + unprintable_ascii_chars ], ) def test_exception_invalid_char(self, value): with pytest.raises(ValidationError) as e: validate_unprintable_char(value) assert e.value.reason == ErrorReason.INVALID_CHARACTER pathvalidate-3.2.3/tox.ini000066400000000000000000000035361473576712500155300ustar00rootroot00000000000000[tox] envlist = py{39,310,311,312,313} pypy3 build cov docs fmt lint readme skip_missing_interpreters = true [testenv] passenv = * extras = test commands = pytest {posargs} [testenv:build] deps = build>=1 twine wheel commands = python -m build twine check dist/*.whl dist/*.tar.gz [testenv:clean] skip_install = true deps = cleanpy>=0.4 commands = cleanpy --all --exclude-envs . [testenv:cov] passenv = GITHUB_* extras = test deps = coverage[toml]>=5 commands = coverage run -m pytest {posargs:-vv} coverage report -m [testenv:docs] extras = docs commands = sphinx-build docs/ docs/_build [testenv:fmt-black] skip_install = true deps = autoflake>=2 black>=24.1 isort>=5 commands = autoflake --in-place --recursive --remove-all-unused-imports . isort . black setup.py test pathvalidate [testenv:fmt] skip_install = true deps = autoflake>=2 isort>=5 ruff>=0.8 commands = autoflake --in-place --recursive --remove-all-unused-imports . isort . ruff format [testenv:lint] extras = docs readme test deps = codespell>=2 pyright>=1.1 releasecmd ruff>=0.8 types-click commands = pyright pathvalidate setup.py codespell pathvalidate docs/pages examples test -q2 --check-filenames ruff format --check ruff check [testenv:lint-examples] deps = jupyter mypy>=1 pathvalidate>=3 pyright>=1.1 types-click ruff>=0.8 commands = jupyter nbconvert examples/pathvalidate_examples.ipynb --to python pyright examples ruff check examples ruff format --check examples [testenv:readme] changedir = docs extras = readme commands = python make_readme.py [testenv:release] deps = releasecmd commands = python setup.py release --sign --skip-uploading --verbose