├── .github └── workflows │ ├── main.yml │ ├── python-publish.yml │ ├── update_readme.py │ ├── update_shfmt_checksums.py │ └── update_shfmt_checksums.yml ├── .pre-commit-config.yaml ├── .pre-commit-hooks.yaml ├── LICENSE ├── README.md ├── renovate.json ├── setup.cfg ├── setup.py └── tox.ini /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: main 2 | 3 | on: 4 | push: 5 | branches: [main, test-me-*] 6 | tags: '*' 7 | pull_request: 8 | 9 | jobs: 10 | main-windows: 11 | uses: asottile/workflows/.github/workflows/tox.yml@v1.8.1 12 | with: 13 | env: '["py39"]' 14 | os: windows-latest 15 | wheel-tags: true 16 | main-macos: 17 | uses: asottile/workflows/.github/workflows/tox.yml@v1.8.1 18 | with: 19 | env: '["py39"]' 20 | os: macos-latest 21 | wheel-tags: true 22 | main-macos-intel: 23 | uses: asottile/workflows/.github/workflows/tox.yml@v1.8.1 24 | with: 25 | env: '["py39"]' 26 | os: macos-13 27 | wheel-tags: true 28 | main-linux: 29 | uses: asottile/workflows/.github/workflows/tox.yml@v1.8.1 30 | with: 31 | env: '["py39"]' 32 | os: ubuntu-latest 33 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: Upload Python Package 10 | 11 | on: 12 | release: 13 | types: [published] 14 | 15 | jobs: 16 | deploy: 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Set up Python 22 | uses: actions/setup-python@v5 23 | with: 24 | python-version: "3.x" 25 | - name: Install dependencies 26 | run: | 27 | python -m pip install --upgrade pip 28 | pip install build 29 | - name: Build package 30 | run: python -m build --sdist 31 | - name: Publish package 32 | uses: pypa/gh-action-pypi-publish@db8f07d3871a0a180efa06b95d467625c19d5d5f 33 | with: 34 | user: __token__ 35 | password: ${{ secrets.PYPI_API_TOKEN }} 36 | -------------------------------------------------------------------------------- /.github/workflows/update_readme.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import re 4 | 5 | # Function to extract version from setup.py 6 | 7 | 8 | def extract_version_from_setup(): 9 | print('[DEBUG] Opening setup.py to read the version...') # Debug log for reading setup.py 10 | # Open and read the setup.py file 11 | with open('setup.py') as setup_file: 12 | setup_content = setup_file.read() 13 | 14 | # Find the version variable in setup.py 15 | version_match = re.search(r"SHFMT_VERSION\s*=\s*'(\d+\.\d+\.\d+)'", setup_content) 16 | 17 | if version_match: 18 | version = version_match.group(1) 19 | print(f"[DEBUG] Found version in setup.py: {version}") # Log the found version 20 | return version 21 | else: 22 | raise ValueError('Version not found in setup.py') 23 | 24 | # Function to update the version in README.md 25 | 26 | 27 | def update_readme_version(): 28 | try: 29 | # Extract the version from setup.py 30 | SHFMT_VERSION = extract_version_from_setup() 31 | 32 | # Append '.0' to the version (if not already appended) 33 | adjusted_version = f"{SHFMT_VERSION}.1" 34 | print(f"[DEBUG] Adjusted version: {adjusted_version}") # Debug log for adjusted version 35 | 36 | # Open and read the README.md file 37 | print('[DEBUG] Opening README.md to read its content...') # Debug log for reading README.md 38 | with open('README.md') as readme_file: 39 | readme_content = readme_file.read() 40 | 41 | # Search for the version pattern and replace it 42 | pattern = r'(rev:\s*v\d+\.\d+\.\d+\.\d+)' 43 | print(f"[DEBUG] Searching for pattern: {pattern}") # Log the pattern being searched for 44 | new_readme_content = re.sub(pattern, f"rev: v{adjusted_version}", readme_content) 45 | 46 | # Check if any changes were made to the README.md content 47 | if new_readme_content != readme_content: 48 | print('[DEBUG] Changes detected in README.md. Updating...') # Debug log for changes found 49 | with open('README.md', 'w') as readme_file: 50 | readme_file.write(new_readme_content) 51 | print(f"[INFO] Updated README.md with new version: {adjusted_version}") 52 | else: 53 | print('[INFO] No changes needed to README.md.') # Debug log for no changes found 54 | 55 | except ValueError as e: 56 | print(f"[ERROR] {e}") # Error log if version is not found in setup.py 57 | 58 | 59 | # Run the function 60 | update_readme_version() 61 | -------------------------------------------------------------------------------- /.github/workflows/update_shfmt_checksums.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import re 4 | import sys 5 | 6 | import requests 7 | 8 | # File paths and URLs 9 | SHFMT_VERSION_FILE = 'setup.py' 10 | GITHUB_RELEASES_URL = 'https://github.com/mvdan/sh/releases/download/v{version}/sha256sums.txt' 11 | 12 | ARCH_MAP = { 13 | 'shfmt_v{version}_linux_arm': 'linux_arm', 14 | 'shfmt_v{version}_linux_arm64': 'linux_arm64', 15 | 'shfmt_v{version}_linux_amd64': 'linux_amd64', 16 | 'shfmt_v{version}_darwin_amd64': 'darwin_amd64', 17 | 'shfmt_v{version}_darwin_arm64': 'darwin_arm64', 18 | 'shfmt_v{version}_windows_amd64.exe': 'windows_amd64_exe', 19 | 'shfmt_v{version}_windows_386.exe': 'windows_386_exe', 20 | } 21 | 22 | # Step 1: Get the current SHFMT version from setup.py 23 | print('[INFO] Reading SHFMT_VERSION from setup.py...') 24 | try: 25 | with open(SHFMT_VERSION_FILE) as f: 26 | content = f.read() 27 | except FileNotFoundError: 28 | print('[ERROR] setup.py not found!') 29 | sys.exit(1) 30 | 31 | match = re.search(r"SHFMT_VERSION = '([^']+)'", content) 32 | if not match: 33 | print('[ERROR] Could not find SHFMT_VERSION in setup.py!') 34 | sys.exit(1) 35 | 36 | shfmt_version = match.group(1) 37 | print(f"[INFO] Found SHFMT_VERSION: {shfmt_version}") 38 | 39 | checksum_url = GITHUB_RELEASES_URL.format(version=shfmt_version) 40 | print(f"[INFO] Downloading checksum file from: {checksum_url}") 41 | 42 | # Step 2: Download `sha256sums.txt` 43 | response = requests.get(checksum_url) 44 | if response.status_code != 200: 45 | print(f"[ERROR] Failed to download {checksum_url} (HTTP {response.status_code})") 46 | sys.exit(1) 47 | 48 | checksums = {} 49 | lines = response.text.splitlines() 50 | print(f"[INFO] Downloaded checksum file ({len(lines)} lines). Processing...") 51 | 52 | for line in lines: 53 | parts = line.split() 54 | if len(parts) != 2: 55 | print(f"[WARNING] Skipping malformed line: {line}") 56 | continue 57 | 58 | sha, filename = parts 59 | expected_key = filename.replace(shfmt_version, '{version}') 60 | 61 | if expected_key in ARCH_MAP: 62 | var_name = ARCH_MAP[expected_key].format(version=shfmt_version) 63 | checksums[var_name] = sha 64 | print(f"[INFO] Found checksum: {var_name} -> {sha}") 65 | else: 66 | print(f"[DEBUG] Skipping unrecognized file: {filename}") 67 | 68 | if not checksums: 69 | print('[ERROR] No matching checksums found! Check the `sha256sums.txt` format.') 70 | sys.exit(1) 71 | 72 | 73 | # Step 3: Update `setup.py` 74 | print('[INFO] Updating setup.py with new checksums...') 75 | 76 | new_content = content 77 | updated = False 78 | 79 | # Directly update variables in setup.py with the new checksums 80 | for var_name, sha in checksums.items(): 81 | print(f"[DEBUG] var_name: {var_name}") 82 | pattern = fr"({var_name}\s*=\s*)\'[a-fA-F0-9]{{64}}\'" 83 | replacement = fr"\1'{sha}'" # Corrected line 84 | 85 | # Debug: print the pattern we are searching for 86 | print(f"[DEBUG] Searching for pattern: {pattern}") 87 | 88 | if re.search(pattern, new_content): 89 | new_content = re.sub(pattern, replacement, new_content) 90 | print(f"[INFO] Updated checksum for {var_name}: {sha}") 91 | updated = True 92 | else: 93 | print(f"[WARNING] Could not find pattern for {var_name}. Check your setup.py format.") 94 | 95 | if not updated: 96 | print('[WARNING] No checksums were updated. Maybe they are already correct?') 97 | else: 98 | with open(SHFMT_VERSION_FILE, 'w') as f: 99 | f.write(new_content) 100 | print('[SUCCESS] setup.py updated with new checksums!') 101 | -------------------------------------------------------------------------------- /.github/workflows/update_shfmt_checksums.yml: -------------------------------------------------------------------------------- 1 | name: Update shfmt checksums 2 | 3 | on: 4 | push: 5 | branches: 6 | - renovate/mvdan-sh** # Only run when Renovate updates SHFMT_VERSION 7 | 8 | jobs: 9 | update-checksums: 10 | runs-on: ubuntu-latest 11 | 12 | permissions: 13 | # Give the default GITHUB_TOKEN write permission to commit and push the 14 | # added or changed files to the repository. 15 | contents: write 16 | 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v4 20 | with: 21 | token: ${{ secrets.PAT }} 22 | 23 | - name: Get last commit message 24 | id: get_commit_message 25 | run: echo "commit_message=$(git log -1 --pretty=%B)" >> $GITHUB_ENV 26 | 27 | - name: Set up Python 28 | uses: actions/setup-python@v5 29 | with: 30 | python-version: '3.x' 31 | 32 | - name: Install dependencies 33 | run: pip install requests 34 | 35 | - name: Run checksum update script 36 | run: python .github/workflows/update_shfmt_checksums.py 37 | 38 | - name: Run README update script 39 | run: python .github/workflows/update_readme.py 40 | 41 | - uses: stefanzweifel/git-auto-commit-action@v5 42 | if: ${{ env.commit_message != 'Apply automatic changes' }} 43 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v5.0.0 4 | hooks: 5 | - id: trailing-whitespace 6 | - id: end-of-file-fixer 7 | - id: check-yaml 8 | - id: debug-statements 9 | - id: double-quote-string-fixer 10 | - id: name-tests-test 11 | - id: requirements-txt-fixer 12 | - repo: https://github.com/asottile/setup-cfg-fmt 13 | rev: v2.8.0 14 | hooks: 15 | - id: setup-cfg-fmt 16 | - repo: https://github.com/asottile/reorder-python-imports 17 | rev: v3.15.0 18 | hooks: 19 | - id: reorder-python-imports 20 | args: [--py39-plus, --add-import, 'from __future__ import annotations'] 21 | - repo: https://github.com/asottile/add-trailing-comma 22 | rev: v3.2.0 23 | hooks: 24 | - id: add-trailing-comma 25 | - repo: https://github.com/asottile/pyupgrade 26 | rev: v3.20.0 27 | hooks: 28 | - id: pyupgrade 29 | args: [--py39-plus] 30 | - repo: https://github.com/hhatto/autopep8 31 | rev: v2.3.2 32 | hooks: 33 | - id: autopep8 34 | - repo: https://github.com/PyCQA/flake8 35 | rev: 7.2.0 36 | hooks: 37 | - id: flake8 38 | -------------------------------------------------------------------------------- /.pre-commit-hooks.yaml: -------------------------------------------------------------------------------- 1 | - id: shfmt 2 | name: shfmt 3 | description: Format shell scripts with shfmt 4 | entry: shfmt 5 | language: python 6 | types: [shell] 7 | args: [-w] # write file back 8 | require_serial: true # shfmt can detect sourcing this way 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright for portions of project shfmt-py are held by Ryan Rhee, 2019 2 | as part of the project located at https://github.com/shellcheck-py/shellcheck-py. 3 | All other copyright for this project are held by Max Winterstein, 2021. 4 | 5 | MIT License 6 | 7 | Copyright (c) 2019 Ryan Rhee 8 | Copyright (c) 2021 Max Winterstein 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/MaxWinterstein/shfmt-py/master.svg)](https://results.pre-commit.ci/latest/github/MaxWinterstein/shfmt-py/master) 2 | 3 | # shfmt-py 4 | 5 | A python wrapper to provide a pip-installable [shfmt] binary. 6 | 7 | Internally this package provides a convenient way to download the pre-built 8 | shellcheck binary for your particular platform. 9 | 10 | This package is totally cloned from [shellcheck-py] and modified to provide `shfmt` instead. 11 | 12 | ## Installation 13 | 14 | ```bash 15 | pip install shfmt-py 16 | ``` 17 | 18 | ## Usage 19 | 20 | ### CLI 21 | 22 | After installation, the `shfmt` binary should be available in your 23 | environment (or `shfmt.exe` on windows). 24 | 25 | ### As pre-commit hook 26 | 27 | See [pre-commit] for instructions 28 | 29 | Sample `.pre-commit-config.yaml`: 30 | 31 | ```yaml 32 | - repo: https://github.com/maxwinterstein/shfmt-py 33 | rev: v3.11.0.2 34 | hooks: 35 | - id: shfmt 36 | ``` 37 | 38 | ## FAQ 39 | 40 | Q: It won't get updated via e.g. `Renovate Bot` 41 | 42 | A: See https://github.com/shfmt-py/update-via-renovate . 43 | 44 | Q: I get something like `SSL: CERTIFICATE_VERIFY_FAILED` on macOS 45 | 46 | A: Install certificates with e.g.: `"/Applications/Python 3.9/Install Certificates.command"`. See [here][here1] or [here][here2] for a solution. 47 | 48 | 49 | [shfmt]: https://github.com/mvdan/sh 50 | [pre-commit]: https://pre-commit.com 51 | [shellcheck-py]: https://github.com/shellcheck-py/shellcheck-py 52 | [here1]: https://github.com/albertogeniola/MerossIot/issues/62#issuecomment-535769621 53 | [here2]: https://stackoverflow.com/questions/27835619/urllib-and-ssl-certificate-verify-failed-error 54 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ], 6 | "customManagers": [ 7 | { 8 | "customType": "regex", 9 | "fileMatch": [ 10 | "\\.py$" 11 | ], 12 | "matchStrings": [ 13 | "SHFMT_VERSION = '(?[^']+)'" 14 | ], 15 | "depNameTemplate": "mvdan/sh", 16 | "datasourceTemplate": "github-releases" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = shfmt_py 3 | description = Python wrapper around invoking shfmt (https://github.com/mvdan/sh) 4 | long_description = file: README.md 5 | long_description_content_type = text/markdown 6 | url = https://github.com/maxwinterstein/shfmt-py 7 | author = Max Winterstein 8 | author_email = github@winterstein.io 9 | license = MIT 10 | license_files = LICENSE 11 | classifiers = 12 | Programming Language :: Python :: 3 13 | Programming Language :: Python :: 3 :: Only 14 | Programming Language :: Python :: Implementation :: CPython 15 | Programming Language :: Python :: Implementation :: PyPy 16 | 17 | [options] 18 | python_requires = >=3.9 19 | 20 | [flake8] 21 | max-line-length = 120 22 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from __future__ import annotations 3 | 4 | import hashlib 5 | import http 6 | import os.path 7 | import platform 8 | import stat 9 | import sys 10 | import urllib.request 11 | 12 | from distutils.command.build import build as orig_build 13 | from distutils.core import Command 14 | from setuptools import setup 15 | from setuptools.command.install import install as orig_install 16 | 17 | linux_arm = '00a243112f22a94594a82cb9399b086cf51441e3d9fef98b1203d633863506ed' 18 | linux_arm64 = 'b3976121710fd4b12bf641b0a7fb2686da598fb0da9f148c641b61b54cfa3407' 19 | linux_amd64 = '1904ec6bac715c1d05cd7f6612eec8f67a625c3749cb327e5bfb4127d09035ff' 20 | darwin_amd64 = '810a76cb7c78351e021c8025f344b12149d8426ce51609a179af68109ed5698e' 21 | darwin_arm64 = 'af206d234dff5d05d9ac355529b2b33a7a78e13fab9b59db777746aab3e72530' 22 | windows_amd64_exe = 'd11a0880588304496065110cb67ce2375cfcd5433e76dec505ce1fa21adf47cc' 23 | windows_386_exe = 'facfb70de36cc3b15d59938fd840d6eaa8dc5446767f043265038a49eb719ec7' 24 | 25 | SHFMT_VERSION = '3.11.0' 26 | POSTFIX_SHA256 = { 27 | ('linux', 'armv6hf'): ( 28 | 'linux_arm', 29 | linux_arm, 30 | ), 31 | ('linux', 'aarch64'): ( 32 | 'linux_arm64', 33 | linux_arm64, 34 | ), 35 | ('linux', 'x86_64'): ( 36 | 'linux_amd64', 37 | linux_amd64, 38 | ), 39 | ('darwin', 'x86_64'): ( 40 | 'darwin_amd64', 41 | darwin_amd64, 42 | ), 43 | ('darwin', 'arm64'): ( 44 | 'darwin_arm64', 45 | darwin_arm64, 46 | ), 47 | ('win32', 'AMD64'): ( 48 | 'windows_amd64.exe', 49 | windows_amd64_exe, 50 | ), 51 | ('win32', 'x86'): ( 52 | 'windows_386.exe', 53 | windows_386_exe, 54 | ), 55 | } 56 | POSTFIX_SHA256[('cygwin', 'x86_64')] = POSTFIX_SHA256[('win32', 'AMD64')] 57 | POSTFIX_SHA256[('linux', 'armv7l')] = POSTFIX_SHA256[('linux', 'armv6hf')] 58 | PY_VERSION = '2' 59 | 60 | 61 | def get_download_url() -> tuple[str, str]: 62 | postfix, sha256 = POSTFIX_SHA256[(sys.platform, platform.machine())] 63 | url = ( 64 | f'https://github.com/mvdan/sh/releases/download/' 65 | f'v{SHFMT_VERSION}/shfmt_v{SHFMT_VERSION}_{postfix}' 66 | ) 67 | print(url) 68 | return url, sha256 69 | 70 | 71 | def download(url: str, sha256: str) -> bytes: 72 | with urllib.request.urlopen(url) as resp: 73 | code = resp.getcode() 74 | if code != http.HTTPStatus.OK: 75 | raise ValueError(f'HTTP failure. Code: {code}') 76 | data = resp.read() 77 | 78 | checksum = hashlib.sha256(data).hexdigest() 79 | if checksum != sha256: 80 | raise ValueError(f'sha256 mismatch, expected {sha256}, got {checksum}') 81 | 82 | return data 83 | 84 | 85 | def save_executable(data: bytes, base_dir: str): 86 | exe = 'shfmt' if sys.platform != 'win32' else 'shfmt.exe' 87 | output_path = os.path.join(base_dir, exe) 88 | os.makedirs(base_dir, exist_ok=True) 89 | 90 | with open(output_path, 'wb') as fp: 91 | fp.write(data) 92 | 93 | # Mark as executable. 94 | # https://stackoverflow.com/a/14105527 95 | mode = os.stat(output_path).st_mode 96 | mode |= stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH 97 | os.chmod(output_path, mode) 98 | 99 | 100 | class build(orig_build): 101 | sub_commands = orig_build.sub_commands + [('fetch_binaries', None)] 102 | 103 | 104 | class install(orig_install): 105 | sub_commands = orig_install.sub_commands + [('install_shfmt', None)] 106 | 107 | 108 | class fetch_binaries(Command): 109 | build_temp = None 110 | 111 | def initialize_options(self): 112 | pass 113 | 114 | def finalize_options(self): 115 | self.set_undefined_options('build', ('build_temp', 'build_temp')) 116 | 117 | def run(self): 118 | # save binary to self.build_temp 119 | url, sha256 = get_download_url() 120 | data = download(url, sha256) 121 | save_executable(data, self.build_temp) 122 | 123 | 124 | class install_shfmt(Command): 125 | description = 'install the shfmt executable' 126 | outfiles = () 127 | build_dir = install_dir = None 128 | 129 | def initialize_options(self): 130 | pass 131 | 132 | def finalize_options(self): 133 | # this initializes attributes based on other commands' attributes 134 | self.set_undefined_options('build', ('build_temp', 'build_dir')) 135 | self.set_undefined_options( 136 | 'install', ('install_scripts', 'install_dir'), 137 | ) 138 | 139 | def run(self): 140 | self.outfiles = self.copy_tree(self.build_dir, self.install_dir) 141 | 142 | def get_outputs(self): 143 | return self.outfiles 144 | 145 | 146 | command_overrides = { 147 | 'install': install, 148 | 'install_shfmt': install_shfmt, 149 | 'build': build, 150 | 'fetch_binaries': fetch_binaries, 151 | } 152 | 153 | 154 | try: 155 | from wheel.bdist_wheel import bdist_wheel as orig_bdist_wheel 156 | except ImportError: 157 | pass 158 | else: 159 | class bdist_wheel(orig_bdist_wheel): 160 | def finalize_options(self): 161 | orig_bdist_wheel.finalize_options(self) 162 | # Mark us as not a pure python package 163 | self.root_is_pure = False 164 | 165 | def get_tag(self): 166 | _, _, plat = orig_bdist_wheel.get_tag(self) 167 | # We don't contain any python source, nor any python extensions 168 | return 'py2.py3', 'none', plat 169 | 170 | command_overrides['bdist_wheel'] = bdist_wheel 171 | 172 | setup(version=f'{SHFMT_VERSION}.{PY_VERSION}', cmdclass=command_overrides) 173 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py,pre-commit 3 | 4 | [testenv] 5 | commands = 6 | shfmt --version 7 | shfmt --help 8 | 9 | [testenv:pre-commit] 10 | skip_install = true 11 | deps = pre-commit 12 | commands = pre-commit run --all-files --show-diff-on-failure 13 | 14 | [pep8] 15 | ignore = E265,E501,W504 16 | --------------------------------------------------------------------------------