diff --git a/.github/workflows/check_translation_tag.py b/.github/workflows/check_translation_tag.py
new file mode 100755
index 000000000..34705f74d
--- /dev/null
+++ b/.github/workflows/check_translation_tag.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python3
+
+# A pre-commit hook for detecting problematic tags
+# Copyright (C) 2021 Mike Tzou (Chocobo1)
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# In addition, as a special exception, the copyright holders give permission to
+# link this program with the OpenSSL project's "OpenSSL" library (or with
+# modified versions of it that use the same license as the "OpenSSL" library),
+# and distribute the linked executables. You must obey the GNU General Public
+# License in all respects for all of the code used other than "OpenSSL". If you
+# modify file(s), you may extend this exception to your version of the file(s),
+# but you are not obligated to do so. If you do not wish to do so, delete this
+# exception statement from your version.
+
+from typing import Optional, Sequence
+import argparse
+import re
+
+def main(argv: Optional[Sequence[str]] = None) -> int:
+ parser = argparse.ArgumentParser()
+ parser.add_argument('filenames', nargs='*', help='Filenames to check')
+ args = parser.parse_args(argv)
+
+ error_msg = ""
+ regex = re.compile(r"\s*")
+
+ for filename in args.filenames:
+ line_counter = 1
+ error_buffer = ""
+
+ with open(filename) as file:
+ try:
+ for line in file:
+ if (match := regex.match(line)) is not None:
+ error_buffer += str(f"Defect file: \"{filename}\"\n"
+ f"Line: {line_counter}\n"
+ f"Column span: {match.span()}\n"
+ f"Part: \"{match.group()}\"\n\n")
+ line_counter += 1
+
+ except UnicodeDecodeError as error:
+ # not a text file, skip
+ continue
+
+ error_msg += error_buffer
+
+ if len(error_msg) > 0:
+ print(error_msg)
+ return 1
+
+ return 0
+
+if __name__ == '__main__':
+ exit(main())
diff --git a/.github/workflows/ci_file_health.yaml b/.github/workflows/ci_file_health.yaml
index 07bd98ec2..739a7ea46 100644
--- a/.github/workflows/ci_file_health.yaml
+++ b/.github/workflows/ci_file_health.yaml
@@ -11,10 +11,7 @@ jobs:
uses: actions/checkout@v2
- name: Install tools
- run: |
- sudo apt update
- sudo apt install zsh
+ uses: actions/setup-python@v2
- name: Check files
- run: |
- ./.github/workflows/file_health.sh
+ uses: pre-commit/action@v2.0.3
diff --git a/.github/workflows/file_health.sh b/.github/workflows/file_health.sh
deleted file mode 100755
index eb21d0054..000000000
--- a/.github/workflows/file_health.sh
+++ /dev/null
@@ -1,85 +0,0 @@
-#!/usr/bin/env zsh
-
-set -o nounset
-
-# Assumption: file names don't contain `:` (for the `cut` invocation).
-# Safe to assume, as such a character in a filename would cause trouble on Windows, a platform we support
-
-# any regression turn this non-zero
-regressions=0
-
-# exclusions (these are just grep extended regular expressions to match against paths relative to the root of the repository)
-exclusions_nonutf8='(.*(7z|gif|ic(ns|o)|png|qm|zip))'
-exclusions_bom='src/base/unicodestrings.h'
-exclusions_tw='(*.ts)|src/webui/www/private/scripts/lib/*'
-exclusions_trailing_newline='configure'
-exclusions_no_lf='(*.ts)|(.*svg)|compile_commands.json|src/webui/www/private/scripts/lib/*'
-
-echo -e "\n*** Detect files not encoded in UTF-8 ***\n"
-
-find . -path ./build -prune -false -o -path ./.git -prune -false -o -type f -exec file --mime {} \; | sort \
- | grep -v -e "charset=us-ascii" -e "charset=utf-8" | cut -d ":" -f 1 \
- | grep -E -v -e "${exclusions_nonutf8}" \
- | tee >(echo -e "--> Files not encoded in UTF-8: found" "$(wc -l < /dev/stdin)" "regression(s)\n") \
- | xargs -I my_input -0 bash -c 'echo "my_input"; test "$(echo -n "my_input" | wc -l)" -eq 0'
-regressions=$((regressions+$?))
-
-echo -e "\n*** Detect files encoded in UTF-8 with BOM ***\n"
-
-grep --exclude-dir={.git,build} -rIl $'\xEF\xBB\xBF' | sort \
- | grep -E -v -e "${exclusions_bom}" \
- | tee >(echo -e "--> Files encoded in UTF-8 with BOM: found" "$(wc -l < /dev/stdin)" "regression(s)\n") \
- | xargs -I my_input -0 bash -c 'echo "my_input"; test "$(echo -n "my_input" | wc -l)" -eq 0'
-regressions=$((regressions+$?))
-
-echo -e "\n*** Detect usage of CR byte ***\n"
-
-grep --exclude-dir={.git,build} -rIlU $'\x0D' | sort \
- | tee >(echo -e "--> Usage of CR byte: found" "$(wc -l < /dev/stdin)" "regression(s)\n") \
- | xargs -I my_input -0 bash -c 'echo "my_input"; test "$(echo -n "my_input" | wc -l)" -eq 0'
-regressions=$((regressions+$?))
-
-echo -e "\n*** Detect trailing whitespace in lines ***\n"
-
-grep --exclude-dir={.git,build} -rIl "[[:blank:]]$" | sort \
- | grep -E -v -e "${exclusions_tw}" \
- | tee >(echo -e "--> Trailing whitespace in lines: found" "$(wc -l < /dev/stdin)" "regression(s)\n") \
- | xargs -I my_input -0 bash -c 'echo "my_input"; test "$(echo -n "my_input" | wc -l)" -eq 0';
-regressions=$((regressions+$?))
-
-echo -e "\n*** Detect too many trailing newlines ***\n"
-
-find . -path ./build -prune -false -o -path ./.git -prune -false -o -type f -exec file --mime {} \; | sort \
- | grep -e "charset=us-ascii" -e "charset=utf-8" | cut -d ":" -f 1 \
- | grep -E -v -e "${exclusions_trailing_newline}" \
- | xargs -L1 -I my_input bash -c 'test "$(tail -q -c2 "my_input" | hexdump -C | grep "0a 0a")" && echo "my_input"' \
- | tee >(echo -e "--> Too many trailing newlines: found" "$(wc -l < /dev/stdin)" "regression(s)\n") \
- | xargs -I my_input -0 bash -c 'echo "my_input"; test "$(echo -n "my_input" | wc -l)" -eq 0'
-regressions=$((regressions+$?))
-
-echo -e "\n*** Detect no trailing newline ***\n"
-
-find . -path ./build -prune -false -o -path ./.git -prune -false -o -type f -exec file --mime {} \; | sort \
- | grep -e "charset=us-ascii" -e "charset=utf-8" | cut -d ":" -f 1 \
- | grep -E -v -e "${exclusions_no_lf}" \
- | xargs -L1 -I my_input bash -c 'test "$(tail -q -c1 "my_input" | hexdump -C | grep "0a")" || echo "my_input"' \
- | tee >(echo -e "--> No trailing newline: found" "$(wc -l < /dev/stdin)" "regression(s)\n") \
- | xargs -I my_input -0 bash -c 'echo "my_input"; test "$(echo -n "my_input" | wc -l)" -eq 0'
-regressions=$((regressions+$?))
-
-echo -e "\n*** Detect translation closing tag in new line ***\n"
-
-grep --exclude-dir={.git,build} -nri "^" | sort \
- | cut -d ":" -f 1,2 \
- | tee >(echo -e "--> Translation closing tag in new line: found" "$(wc -l < /dev/stdin)" "regression(s)\n") \
- | xargs -I my_input -0 bash -c 'echo "my_input"; test "$(echo -n "my_input" | wc -l)" -eq 0'
-regressions=$((regressions+$?))
-
-if [ "$regressions" -ne 0 ]; then
- regressions=1
- echo "\nFile health regressions found. Please fix them (or add them as exclusions)."
-else
- echo "All OK, no file health regressions found."
-fi
-
-exit $regressions;
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 000000000..ced7adbf5
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,55 @@
+repos:
+ - repo: local
+ hooks:
+ - id: check-translation-tag
+ name: Check newline characters in tag
+ entry: .github/workflows/check_translation_tag.py
+ language: script
+ types_or:
+ - ts
+
+ - repo: https://github.com/pre-commit/pre-commit-hooks.git
+ rev: v4.0.1
+ hooks:
+ - id: check-json
+ name: Check JSON files
+
+ - id: check-yaml
+ name: Check YAML files
+
+ - id: fix-byte-order-marker
+ name: Check file encoding (UTF-8 without BOM)
+ exclude: |
+ (?x)^(
+ src/base/unicodestrings.h
+ )$
+
+ - id: mixed-line-ending
+ name: Check line ending character (LF)
+ args: ["--fix=lf"]
+ exclude: |
+ (?x)^(
+ compile_commands.json |
+ src/webui/www/private/scripts/lib/.*
+ )$
+
+ - id: end-of-file-fixer
+ name: Check trailing newlines
+ exclude: |
+ (?x)^(
+ compile_commands.json |
+ configure |
+ src/webui/www/private/scripts/lib/.*
+ )$
+ exclude_types:
+ - svg
+ - ts
+
+ - id: trailing-whitespace
+ name: Check trailing whitespaces
+ exclude: |
+ (?x)^(
+ src/webui/www/private/scripts/lib/.*
+ )$
+ exclude_types:
+ - ts