Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
delsauce committed Nov 11, 2022
1 parent bf2897e commit 3ea933e
Show file tree
Hide file tree
Showing 5 changed files with 219 additions and 0 deletions.
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# fw-precommit-hooks
Contains custom hooks for the [pre-commit](https://pre-commit.com/) python package for checking source code.

## Support
This works with two different tools to format source code with the help of pre-commit:
* [astyle](https://astyle.sourceforge.net/) - an old package that does a pretty decent job, but no longer maintained
* [clang-format](https://clang.llvm.org/docs/ClangFormat.html) - a newer tools that is well supported with lots of formatting options

## Usage
In order to use this, you need to add a section to your .pre-commit-config.yaml file to get this hook that specifies which of the formatters you want to use and give it a path to the options file(s). You can use either or both `astyle` or `clang-format`

Here is an example of clang format usage:
```
- repo: https://github.com/delsauce/pre-commit-hooks
rev: v1.0.0
hooks:
- id: format-c-source
types: [file, c]
args: [--clangformat, 'path-to-clang-format-options-file']
```

If you'd rather use `astyle` change the args:
```
args: [--astlye, 'path-to-astyle-options-file']
```
4 changes: 4 additions & 0 deletions hooks/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"""
Version info for this package
"""
__version__ = "1.0.0"
134 changes: 134 additions & 0 deletions hooks/format_c_source.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
"""
This module runs various style tools to format source code files. It is meant to be used
in conjunction with the python module precommit (https://pre-commit.com/) to help enforce
guidelines before code is committed to a git repository.
"""
import os
import subprocess
import argparse
import shutil
import sys
from typing import (
Optional,
Sequence
)

def main(argv: Optional[Sequence[str]] = None) -> int:
"""
Format specified text source files
:param argv: list of filenames to process
:return program exit code, 0 = success, > 0 fail (1 for generic error)
"""

parser = argparse.ArgumentParser()
parser.add_argument(
'filenames', nargs='*',
help='Filenames pre-commit believes are changed.',
)

# Eventually we can add other tool options here...
parser.add_argument(
'-a', '--astyle', type=str,
help='path to astyle config file',
)
parser.add_argument(
'-c', '--clangformat', type=str,
help='path to clang-format options file',
)

args = parser.parse_args(argv)

if not (args.astyle or args.clangformat):
parser.error('No action requested, add --astyle or --clangformat')

# Bail early if there are no files to process
if len(args.filenames) == 0:
print('No files')
return 0

# Note: sourcetree doesn't use the same system path when running commit hooks (there
# is a bug in their tool). We can try to fix that here (not really sure what
# happens on windows -- so not handling that now)
if sys.platform != 'win32':
if '/usr/local/bin' not in os.environ['PATH']:
os.environ['PATH'] += os.pathsep + '/usr/local/bin'

if args.astyle:
# check to make sure astyle is installed
locate = shutil.which('astyle')

if locate is None:
print('astyle executable not found on in PATH, is it installed?')
print('Consult your favorite package manager for installation')
print('(e.g. \'brew install astyle\' on mac or \'apt-get install astyle\' on Ubuntu)')
return 1

# Check to make sure that options file is present
if not os.path.exists(args.astyle):
print(f'{args.astyle} not found. Please check that the file exists')
return 1

# Run astyle
for fname in args.filenames:
# Since check=True below, this will throw an exception if the call
# to astyle fails for some reason. Note, as long as the options and
# filename passed to astyle are OK, it should never fail (always returns
# a '0' whether it formatted a file or not. Need to check the output
# to know if it actually changed anything in the file or not.
astyle_result = subprocess.run(["astyle",
"--options=" + args.astyle, fname],
capture_output=True, text=True,
check=True)

if 'Formatted' in astyle_result.stdout:
print('Formatted: ' + fname)


if args.clangformat:
# check to make sure astyle is installed
locate = shutil.which('clang-format')

if locate is None:
print('clang-format executable not found on in PATH, is it installed?')
print('Consult your favorite package manager for installation')
print('(e.g. \'brew install clang-format\' on mac or \'apt-get install clang-format\' on Debian/Ubuntu)')
return 1

# Check to make sure that options file is present
if not os.path.exists(args.clangformat):
print(f'{args.clangformat} not found. Please check that the file exists')
return 1

# Run clang-format
for fname in args.filenames:
# Since check=True below, this will throw an exception if the call
# to clang-format fails for some reason. Note, as long as the options and
# filename passed to clang-format are OK, it should never fail.

# First do a dry run to see if the file needs formatting or not
# (this is just to support better info being printed during execution)
clang_result = subprocess.run(["clang-format",
"--dry-run",
"--Werror",
"--style=file:" + args.clangformat,
"-i", fname],
capture_output=True, text=True)

if clang_result.returncode != 0:
# Error code set means the file needs formatting
clang_result = subprocess.run(["clang-format",
"--style=file:" + args.clangformat,
"-i", fname])

if clang_result.returncode == 0:
print('Formatted: ' + fname)
else:
print('clang-format can not format file: ' + clang_result.stdout)
return 1

return 0


if __name__ == '__main__':
sys.exit(main())
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# No requirements necessary for the module, add to this file if changes needed

54 changes: 54 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
'''
pip package configuration for embedded c coding standard compliance
'''
import codecs
import os
from setuptools import setup

# pylint: disable-next=consider-using-with
reqs: list = open("requirements.txt", "r",encoding='utf-8').read().splitlines()

def read(rel_path):
'''
Supporting function to read the __init__.py file from the package
to get attributes. See get_version() below for more details.
'''
here = os.path.abspath(os.path.dirname(__file__))
# intentionally *not* adding an encoding option to open, See:
# https://github.com/pypa/virtualenv/issues/201#issuecomment-3145690
with codecs.open(os.path.join(here, rel_path), 'r') as file_pointer:
return file_pointer.read()

def get_version(rel_path):
'''
Allows the version of the package to be fetched via the one
stored in the __init__.py file. This enables a single location
to store this information (rather than having to pass it in
with the metadata in the call to setup())
See here:
https://packaging.python.org/guides/single-sourcing-package-version/
'''
for line in read(rel_path).splitlines():
if line.startswith('__version__'):
delim = '"' if '"' in line else "'"
return line.split(delim)[1]

raise RuntimeError("Unable to find version string.")

setup(name='pre_commit_hooks',
description='C/C++ File formatting and cleanup per coding standards',
url='https://github.com/delsauce/pre-commit-hooks',
license='MIT',
packages=['pre_commit_hooks'],
zip_safe=False,
version=get_version("hooks/__init__.py"),
entry_points={
'console_scripts': [
'format-c-source=hooks.format_c_source:main',
],
},

python_requires='>=3.7',
install_requires=reqs
)

0 comments on commit 3ea933e

Please sign in to comment.