Skip to content
This repository has been archived by the owner on May 26, 2024. It is now read-only.

Commit

Permalink
Add option to run linter on complete directory
Browse files Browse the repository at this point in the history
  • Loading branch information
lgulich committed Jun 16, 2020
1 parent eed750b commit f0242b1
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 90 deletions.
18 changes: 11 additions & 7 deletions lib/lg_linter/scripts/init_lg_linter
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,22 @@ chmod +x "$REPO_DIR"/.git/hooks/pre-commit
PRE_COMMIT_SCRIPT="#!/usr/bin/env python3
import os
import pathlib
from pathlib import Path
from lg_linter.src.lg_linter import lint
from lg_linter.src.lg_linter import LinterConfig, lint
def main():
repo_path = pathlib.Path(__file__).parent.parent.parent.resolve()
if lint(repo_path):
return 0
else:
return 1
repo_path = Path(__file__).parent.parent.parent.resolve()
config = LinterConfig(repo_path, False)
linted_successfully = lint(config)
if not linted_successfully:
return 1
return 0
if __name__ == '__main__':
exit(main())
Expand Down
17 changes: 13 additions & 4 deletions lib/lg_linter/scripts/lint_repo
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
#!/usr/bin/env python3
import argparse
import pathlib
import sys
from pathlib import Path
sys.path.append('../src')

from lg_linter import lint
from lg_linter.src.lg_linter import LinterConfig, lint


def main():
parser = argparse.ArgumentParser(description='Lint all staged files')
parser.add_argument('repo_path')
parser.add_argument('directory_path')
parser.add_argument(
'-a',
'--all',
action='store_true',
help='Lint all files (default is to only lint staged files).')
args = parser.parse_args()

if not lint(pathlib.Path(args.repo_path)):
config = LinterConfig(Path(args.directory_path), args.all)

linted_successfully = lint(config)

if not linted_successfully:
return 1

return 0
Expand Down
7 changes: 4 additions & 3 deletions lib/lg_linter/src/file_linters.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import os
import subprocess
from pathlib import Path


def lint_cpp_file(file_path):
def lint_cpp_file(file_path: Path) -> (bool, str):
os.system(f'clang-format -i {file_path}')
os.system(f'git add {file_path}')
completed_process = subprocess.run(['cpplint', file_path],
Expand All @@ -17,7 +18,7 @@ def lint_cpp_file(file_path):
return False, completed_process.stderr.decode('utf-8')


def lint_python_file(file_path):
def lint_python_file(file_path: Path) -> (bool, str):
os.system(f'yapf -i {file_path}')
os.system(f'git add {file_path}')
completed_process = subprocess.run(['pylint', '--reports=n', file_path],
Expand All @@ -30,7 +31,7 @@ def lint_python_file(file_path):
return False, completed_process.stdout.decode('utf-8')


def lint_shell_file(file_path):
def lint_shell_file(file_path: Path) -> (bool, str):
completed_process = subprocess.run(['shellcheck', file_path],
check=False,
stdout=subprocess.PIPE)
Expand Down
180 changes: 108 additions & 72 deletions lib/lg_linter/src/lg_linter.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import os
from pathlib import Path
from dataclasses import dataclass

import git

Expand All @@ -9,52 +11,85 @@
'========================')


def print_file_names(files):
for elem in files:
print(f'- {elem.a_path}')
@dataclass
class LinterConfig:
directory_path: Path
lint_all: bool = False


def files_were_modified_after_staging(modified_files, staged_files):
for modified_file in modified_files:
if modified_file.a_path in [x.a_path for x in staged_files]:
return True

return False
def lint(config: LinterConfig):
"""
Run complete linter.
"""
print(DASHED_DOUBLE_LINE)
print('Running linter...')

if config.lint_all:
files = get_all_files(config.directory_path)
else:
files = get_staged_files(config.directory_path)

def get_file_type(file_path):
EXTENSIONS = {
'cpp': ['.h', '.hpp', '.cc', '.cpp'],
'python': ['.py'],
'shell': ['.sh', '.bash']
}
success = True
outputs = []

SHEBANG_KEYWORDS = {
'cpp': [],
'python': ['python'],
'shell': ['sh', 'bash']
}
for file in files:
print(f'Linting {file}... ', end='')
file_is_ok, stdout = lint_file(file)
if file_is_ok:
print('[\033[92mOK\033[0m]')
else:
print('[\033[91mFAIL\033[0m]')
success = False
outputs.append(stdout)

_, extension = os.path.splitext(file_path)
if success:
print('\nHurray linted succesfully!')

type_name = None
if extension:
for (file_type, extensions_for_type) in EXTENSIONS.items():
if extension in extensions_for_type:
type_name = file_type
break
else:
shebang = open(file_path).readline()
for (file_type, keywords_for_type) in SHEBANG_KEYWORDS.items():
for keyword in keywords_for_type:
if keyword in shebang:
type_name = file_type
break
print('\nFound the following errors:')
print(DASHED_DOUBLE_LINE)
for output in outputs:
print(output)
print(DASHED_DOUBLE_LINE)

return success

return type_name

def get_staged_files(repo_path: Path):
repo = git.Repo(repo_path)
assert not repo.bare

def lint_file(file_path):
modified_files = repo.index.diff(None)
staged_files = repo.index.diff('HEAD')
if files_were_modified_after_staging(modified_files, staged_files):
raise RuntimeError(
'Found files which were modified after staging. Please'
' stage all modifications first.')

for staged_file in staged_files:
# Do not lint if the file was deleted. It seems as if pythongit has
# a bug and has new_file and deleted_file confused.
if not staged_file.new_file:
yield repo_path / staged_file.a_path


def get_all_files(dir_path: Path):
for root, _, filenames in os.walk(dir_path):
for filename in filenames:
yield Path(root) / filename


def files_were_modified_after_staging(modified_files, staged_files):
for modified_file in modified_files:
if modified_file.a_path in [
staged_file.a_path for staged_file in staged_files
]:
return True

return False


def lint_file(file_path: Path) -> (bool, str):
file_type = get_file_type(file_path)

if file_type == 'cpp':
Expand All @@ -68,46 +103,47 @@ def lint_file(file_path):
'ignored.')


def lint(repo_path):
"""
Lint all the staged files, return true if successful.
"""
print(DASHED_DOUBLE_LINE)
print('Running linter...')
def get_file_type(file_path: Path):
_, extension = os.path.splitext(file_path)

repo = git.Repo(repo_path)
assert not repo.bare
if extension:
return get_file_type_from_extension(extension)

modified_files = repo.index.diff(None)
staged_files = repo.index.diff('HEAD')
if files_were_modified_after_staging(modified_files, staged_files):
print('Error: Found files which were modified after staging. Please'
' stage all modifications first.')
return False
return get_file_type_from_shebang(file_path)

success = True
outputs = []

for staged_file in staged_files:
# Do not lint if the file was deleted. It seems as if pythongit has
# a bug and has new_file and deleted_file confused.
if not staged_file.new_file:
print(f'Linting {staged_file.a_path}... ', end='')
file_is_ok, stdout = lint_file(repo_path / staged_file.a_path)
if file_is_ok:
print('[\033[92mOK\033[0m]')
else:
print('[\033[91mFAIL\033[0m]')
success = False
outputs.append(stdout)
def get_file_type_from_extension(extension: str) -> str:
EXTENSIONS = {
'cpp': ['.h', '.hpp', '.cc', '.cpp'],
'python': ['.py'],
'shell': ['.sh', '.bash']
}

if success:
print("\nHurray linted succesfully!")
else:
print("\nFound the following errors:")
print(DASHED_DOUBLE_LINE)
for output in outputs:
print(output)
print(DASHED_DOUBLE_LINE)
for (file_type, extensions_for_type) in EXTENSIONS.items():
if extension in extensions_for_type:
return file_type

return success
return None


def get_file_type_from_shebang(file_path: Path) -> str:
SHEBANG_KEYWORDS = {
'cpp': [],
'python': ['python'],
'shell': ['sh', 'bash']
}

try:
first_line = open(file_path).readline()
except UnicodeDecodeError:
# Ignore binary files.
return None

if not '#!' in first_line:
return None

for (file_type, keywords_for_type) in SHEBANG_KEYWORDS.items():
for keyword in keywords_for_type:
if keyword in first_line:
return file_type
return None
1 change: 0 additions & 1 deletion requirements.txt

This file was deleted.

7 changes: 4 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,20 @@

setuptools.setup(
name="lg-linter",
version="v0.1.10",
version="v0.1.11",
author="Lionel Gulich",
author_email="[email protected]",
url="https://github.com/lgulich/lg-linter",
description="A pre commit linter for cpp, python and sh.",
long_description=LONG_DESCRIPTION,
long_description_content_type="text/markdown",
download_url="https://github.com/lgulich/lg-linter/archive/v0.1.8.tar.gz",
download_url="https://github.com/lgulich/lg-linter/archive/v0.1.11.tar.gz",
install_requires=[
'dataclasses',
'gitpython',
'lg-cpplint',
'pylint',
'yapf',
'lg-cpplint',
],
classifiers=[
"Programming Language :: Python :: 3 :: Only",
Expand Down

0 comments on commit f0242b1

Please sign in to comment.