diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8154844 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*~ +*.py[co] +*.egg-info +/build/ +/dist/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..e8d658f --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +# Fab Polish + +Run various checks against source code using Fabric + +## Installation + +`pip install fab-polish` + +## Usage + +### Minimal Usage + +Create a `fabfile.py` in your source code with the following minimal code: + +```python +from fabpolish import polish +from fabpolish.contrib import find_merge_conflict_leftovers +``` + +Now run `fab polish`. The above example runs a sniff that finds bad merge +commits by checking if symbols like '<<<<<<<' are present in the versioned +files. + +### Writing Sniffs + +You can create your own sniff by using the sniff decorator: + +```python +from fabpolish import polish, sniff, local, info + +@sniff(severity='critical', timing='fast') +def check_var_dump(): + info("Checking var_dump statements...") + return local("! git grep 'var_dump'") +``` + +Severity can be 'critical', 'major', 'minor', 'info'. Default is 'critical'. +Timing can be 'slow', 'fast'. Default is 'fast'. + +When using default values, the sniff decorator can be used without the function +call like so: + +```python +@sniff +def your_sniff(): + # code +``` + +Check https://github.com/practo/FabPolish/blob/master/fabpolish/contrib.py for more examples. + +### Modifying Imported Sniffs + +The severity, timing values can be altered for any sniff imported from contrib +using `update_sniff` function like follows: + +```python +from fabpolish import update_sniff +from fabpolish.contrib import find_pep8_violations + +update_sniff(find_pep8_violations, severity='major', timing='fast') +``` + +### Running All Sniffs + +By default `fab polish` runs only fast-critical and fast-major sniffs. In a CI +environment, to run all the sniffs including slow, minor ones, run `fab polish:ci` diff --git a/fabfile.py b/fabfile.py new file mode 100644 index 0000000..bed5cac --- /dev/null +++ b/fabfile.py @@ -0,0 +1,7 @@ +from fabpolish import polish, update_sniff +from fabpolish.contrib import ( + find_merge_conflict_leftovers, + find_pep8_violations +) + +update_sniff(find_pep8_violations, severity='major', timing='fast') diff --git a/fabpolish/__init__.py b/fabpolish/__init__.py new file mode 100644 index 0000000..fd423fd --- /dev/null +++ b/fabpolish/__init__.py @@ -0,0 +1,125 @@ +import os +import sys + +from functools import wraps + +from fabric.api import lcd, local, settings, task, puts, hide +from fabric.colors import green + +import fabfile + +FABFILE_DIR = os.path.abspath(os.path.dirname(fabfile.__file__)) + +__version__ = '1.0.0' + + +def info(text): + puts(green(text)) + + +def validate_severity(severity): + severity_options = ['critical', 'major', 'minor', 'info'] + if severity not in severity_options: + raise ValueError('severity must be one of: ' + str(severity_options)) + + +def validate_timing(timing): + timing_options = ['slow', 'fast'] + if timing not in timing_options: + raise ValueError('timing must be one of: ' + str(timing_options)) + + +_sniffs = [] + + +def sniff(*args, **kwargs): + """ Decorator to collect sniffs and execute on polish + :param severity: Keyword argument only. + One of 'critical', 'major', 'minor', 'info' + Default: 'critical' + :type severity: str + :param timing: Keyword argument only. One of 'slow', 'fast' + Default: 'fast' + :type timing: str + """ + DEFAULT_SEVERITY = 'critical' + DEFAULT_TIMING = 'fast' + invoked = bool(not args or kwargs) + severity = kwargs.get('severity', DEFAULT_SEVERITY) + timing = kwargs.get('timing', DEFAULT_TIMING) + validate_severity(severity) + validate_timing(timing) + + def decorator(func): + @task + @wraps(func) + def wrapper(*args, **kwargs): + with lcd(FABFILE_DIR), settings(hide('running')): + return func(*args, **kwargs) + _sniffs.append({ + 'severity': severity, + 'timing': timing, + 'function': wrapper + }) + return wrapper + return decorator if invoked else decorator(args[0]) + + +@task +def polish(env='dev'): + """Polish code by running some or all sniffs + :param env: Environment to determine what all sniffs to run + Options: 'dev', 'ci' + Default: 'dev' + :type env: str + + When environment is 'ci', all the sniffs registered are run. + When environment is 'dev', only fast-critical and fast-major + sniffs are run. + """ + results = list() + with settings(warn_only=True): + if env == 'ci': + sniffs_to_run = _sniffs + elif env == 'dev': + sniffs_to_run = [] + for sniff in _sniffs: + if sniff['timing'] != 'fast': + continue + if sniff['severity'] not in ('critical', 'major'): + continue + sniffs_to_run.append(sniff) + else: + raise ValueError('env must be one of: ' + str(['dev', 'ci'])) + for sniff in sniffs_to_run: + results.append(sniff['function']()) + + if any(result.failed for result in results): + sys.exit(1) + + +def update_sniff(function, severity=None, timing=None): + if type(function) == str: + function_name = function + else: + function_name = function.name + for sniff in _sniffs: + if sniff['function'].name == function_name: + break + else: + raise ValueError('function is not a sniff or is not loaded') + if severity is not None: + validate_severity(severity) + sniff['severity'] = severity + if timing is not None: + validate_timing(timing) + sniff['timing'] = timing + + +__all__ = [ + 'sniff', + 'info', + 'local', + 'polish', + 'update_sniff' +] diff --git a/fabpolish/contrib.py b/fabpolish/contrib.py new file mode 100644 index 0000000..65a645a --- /dev/null +++ b/fabpolish/contrib.py @@ -0,0 +1,33 @@ +from fabpolish import sniff, info, local + + +@sniff(severity='critical', timing='fast') +def find_merge_conflict_leftovers(): + """Find Merge conflict leftovers + """ + info('Finding merge conflict leftovers...') + return local("! git grep -P '^(<|=|>){7}(?![<=>])'") + + +@sniff(severity='major', timing='slow') +def find_php_syntax_errors(): + """Find syntax error in php files + """ + info('Finding syntax error in php files...') + return local( + "git ls-files -z | " + "grep -PZz '\.(php|phtml)$' | " + "xargs -0 -n 1 php -l >/tmp/debug" + ) + + +@sniff(severity='minor', timing='slow') +def find_pep8_violations(): + """Run pep8 python coding standard check + """ + info('Running coding standards check for python files...') + return local( + "git ls-files -z | " + "grep -PZz '\.py$' | " + "xargs -0 pep8" + ) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..2fff5cd --- /dev/null +++ b/setup.py @@ -0,0 +1,14 @@ +from setuptools import setup + +setup(name='fab-polish', + version='1.0.0', + description='Polish git versioned source code using Fabric', + url='https://github.com/practo/FabPolish', + author='J Kishore Kumar', + author_email='kishore@practo.com', + license='MIT', + packages=['fabpolish'], + install_requires=[ + 'fabric', + ], + zip_safe=False)