From 0a6770a4efe640c223b8426cf2a84575dfbdc909 Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Wed, 14 Oct 2020 10:21:31 +0530 Subject: [PATCH] chore: GitHub actions for translation syntax validation and docs link (#23627) * chore: Add GA for translation syntax validation * chore: Documentation link checker Co-authored-by: Gavin D'souza * fix: URL Co-authored-by: Nabin Hait Co-authored-by: Gavin D'souza Co-authored-by: Nabin Hait --- .github/helper/documentation.py | 48 +++++++++++++++++++ .github/helper/translation.py | 60 ++++++++++++++++++++++++ .github/workflows/docs-checker.yml | 24 ++++++++++ .github/workflows/translation_linter.yml | 22 +++++++++ 4 files changed, 154 insertions(+) create mode 100644 .github/helper/documentation.py create mode 100644 .github/helper/translation.py create mode 100644 .github/workflows/docs-checker.yml create mode 100644 .github/workflows/translation_linter.yml diff --git a/.github/helper/documentation.py b/.github/helper/documentation.py new file mode 100644 index 0000000000..b603ed5e53 --- /dev/null +++ b/.github/helper/documentation.py @@ -0,0 +1,48 @@ +import sys +import requests +from urllib.parse import urlparse + + +docs_repos = [ + "frappe_docs", + "erpnext_documentation", + "erpnext_com", + "frappe_io", +] + + +def uri_validator(x): + result = urlparse(x) + return all([result.scheme, result.netloc, result.path]) + +def docs_link_exists(body): + for line in body.splitlines(): + for word in line.split(): + if word.startswith('http') and uri_validator(word): + parsed_url = urlparse(word) + if parsed_url.netloc == "github.com": + _, org, repo, _type, ref = parsed_url.path.split('/') + if org == "frappe" and repo in docs_repos: + return True + + +if __name__ == "__main__": + pr = sys.argv[1] + response = requests.get("https://api.github.com/repos/frappe/erpnext/pulls/{}".format(pr)) + + if response.ok: + payload = response.json() + title = payload.get("title", "").lower() + head_sha = payload.get("head", {}).get("sha") + body = payload.get("body", "").lower() + + if title.startswith("feat") and head_sha and "no-docs" not in body: + if docs_link_exists(body): + print("Documentation Link Found. You're Awesome! 🎉") + + else: + print("Documentation Link Not Found! ⚠️") + sys.exit(1) + + else: + print("Skipping documentation checks... 🏃") diff --git a/.github/helper/translation.py b/.github/helper/translation.py new file mode 100644 index 0000000000..340f4f8772 --- /dev/null +++ b/.github/helper/translation.py @@ -0,0 +1,60 @@ +import re +import sys + +errors_encounter = 0 +pattern = re.compile(r"_\(([\"']{,3})(?P((?!\1).)*)\1(\s*,\s*context\s*=\s*([\"'])(?P((?!\5).)*)\5)*(\s*,\s*(.)*?\s*(,\s*([\"'])(?P((?!\11).)*)\11)*)*\)") +words_pattern = re.compile(r"_{1,2}\([\"'`]{1,3}.*?[a-zA-Z]") +start_pattern = re.compile(r"_{1,2}\([f\"'`]{1,3}") +f_string_pattern = re.compile(r"_\(f[\"']") +starts_with_f_pattern = re.compile(r"_\(f") + +# skip first argument +files = sys.argv[1:] +files_to_scan = [_file for _file in files if _file.endswith(('.py', '.js'))] + +for _file in files_to_scan: + with open(_file, 'r') as f: + print(f'Checking: {_file}') + file_lines = f.readlines() + for line_number, line in enumerate(file_lines, 1): + if 'frappe-lint: disable-translate' in line: + continue + + start_matches = start_pattern.search(line) + if start_matches: + starts_with_f = starts_with_f_pattern.search(line) + + if starts_with_f: + has_f_string = f_string_pattern.search(line) + if has_f_string: + errors_encounter += 1 + print(f'\nF-strings are not supported for translations at line number {line_number + 1}\n{line.strip()[:100]}') + continue + else: + continue + + match = pattern.search(line) + error_found = False + + if not match and line.endswith(',\n'): + # concat remaining text to validate multiline pattern + line = "".join(file_lines[line_number - 1:]) + line = line[start_matches.start() + 1:] + match = pattern.match(line) + + if not match: + error_found = True + print(f'\nTranslation syntax error at line number {line_number + 1}\n{line.strip()[:100]}') + + if not error_found and not words_pattern.search(line): + error_found = True + print(f'\nTranslation is useless because it has no words at line number {line_number + 1}\n{line.strip()[:100]}') + + if error_found: + errors_encounter += 1 + +if errors_encounter > 0: + print('\nVisit "https://frappeframework.com/docs/user/en/translations" to learn about valid translation strings.') + sys.exit(1) +else: + print('\nGood To Go!') diff --git a/.github/workflows/docs-checker.yml b/.github/workflows/docs-checker.yml new file mode 100644 index 0000000000..cdf676dd67 --- /dev/null +++ b/.github/workflows/docs-checker.yml @@ -0,0 +1,24 @@ +name: 'Documentation Required' +on: + pull_request: + types: [ opened, synchronize, reopened, edited ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: 'Setup Environment' + uses: actions/setup-python@v2 + with: + python-version: 3.6 + + - name: 'Clone repo' + uses: actions/checkout@v2 + + - name: Validate Docs + env: + PR_NUMBER: ${{ github.event.number }} + run: | + pip install requests --quiet + python $GITHUB_WORKSPACE/.github/helper/documentation.py $PR_NUMBER diff --git a/.github/workflows/translation_linter.yml b/.github/workflows/translation_linter.yml new file mode 100644 index 0000000000..4becaebd6b --- /dev/null +++ b/.github/workflows/translation_linter.yml @@ -0,0 +1,22 @@ +name: Frappe Linter +on: + pull_request: + branches: + - develop + - version-12-hotfix + - version-11-hotfix +jobs: + check_translation: + name: Translation Syntax Check + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@v2 + - name: Setup python3 + uses: actions/setup-python@v1 + with: + python-version: 3.6 + - name: Validating Translation Syntax + run: | + git fetch origin $GITHUB_BASE_REF:$GITHUB_BASE_REF -q + files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF) + python $GITHUB_WORKSPACE/.github/helper/translation.py $files