Skip to content

Commit

Permalink
Add pre-commit action
Browse files Browse the repository at this point in the history
  • Loading branch information
mdeweerd committed Dec 19, 2023
1 parent 03ece9a commit f7ef621
Show file tree
Hide file tree
Showing 5 changed files with 1,235 additions and 961 deletions.
227 changes: 227 additions & 0 deletions .github/logToCs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
#!/usr/bin/python3
"""
Convert a log to CheckStyle format.
Url: https://github.com/mdeweerd/LogToCheckStyle
The log can then be used for generating annotations in a github action.
Note: this script is very young and "quick and dirty".
Patterns can be added to "PATTERNS" to match more messages.
# Examples
Assumes that logToCs.py is available as .github/logToCs.py.
## Example 1:
```yaml
- run: |
pre-commit run -all-files | tee pre-commit.log
.github/logToCs.py pre-commit.log pre-commit.xml
- uses: staabm/annotate-pull-request-from-checkstyle-action@v1
with:
files: pre-commit.xml
notices-as-warnings: true # optional
```
## Example 2:
```yaml
- run: |
pre-commit run --all-files | tee pre-commit.log
- name: Add results to PR
if: ${{ always() }}
run: |
.github/logToCs.py pre-commit.log | cs2pr
```
Author(s):
- https://github.com/mdeweerd
License: MIT License
"""

import argparse
import re
import sys
import xml.etree.ElementTree as ET


def convert_to_checkstyle(messages):
root = ET.Element("checkstyle")
for message in messages:
fields = parse_message(message)
if fields:
add_error_entry(root, **fields)
return ET.tostring(root, encoding="utf-8").decode("utf-8")


ANY_REGEX = r".*?"
FILE_REGEX = r"\s*(?P<file_name>\S.*?)\s*?"
LINE_REGEX = r"\s*(?P<line>\d+?)\s*?"
COLUMN_REGEX = r"\s*(?P<column>\d+?)\s*?"
SEVERITY_REGEX = r"\s*(?P<severity>error|warning|notice)\s*?"
MSG_REGEX = r"\s*(?P<message>.+?)\s*?"
# cpplint confidence index
CONFIDENCE_REGEX = r"\s*\[(?P<confidence>\d+)\]\s*?"


# List of message patterns, add more specific patterns earlier in the list
# Creating patterns by using constants makes them easier to define and read.
PATTERNS = [
# ESLint (JavaScript Linter), RoboCop, shellcheck
# path/to/file.js:10:2: Some linting issue
# path/to/file.rb:10:5: Style/Indentation: Incorrect indentation detected
# path/to/script.sh:10:1: SC2034: Some shell script issue
re.compile(f"^{FILE_REGEX}:{LINE_REGEX}:{COLUMN_REGEX}: {MSG_REGEX}$"),
# Cpplint default output:
# '%s:%s: %s [%s] [%d]\n'
# % (filename, linenum, message, category, confidence)
re.compile(f"^{FILE_REGEX}:{LINE_REGEX}:{MSG_REGEX}{CONFIDENCE_REGEX}$"),
# MSVC
# file.cpp(10): error C1234: Some error message
re.compile(
f"^{FILE_REGEX}\\({LINE_REGEX}\\):{SEVERITY_REGEX}{MSG_REGEX}$"
),
# Java compiler
# File.java:10: error: Some error message
re.compile(f"^{FILE_REGEX}:{LINE_REGEX}:{SEVERITY_REGEX}:{MSG_REGEX}$"),
# Python
# File ".../logToCs.py", line 90 (note: code line follows)
re.compile(f'^File "{FILE_REGEX}", line {LINE_REGEX}$'),
# Pylint, others
# path/to/file.py:10: [C0111] Missing docstring
# others
re.compile(f"^{FILE_REGEX}:{LINE_REGEX}: {MSG_REGEX}$"),
]

# Severities available in CodeSniffer report format
SEVERITY_NOTICE = "notice"
SEVERITY_WARNING = "warning"
SEVERITY_ERROR = "error"


def parse_message(message):
"""
Parse message until it matches a pattern.
Returns the fields in a dict.
"""
for pattern in PATTERNS:
m = pattern.match(message)
if not m:
continue
result = m.groupdict()
if len(result) == 0:
continue

if "confidence" in result:
# Convert confidence level of cpplint
# to warning, etc.
confidence = int(result["confidence"])
del result["confidence"]

if confidence <= 1:
severity = SEVERITY_NOTICE
elif confidence >= 5:
severity = SEVERITY_ERROR
else:
severity = SEVERITY_WARNING
result["severity"] = severity

if "severity" not in result:
result["severity"] = SEVERITY_ERROR
else:
result["severity"] = result["severity"].lower()

return result

# Nothing matched
return None


def add_error_entry(
root,
severity,
file_name,
line=None,
column=None,
message=None,
source=None,
):
file_element = find_or_create_file_element(root, file_name)
error_element = ET.SubElement(file_element, "error")
error_element.set("severity", severity)
if line:
error_element.set("line", line)
if column:
error_element.set("column", column)
if message:
error_element.set("message", message)
if source:
# To verify if this is a valid attribute
error_element.set("source", source)


def find_or_create_file_element(root, file_name):
for file_element in root.findall("file"):
if file_element.get("name") == file_name:
return file_element
file_element = ET.SubElement(root, "file")
file_element.set("name", file_name)
return file_element


def main():
parser = argparse.ArgumentParser(
description="Convert messages to Checkstyle XML format."
)
parser.add_argument(
"input", help="Input file. Use '-' for stdin.", nargs="?", default="-"
)
parser.add_argument(
"output",
help="Output file. Use '-' for stdout.",
nargs="?",
default="-",
)
parser.add_argument(
"-i",
"--input-named",
help="Named input file. Overrides positional input.",
)
parser.add_argument(
"-o",
"--output-named",
help="Named output file. Overrides positional output.",
)

args = parser.parse_args()

if args.input == "-" and args.input_named:
with open(args.input_named) as input_file:
messages = input_file.readlines()
elif args.input != "-":
with open(args.input) as input_file:
messages = input_file.readlines()
else:
messages = sys.stdin.readlines()

checkstyle_xml = convert_to_checkstyle(messages)

if args.output == "-" and args.output_named:
with open(args.output_named, "w") as output_file:
output_file.write(checkstyle_xml)
elif args.output != "-":
with open(args.output, "w") as output_file:
output_file.write(checkstyle_xml)
else:
print(checkstyle_xml)


if __name__ == "__main__":
main()
48 changes: 48 additions & 0 deletions .github/workflows/pre-commit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
name: pre-commit
on:
pull_request:
push:
jobs:
pre-commit:
runs-on: ubuntu-latest
env:
LOG_TO_CS: .github/logToCs.py
RAW_LOG: pre-commit.log
CS_XML: pre-commit.xml
steps:
- run: sudo apt-get update && sudo apt-get install cppcheck
if: false
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
if: false
with:
cache: pip
python-version: "3.11"
- run: python -m pip install pre-commit
- uses: actions/cache/restore@v3
with:
path: ~/.cache/pre-commit/
key: pre-commit-4|${{ env.pythonLocation }}|${{ hashFiles('.pre-commit-config.yaml')
}}
- name: Run pre-commit hooks
run: |
set -o pipefail
pre-commit run --show-diff-on-failure --color=always --all-files | tee ${RAW_LOG}
- name: Convert Raw Log to CheckStyle format
if: ${{ failure() }}
run: |
${LOG_TO_CS} ${RAW_LOG} ${CS_XML}
- name: Annotate Source Code with Messages
uses: staabm/annotate-pull-request-from-checkstyle-action@v1
if: ${{ failure() }}
with:
files: ${{ env.CS_XML }}
notices-as-warnings: true # optional
prepend-filename: true # optional
- uses: actions/cache/save@v3
if: ${{ always() }}
with:
path: ~/.cache/pre-commit/
key: pre-commit-4|${{ env.pythonLocation }}|${{ hashFiles('.pre-commit-config.yaml')
}}
1 change: 0 additions & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -337,4 +337,3 @@ proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ This is the script originally developed by Matthew Montgomery, and previously ho
This repository will be used to handle ongoing development and bugfixes for this script. Please file bug reports <a href="https://github.com/BMDan/tuning-primer.sh/issues">here</a>. Pull requests are welcomed.

## About this script
This script takes information from `SHOW STATUS LIKE...` and `SHOW VARIABLES LIKE...` to produce sane recomendations for tuning server variables.
This script takes information from `SHOW STATUS LIKE...` and `SHOW VARIABLES LIKE...` to produce sane recomendations for tuning server variables.

It (attempts to be!) compatible with all versions of MySQL 3.23 and higher (including 5.x), as well as MariaDB, and Percona Server for MySQL.&sup2;

Expand Down
Loading

0 comments on commit f7ef621

Please sign in to comment.