diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 8f48c03..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,21 +0,0 @@ -version: 2 -registries: - python-index-pypi-python-org-simple: - type: python-index - url: https://pypi.python.org/simple/ - replaces-base: false - username: "${{secrets.PYTHON_INDEX_PYPI_PYTHON_ORG_SIMPLE_USERNAME}}" - password: "${{secrets.PYTHON_INDEX_PYPI_PYTHON_ORG_SIMPLE_PASSWORD}}" - -updates: -- package-ecosystem: pip - directory: "/" - schedule: - interval: daily - open-pull-requests-limit: 10 - ignore: - - dependency-name: pycodestyle - versions: - - 2.7.0 - registries: - - python-index-pypi-python-org-simple diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 87a618f..2e3157e 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -1,7 +1,7 @@ - id: editorconfig-checker name: editorconfig-checker description: '`editorconfig-checker` is a tool to check if your files consider your .editorconfig-rules.' - entry: editorconfig-checker + entry: ec language: python types: [text] require_serial: true diff --git a/Makefile b/Makefile index fcec853..7bd33b5 100644 --- a/Makefile +++ b/Makefile @@ -1,18 +1,27 @@ .PHONY: help help: @echo "Available targets:" - @echo " - help : Print this help message." - @echo " - clean : Remove generated files." - @echo " - coding_style : Run coding style tools." + @echo " - help : Print this help message." + @echo " - clean : Remove generated files." + @echo " - coding-style : Run coding style tools." + @echo " - publish : Publish package to PyPI." + @echo " - test : Run coding style tools and tests." .PHONY: all all: help .PHONY: clean clean: - @rm -rf build dist editorconfig_checker.egg-info editorconfig_checker/bin + rm -rf build dist editorconfig_checker.egg-info editorconfig_checker/bin -.PHONY: coding_style -coding_style: - @pycodestyle --ignore E501 . - @flake8 --ignore E501 . +.PHONY: coding-style +coding-style: + flake8 --ignore E501 setup.py + +.PHONY: publish +publish: + bash publish.sh + +.PHONY: test +test: coding-style + bash test.sh diff --git a/README.md b/README.md index 6f05335..7468674 100644 --- a/README.md +++ b/README.md @@ -1,70 +1,27 @@ -# editorconfig-checker +# editorconfig-checker.python +A Python wrapper to provide a pip-installable [editorconfig-checker](https://github.com/editorconfig-checker/editorconfig-checker) binary. -![Logo](https://raw.githubusercontent.com/editorconfig-checker/editorconfig-checker.python/master/docs/logo.png "Logo") +Internally, this package provides a convenient way to download the pre-built `editorconfig-checker` binary for your particular platform. -Buy Me A Coffee - -## What? - -This is a tool to check if your files consider your `.editorconfig`. -Most tools - like linters for example - only test one filetype and need an extra configuration. -This tool only needs your editorconfig to check all files. - -![Sample Output](https://raw.githubusercontent.com/editorconfig-checker/editorconfig-checker.python/master/docs/sample-output.png "Sample output") - -## Important - -This is only a wrapper for the core [editorconfig-checker](https://github.com/editorconfig-checker/editorconfig-checker). -You should have a look at this repository to know how this tool can be used and what possibilities/caveats are there. -This version can be used in the same way as the core as every argument is simply passed down to it. ## Installation - ``` -$ pip install . # from cloned repo +$ pip install . # from source code $ pip install editorconfig-checker # from PyPI ``` + ## Usage +After installation, the `ec` binary should be available in your environment (or `ec.exe` on Windows): ``` -$ editorconfig-checker -help -USAGE: - -config string - config - -debug - print debugging information - -disable-end-of-line - disables the trailing whitespace check - -disable-indentation - disables the indentation check - -disable-insert-final-newline - disables the final newline check - -disable-trim-trailing-whitespace - disables the trailing whitespace check - -dry-run - show which files would be checked - -exclude string - a regex which files should be excluded from checking - needs to be a valid regular expression - -h print the help - -help - print the help - -ignore-defaults - ignore default excludes - -init - creates an initial configuration - -no-color - dont print colors - -v print debugging information - -verbose - print debugging information - -version - print the version number +$ ec -version ``` -## Usage with the pre-commit git hooks framework -editorconfig-checker can be included as a hook for [pre-commit](https://pre-commit.com/). The easiest way to get started is to add this configuration to your `.pre-commit-config.yaml`: +## Usage with the pre-commit git hooks framework +`editorconfig-checker` can be included as a hook for [pre-commit](https://pre-commit.com/). +The easiest way to get started is to add this configuration to your `.pre-commit-config.yaml`: ```yaml repos: @@ -72,20 +29,7 @@ repos: rev: '' # pick a git hash / tag to point to hooks: - id: editorconfig-checker + alias: ec ``` -See the [pre-commit docs](https://pre-commit.com/#pre-commit-configyaml---hooks) for how to customize this configuration. - -## Run tests - -The test script uses `docker`. After installing it, you can run the test with: -``` -$ ./test.sh -``` - -## Support - -If you have any questions or just want to chat join #editorconfig-checker on -freenode(IRC). -If you don't have an IRC-client set up you can use the -[freenode webchat](https://webchat.freenode.net/?channels=editorconfig-checker). +See the [pre-commit docs](https://pre-commit.com/#pre-commit-configyaml---hooks) to check how to customize this configuration. diff --git a/dev_requirements.txt b/dev_requirements.txt new file mode 100644 index 0000000..5b6fa90 --- /dev/null +++ b/dev_requirements.txt @@ -0,0 +1 @@ +flake8==3.9.1 diff --git a/docs/logo.png b/docs/logo.png deleted file mode 100644 index 322b979..0000000 Binary files a/docs/logo.png and /dev/null differ diff --git a/docs/sample-output.png b/docs/sample-output.png deleted file mode 100644 index e7c6982..0000000 Binary files a/docs/sample-output.png and /dev/null differ diff --git a/editorconfig_checker/__init__.py b/editorconfig_checker/__init__.py deleted file mode 100644 index 7cf27e9..0000000 --- a/editorconfig_checker/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -__version__ = '2.3.5' diff --git a/editorconfig_checker/__main__.py b/editorconfig_checker/__main__.py deleted file mode 100644 index f7b069f..0000000 --- a/editorconfig_checker/__main__.py +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -from sys import argv, exit as sys_exit - -from editorconfig_checker.wrapper import run_editor_config_checker - - -def main(): - return run_editor_config_checker(argv[1:]) - - -if __name__ == "__main__": - sys_exit(main()) diff --git a/editorconfig_checker/wrapper.py b/editorconfig_checker/wrapper.py deleted file mode 100644 index 30f2ff9..0000000 --- a/editorconfig_checker/wrapper.py +++ /dev/null @@ -1,121 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -from os import remove, rename -from os.path import abspath, dirname, isfile, join as path_join -from platform import architecture, system -from subprocess import call -from tarfile import open as tar_open - -from requests import get - -from editorconfig_checker import __version__ - -EXECUTION_PATH = dirname(abspath(__file__)) - - -def run_editor_config_checker(args): - def get_checker_name(): - ''' - Return the `editorconfig-checker` executable name based on the: - 1) OS. - 2) Architecture. - 3) Executable extension. - - :return: `editorconfig-checker` executable name - :rtype: str - ''' - if isinstance(architecture(), tuple) and len(architecture()): - arch = architecture()[0] - - return 'ec-{}-{}{}'.format( - system().lower(), - 'amd64' if arch == '64bit' else '386', - '.exe' if system() == 'Windows' else '' - ) - - def get_checker_name_with_version(): - ''' - Return the `editorconfig-checker` executable name together with its version. - - :return: `editorconfig-checker` executable name with its version - :rtype: str - ''' - return '{}-{}'.format(get_checker_name(), __version__) - - def download_tar(): - ''' - Download the tar which contains the `editorconfig-checker` executable - from Github. - - :return: Absolute path of the tar file if everything goes fine. - Otherwise, the function returns `None`. - :rtype: Optional[str] - ''' - try: - tar_name = '{}.tar.gz'.format(get_checker_name()) - tar_url = ( - 'https://github.com/editorconfig-checker/editorconfig-checker' - '/releases/download/{}/{}'.format(__version__, tar_name) - ) - tar_path = path_join(EXECUTION_PATH, tar_name) - - response = get(tar_url, stream=True) - if response.status_code == 200: - with open(tar_path, 'wb') as fp: - fp.write(response.raw.read()) - - return tar_path - - return None - except BaseException: - return None - - def process_tar(tar_path): - ''' - Extract the directory `bin` contained in the tar file and remove - the archive. - - :return: `True` if the tar has been processed correctly. - Otherwise, the function returns `False`. - :rtype: bool - ''' - if not isfile(tar_path): - return 1 - - ok = True - tar = None - try: - tar = tar_open(tar_path, 'r') - tar.extractall(path=EXECUTION_PATH) - tar.close() - - # Rename executable based on the version - old_fn = path_join(EXECUTION_PATH, 'bin', get_checker_name()) - new_fn = path_join(EXECUTION_PATH, 'bin', get_checker_name_with_version()) - rename(old_fn, new_fn) - except BaseException: - if tar: - tar.close() - ok = False - - # No error if 'remove' raises an exception. The aim of the function - # is to extract the executable from the archive. - try: - remove(tar_path) - except BaseException: - pass - - return ok - - # Check if the `editorconfig-checker` exists - edc_path = path_join(EXECUTION_PATH, 'bin', get_checker_name_with_version()) - if not isfile(edc_path): - # `editorconfig-checker` does not exist in the system. Try to download it - tar_path = download_tar() - if not tar_path: - return 1 - - if not process_tar(tar_path): - return 2 - - return call([edc_path] + args) diff --git a/publish.sh b/publish.sh index 4c32afa..56121bd 100644 --- a/publish.sh +++ b/publish.sh @@ -2,12 +2,10 @@ set -e -# Run tests -bash test.sh - -# Remove generated files +# Run tests & cleanup +make test make clean # Build & publish (currently, we push the package under the username `mmicu_00`) -python3 setup.py sdist bdist_wheel +python3 setup.py sdist twine upload dist/* diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 67cfc1f..0000000 --- a/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -flake8==3.9.1 -pycodestyle==2.6.0 -requests==2.25.1 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..707dcb1 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,25 @@ +[metadata] +name = editorconfig-checker +description = Python wrapper around invoking editorconfig-checker (https://github.com/editorconfig-checker/editorconfig-checker) +long_description = file: README.md +long_description_content_type = text/markdown +url = https://github.com/editorconfig-checker/editorconfig-checker.python +author = Marco M. +author_email = mmicu.github00@gmail.com +license = MIT +license_file = LICENSE +classifiers = + Programming Language :: Python + Programming Language :: Python :: 2 + Programming Language :: Python :: 2.7 + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.5 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Topic :: Text Processing + Topic :: Utilities + +[options] +python_requires = >=2.7 diff --git a/setup.py b/setup.py index 0b4aa82..def4620 100644 --- a/setup.py +++ b/setup.py @@ -1,60 +1,172 @@ #!/usr/bin/python3 # -*- coding: utf-8 -*- -from io import open as io_open -from os import path as os_path -from re import search as re_search +""" +This setup logic is highly ispired to the one used in `https://github.com/shellcheck-py/shellcheck-py`. + +After `https://github.com/editorconfig-checker/editorconfig-checker.python/issues/15` was opened, +we decided to move the wrapper logic directly in the setup phase. + +During setup, the tarball that contains the executable will be downloaded based on +the target machine and its content extracted in the proper output directory. + +Once the setup is complete, the `ec` executable should be available on your machine. +""" + +from io import BytesIO +from distutils.command.build import build as orig_build +from distutils.core import Command +from os import chmod, makedirs, path, stat +from platform import architecture, system from setuptools import setup +from setuptools.command.install import install as orig_install +from stat import S_IXUSR, S_IXGRP, S_IXOTH +from tarfile import open as tarfile_open + +try: + # Python 3 + from urllib.request import urlopen +except ImportError: + # Python 2.7 + from urllib2 import urlopen + + +WRAPPER_VERSION = '2.3.53' +EDITORCONFIG_CHECKER_CORE_VERSION = '2.3.5' +EDITORCONFIG_CHECKER_EXE_NAME = 'ec' + + +def get_tarball_url(): + def get_ec_name_by_system(): + if isinstance(architecture(), tuple) and len(architecture()) > 0: + arch = architecture()[0] + else: + raise ValueError('Cannot obtain architecture') + + return 'ec-{}-{}{}'.format( + system().lower(), + 'amd64' if arch == '64bit' else '386', + '.exe' if system() == 'Windows' else '' + ) + + return 'https://github.com/editorconfig-checker/editorconfig-checker/releases/download/{}/{}'.format( + EDITORCONFIG_CHECKER_CORE_VERSION, + '{}.tar.gz'.format(get_ec_name_by_system()) + ) + + +def download_tarball(url): + sock = urlopen(url) + code = sock.getcode() + + if code != 200: + sock.close() + raise ValueError('HTTP failure. Code: {}'.format(code)) + + data = sock.read() + sock.close() + + return data + + +def extract_tarball(url, data): + with BytesIO(data) as bio: + if '.tar.' in url: + with tarfile_open(fileobj=bio) as tarf: + for info in tarf.getmembers(): + if info.isfile() and info.name.startswith('bin/ec-'): + return tarf.extractfile(info).read() + + raise AssertionError('unreachable `extract` function') + + +def save_executables(data, base_dir): + exe = EDITORCONFIG_CHECKER_EXE_NAME + if system() == 'Windows': + exe += '.exe' + + output_path = path.join(base_dir, exe) + makedirs(base_dir) + + with open(output_path, 'wb') as fp: + fp.write(data) + + # Mark as executable ~ https://stackoverflow.com/a/14105527 + mode = stat(output_path).st_mode + mode |= S_IXUSR | S_IXGRP | S_IXOTH + chmod(output_path, mode) + + +class build(orig_build): + sub_commands = orig_build.sub_commands + [('fetch_binaries', None)] + + +class install(orig_install): + sub_commands = orig_install.sub_commands + [('install_editorconfig_checker', None)] + + +class fetch_binaries(Command): + build_temp = None + + def initialize_options(self): + pass + + def finalize_options(self): + self.set_undefined_options('build', ('build_temp', 'build_temp')) + + def run(self): + # save binary to self.build_temp + url = get_tarball_url() + archive = download_tarball(url) + data = extract_tarball(url, archive) + save_executables(data, self.build_temp) + + +class install_editorconfig_checker(Command): + description = 'install the editorconfig-checker executable' + outfiles = () + build_dir = install_dir = None + + def initialize_options(self): + pass + + def finalize_options(self): + # this initializes attributes based on other commands' attributes + self.set_undefined_options('build', ('build_temp', 'build_dir')) + self.set_undefined_options('install', ('install_scripts', 'install_dir')) + + def run(self): + self.outfiles = self.copy_tree(self.build_dir, self.install_dir) + + def get_outputs(self): + return self.outfiles + + +command_overrides = { + 'install': install, + 'install_editorconfig_checker': install_editorconfig_checker, + 'build': build, + 'fetch_binaries': fetch_binaries, +} + + +try: + from wheel.bdist_wheel import bdist_wheel as orig_bdist_wheel +except ImportError: + pass +else: + class bdist_wheel(orig_bdist_wheel): + def finalize_options(self): + orig_bdist_wheel.finalize_options(self) + # Mark us as not a pure python package + self.root_is_pure = False + + def get_tag(self): + _, _, plat = orig_bdist_wheel.get_tag(self) + # We don't contain any python source, nor any python extensions + return 'py2.py3', 'none', plat + + command_overrides['bdist_wheel'] = bdist_wheel + -with io_open('README.md', 'rt', encoding='utf8') as fp: - readme = fp.read() - -with io_open(os_path.join('editorconfig_checker', '__init__.py'), 'rt', encoding='utf8') as fp: - version = re_search(r'__version__ = \'(.*?)\'', fp.read()).group(1) - -setup( - name='editorconfig-checker', - version=version, - url='https://editorconfig-checker.github.io', - project_urls={ - 'Documentation': 'https://editorconfig-checker.github.io', - 'Code': 'https://github.com/editorconfig-checker/editorconfig-checker.python', - 'Issue tracker': 'https://github.com/editorconfig-checker/editorconfig-checker/issues', - }, - license='MIT', - author='Marco M.', - author_email='mmicu.github00@gmail.com', - maintainer='Marco M., Max StrĂ¼bing', - maintainer_email='mmicu.github00@gmail.com, mxstrbng@gmail.com', - description='A tool to verify that your files are in harmony with your .editorconfig', - long_description=readme, - long_description_content_type='text/markdown', - classifiers=[ - 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Topic :: Text Processing', - 'Topic :: Utilities' - ], - packages=['editorconfig_checker'], - include_package_data=True, - python_requires='>=2.7', - install_requires=[ - 'requests>=2.22' - ], - extras_require={ - 'dev': [ - 'flake8', - 'pycodestyle' - ] - }, - entry_points={ - 'console_scripts': [ - 'editorconfig-checker = editorconfig_checker.__main__:main' - ] - } -) +setup(version=WRAPPER_VERSION, cmdclass=command_overrides) diff --git a/test.sh b/test.sh index 85c4424..0116338 100644 --- a/test.sh +++ b/test.sh @@ -8,16 +8,16 @@ PY_DOCKER_IMAGES=("2.7.16-slim" "3.7.4-slim") # "editorconfig-checker" -> PyPI package PACKAGES=("." "editorconfig-checker") -DOCKERFILE_TEMPLATE=tests/Dockerfile.template +DOCKERFILE_TEMPLATE="tests/Dockerfile.template" for py_docker_image in "${PY_DOCKER_IMAGES[@]}"; do for package in "${PACKAGES[@]}"; do is_local="0" if [[ "$package" == "." ]]; then - package_pp=local + package_pp="local" is_local="1" elif [[ "$package" == "editorconfig-checker" ]]; then - package_pp=pypi + package_pp="pypi" else echo "Unknown package '$package'. Valid values are '.' and 'editorconfig-checker'." exit 1 @@ -26,25 +26,25 @@ for py_docker_image in "${PY_DOCKER_IMAGES[@]}"; do echo "docker image: $py_docker_image ~ package: $package ($package_pp)" # Generate a valid Dockerfile from a template file - dockerfile=tests/Dockerfile-$py_docker_image-$package_pp - cp $DOCKERFILE_TEMPLATE $dockerfile - sed -i '' "s/\$IMAGE/$py_docker_image/g" $dockerfile - sed -i '' "s/\$PACKAGE/$package/g" $dockerfile + dockerfile="tests/Dockerfile-$py_docker_image-$package_pp" + cp "$DOCKERFILE_TEMPLATE" "$dockerfile" + sed -i "s/\$IMAGE/$py_docker_image/g" "$dockerfile" + sed -i "s/\$PACKAGE/$package/g" "$dockerfile" echo "Running docker file in $dockerfile" # Build & run - docker_image=editorconfig-checker-$py_docker_image-$package_pp:latest - docker build -t $docker_image -f $dockerfile --no-cache --quiet . - docker run --rm $docker_image + docker_image="editorconfig-checker-$py_docker_image-$package_pp:latest" + docker build -t "$docker_image" -f "$dockerfile" --no-cache --quiet . + docker run --rm "$docker_image" # Run coding style tools if [[ "$is_local" == "1" ]]; then - docker run --rm $docker_image make coding_style + docker run --rm "$docker_image" make coding_style fi # Remove the created image - docker image rm $docker_image > /dev/null + docker image rm "$docker_image" > /dev/null echo "" done diff --git a/tests/Dockerfile.template b/tests/Dockerfile.template index 1a70947..846d158 100644 --- a/tests/Dockerfile.template +++ b/tests/Dockerfile.template @@ -7,5 +7,5 @@ WORKDIR /app RUN set -x \ && apt-get update \ && apt-get install -y make \ - && pip install -r requirements.txt \ + && pip install -r dev_requirements.txt \ && pip install --no-cache-dir $PACKAGE