# Проверка comments на модели пк. Комментарии и исходный текст в UTF-8. # Продолжает текущий вызов, список или выражение: """Проверка UTF-8 и русских пояснений к каждой строке исходного кода."""; порядок аргументов сохраняется. """Проверка UTF-8 и русских пояснений к каждой строке исходного кода.""" # Подключает модуль argparse для этой проверки или подготовки сборки. import argparse # Подключает модуль io для этой проверки или подготовки сборки. import io # Импортирует необходимые имена: from pathlib import Path. from pathlib import Path # Подключает модуль re для этой проверки или подготовки сборки. import re # Подключает модуль subprocess для этой проверки или подготовки сборки. import subprocess # Подключает модуль tokenize для этой проверки или подготовки сборки. import tokenize # Задаёт ROOT по выражению справа; значение используется последующими операциями. ROOT = Path(__file__).resolve().parents[2] # Задаёт RUSSIAN по выражению справа; значение используется последующими операциями. RUSSIAN = re.compile("[А-Яа-яЁё]") # Задаёт C_SUFFIXES по выражению справа; значение используется последующими операциями. C_SUFFIXES = {".c", ".h", ".cmd", ".inc"} # Задаёт TEXT_SUFFIXES по выражению справа; значение используется последующими операциями. TEXT_SUFFIXES = C_SUFFIXES | {".asm", ".py", ".md", ".bat", ".old", ".prefs", ".json", ".pjt", ".lkf"} # Задаёт TEXT_NAMES по выражению справа; значение используется последующими операциями. TEXT_NAMES = {".project", ".cproject", ".ccsproject", ".gitignore", ".editorconfig"} # Объявляет функцию c_code_lines и её входные параметры. def c_code_lines(text): # Задаёт block по выражению справа; значение используется последующими операциями. block = False # Перебирает line in text.splitlines() в указанном порядке. for line in text.splitlines(): # Задаёт out по выражению справа; значение используется последующими операциями. out = [] # Задаёт i по выражению справа; значение используется последующими операциями. i = 0 # Задаёт quote по выражению справа; значение используется последующими операциями. quote = None # Повторяет блок при выполнении i < len(line). while i < len(line): # Проверяет условие if block перед выполнением следующего блока. if block: # Задаёт end по выражению справа; значение используется последующими операциями. end = line.find("*/", i) # Проверяет условие if end < 0 перед выполнением следующего блока. if end < 0: # Продолжает текущий вызов, список или выражение: break; порядок аргументов сохраняется. break # Задаёт block по выражению справа; значение используется последующими операциями. block = False # Задаёт i по выражению справа; значение используется последующими операциями. i = end + 2 # Продолжает текущий вызов, список или выражение: out.append(" "); порядок аргументов сохраняется. out.append(" ") # Продолжает текущий вызов, список или выражение: continue; порядок аргументов сохраняется. continue # Задаёт c по выражению справа; значение используется последующими операциями. c = line[i] # Проверяет условие if quote перед выполнением следующего блока. if quote: # Продолжает текущий вызов, список или выражение: out.append(c); порядок аргументов сохраняется. out.append(c) # Проверяет условие if c == "\\" and i + 1 < len(line) перед выполнением следующего блока. if c == "\\" and i + 1 < len(line): # Задаёт i + по выражению справа; значение используется последующими операциями. i += 1 # Продолжает текущий вызов, список или выражение: out.append(line[i]); порядок аргументов сохраняется. out.append(line[i]) # Проверяет условие elif c == quote перед выполнением следующего блока. elif c == quote: # Задаёт quote по выражению справа; значение используется последующими операциями. quote = None # Задаёт i + по выражению справа; значение используется последующими операциями. i += 1 # Продолжает текущий вызов, список или выражение: continue; порядок аргументов сохраняется. continue # Проверяет условие if line.startswith("//", i) перед выполнением следующего блока. if line.startswith("//", i): # Продолжает текущий вызов, список или выражение: break; порядок аргументов сохраняется. break # Проверяет условие if line.startswith("/*", i) перед выполнением следующего блока. if line.startswith("/*", i): # Задаёт block по выражению справа; значение используется последующими операциями. block = True # Задаёт i + по выражению справа; значение используется последующими операциями. i += 2 # Продолжает текущий вызов, список или выражение: out.append(" "); порядок аргументов сохраняется. out.append(" ") # Продолжает текущий вызов, список или выражение: continue; порядок аргументов сохраняется. continue # Проверяет условие if c in "\"'" перед выполнением следующего блока. if c in "\"'": # Задаёт quote по выражению справа; значение используется последующими операциями. quote = c # Продолжает текущий вызов, список или выражение: out.append(c); порядок аргументов сохраняется. out.append(c) # Задаёт i + по выражению справа; значение используется последующими операциями. i += 1 # Продолжает текущий вызов, список или выражение: yield "".join(out).strip(); порядок аргументов сохраняется. yield "".join(out).strip() # Объявляет функцию python_code_lines и её входные параметры. def python_code_lines(text): # Задаёт active по выражению справа; значение используется последующими операциями. active = set() # Задаёт strings по выражению справа; значение используется последующими операциями. strings = set() # Перебирает token in tokenize.generate_tokens(io.StringIO(text).readline) в указанном порядке. for token in tokenize.generate_tokens(io.StringIO(text).readline): # Проверяет условие if token.type == tokenize.STRING and token.end[0] > token.start[0] перед выполнением следующего блока. if token.type == tokenize.STRING and token.end[0] > token.start[0]: # Продолжает текущий вызов, список или выражение: strings.update(range(token.start[0] + 1, token.end[0] + 1)); порядок аргументов сохраняется. strings.update(range(token.start[0] + 1, token.end[0] + 1)) # Проверяет условие if token.type not in {tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE, tokenize.INDENT, tokenize.DEDENT, tokenize.ENDMARKER} перед выполнением следующего блока. if token.type not in {tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE, tokenize.INDENT, tokenize.DEDENT, tokenize.ENDMARKER}: # Продолжает текущий вызов, список или выражение: active.add(token.start[0]); порядок аргументов сохраняется. active.add(token.start[0]) # Задаёт lines по выражению справа; значение используется последующими операциями. lines = text.splitlines() # Возвращает [line.strip() if n in active and n not in strings else "" for n, line in enumerate(lines, 1)] вызывающему коду и завершает функцию. return [line.strip() if n in active and n not in strings else "" for n, line in enumerate(lines, 1)] # Объявляет функцию audit и её входные параметры. def audit(path, text): # Задаёт lines по выражению справа; значение используется последующими операциями. lines = text.splitlines() # Проверяет условие if path.suffix in C_SUFFIXES перед выполнением следующего блока. if path.suffix in C_SUFFIXES: # Задаёт code по выражению справа; значение используется последующими операциями. code = list(c_code_lines(text)) # Проверяет условие elif path.suffix == ".asm" перед выполнением следующего блока. elif path.suffix == ".asm": # Задаёт code по выражению справа; значение используется последующими операциями. code = [line.split(";")[0].strip() for line in lines] # Проверяет условие elif path.suffix == ".py" перед выполнением следующего блока. elif path.suffix == ".py": # Задаёт code по выражению справа; значение используется последующими операциями. code = python_code_lines(text) # Обрабатывает альтернативу предыдущего условия. else: # Возвращает 0, [] вызывающему коду и завершает функцию. return 0, [] # Задаёт missing по выражению справа; значение используется последующими операциями. missing = [] # Задаёт count по выражению справа; значение используется последующими операциями. count = 0 # Перебирает i, value in enumerate(code) в указанном порядке. for i, value in enumerate(code): # Проверяет условие if not value перед выполнением следующего блока. if not value: # Продолжает текущий вызов, список или выражение: continue; порядок аргументов сохраняется. continue # Задаёт count + по выражению справа; значение используется последующими операциями. count += 1 # Эвристика ищет кириллицу в самой строке или перед ней; русский строковый литерал тоже может дать совпадение. Это проверка наличия текста рядом, а не качества и достоверности комментария. if RUSSIAN.search(lines[i]) or (i and RUSSIAN.search(lines[i-1])): # Продолжает текущий вызов, список или выражение: continue; порядок аргументов сохраняется. continue # Явное продолжение Python-выражения относится к пояснённой начальной строке. # Проверяет условие if path.suffix == ".py" and i and lines[i-1].rstrip().endswith("\\") перед выполнением следующего блока. if path.suffix == ".py" and i and lines[i-1].rstrip().endswith("\\"): # Продолжает текущий вызов, список или выражение: continue; порядок аргументов сохраняется. continue # Продолжает текущий вызов, список или выражение: missing.append(i+1); порядок аргументов сохраняется. missing.append(i+1) # Возвращает count, missing вызывающему коду и завершает функцию. return count, missing # Объявляет функцию main и её входные параметры. def main(): # Задаёт parser по выражению справа; значение используется последующими операциями. parser = argparse.ArgumentParser(description=__doc__) # Задаёт parser.add_argument("--report", type по выражению справа; значение используется последующими операциями. parser.add_argument("--report", type=Path, help="Сохранить таблицу покрытия в Markdown") # Задаёт args по выражению справа; значение используется последующими операциями. args = parser.parse_args() # Задаёт names по выражению справа; значение используется последующими операциями. names = subprocess.check_output(["git", "ls-files", "-z"], cwd=ROOT).decode("utf-8").split("\0") # Задаёт names по выражению справа; значение используется последующими операциями. names = sorted(set(filter(None, names)) | {"tests/comments/run_test.py", ".editorconfig"}) # Задаёт rows по выражению справа; значение используется последующими операциями. rows = [] # Задаёт errors по выражению справа; значение используется последующими операциями. errors = [] # Задаёт checked по выражению справа; значение используется последующими операциями. checked = 0 # Перебирает name in names в указанном порядке. for name in names: # Задаёт path по выражению справа; значение используется последующими операциями. path = ROOT / name # Проверяет условие if path.suffix.lower() not in TEXT_SUFFIXES and path.name not in TEXT_NAMES перед выполнением следующего блока. if path.suffix.lower() not in TEXT_SUFFIXES and path.name not in TEXT_NAMES: # Продолжает текущий вызов, список или выражение: continue; порядок аргументов сохраняется. continue # Начинает блок обработки возможной ошибки. try: # Читает исходный файл или образ для последующей проверки и разбора. text = path.read_text(encoding="utf-8") # Перехватывает указанный тип ошибки и выполняет обработку. except UnicodeError as error: # Продолжает текущий вызов, список или выражение: errors.append(f"{name}: неверная кодировка UTF-8: {error}"); порядок аргументов сохраняется. errors.append(f"{name}: неверная кодировка UTF-8: {error}") # Продолжает текущий вызов, список или выражение: continue; порядок аргументов сохраняется. continue # Задаёт checked + по выражению справа; значение используется последующими операциями. checked += 1 # Задаёт count, missing по выражению справа; значение используется последующими операциями. count, missing = audit(path, text) # Проверяет условие if missing перед выполнением следующего блока. if missing: # Продолжает текущий вызов, список или выражение: errors.append(f"{name}: нет русского пояснения, строки {missing[:20]}"); порядок аргументов сохраняется. errors.append(f"{name}: нет русского пояснения, строки {missing[:20]}") # Проверяет условие if path.suffix in C_SUFFIXES or path.suffix in {".asm", ".py"} перед выполнением следующего блока. if path.suffix in C_SUFFIXES or path.suffix in {".asm", ".py"}: # Продолжает текущий вызов, список или выражение: rows.append((name, count, count-len(missing))); порядок аргументов сохраняется. rows.append((name, count, count-len(missing))) # Проверяет условие if args.report перед выполнением следующего блока. if args.report: # Задаёт report по выражению справа; значение используется последующими операциями. report = [ # Продолжает текущий вызов, список или выражение: "# Проверка комментариев и кодировки",; порядок аргументов сохраняется. "# Проверка комментариев и кодировки", # Продолжает текущий вызов, список или выражение: "",; порядок аргументов сохраняется. "", # Продолжает текущий вызов, список или выражение: "Проверка относится к редактируемым исходникам C/H, ASM, CMD, тестовым INC и Python.",; порядок аргументов сохраняется. "Проверка относится к редактируемым исходникам C/H, ASM, CMD, тестовым INC и Python.", # Описывает фактический критерий покрытия, не выдавая наличие кириллицы за оценку качества пояснения. "Для каждой непустой строки кода проверяется русский текст на ней или непосредственно перед ней.", # Уточняет исключения счётчика, чтобы комментарии и многострочные данные не завышали объём кода. "Пустые строки, текст комментариев и содержимое многострочных строк данных не считаются кодом.", # Предупреждает о главном ограничении: шаблонный текст или русский строковый литерал тоже проходит эвристику. "Качество и достоверность пояснений этой проверкой не устанавливаются; совпадение возможно и в строковом литерале.", # Отделяет исходники репозитория от подключаемого и генерируемого кода, отсутствующего в Git-индексе. "Охват: исходники из Git-индекса. Сгенерированные файлы и подключённые соседние библиотеки проверяются отдельно.", # Продолжает текущий вызов, список или выражение: "Исходные уведомления TI сохранены. JSON-паспорта, индексы IDE, таблицы XLS и двоичные",; порядок аргументов сохраняется. "Исходные уведомления TI сохранены. JSON-паспорта, индексы IDE, таблицы XLS и двоичные", # Продолжает текущий вызов, список или выражение: "результаты сборки не допускают исходных комментариев; их назначение описано в документации.",; порядок аргументов сохраняется. "результаты сборки не допускают исходных комментариев; их назначение описано в документации.", # Продолжает текущий вызов, список или выражение: "",; порядок аргументов сохраняется. "", # Продолжает текущий вызов, список или выражение: f"UTF-8 проверен в {checked} текстовых файлах. Строк кода: {sum(r[1] for r in rows)}.",; порядок аргументов сохраняется. f"UTF-8 проверен в {checked} текстовых файлах. Строк кода: {sum(r[1] for r in rows)}.", # Продолжает текущий вызов, список или выражение: "",; порядок аргументов сохраняется. "", # Подписывает колонку строго по измеряемому признаку, не утверждая содержательность найденного текста. "| Файл | Строк кода | С русским текстом рядом |", # Продолжает текущий вызов, список или выражение: "| --- | ---: | ---: |",; порядок аргументов сохраняется. "| --- | ---: | ---: |", # Продолжает текущий вызов, список или выражение: ]; порядок аргументов сохраняется. ] # Задаёт report + по выражению справа; значение используется последующими операциями. report += [f"| {name} | {count} | {covered} |" for name,count,covered in rows] # Сохраняет подготовленный файл в указанном формате для следующего шага. args.report.write_text("\n".join(report)+"\n", encoding="utf-8") # Проверяет условие if errors перед выполнением следующего блока. if errors: # Останавливает текущую операцию с исключением SystemExit("\n".join(errors)). raise SystemExit("\n".join(errors)) # Продолжает текущий вызов, список или выражение: print(f"PASS UTF-8: {checked} files; Russian comments: {len(rows)} source files, {sum(r[1] for r in rows)} code lines"); порядок аргументов сохраняется. print(f"PASS UTF-8: {checked} files; Russian comments: {len(rows)} source files, {sum(r[1] for r in rows)} code lines") # Проверяет условие if __name__ == "__main__" перед выполнением следующего блока. if __name__ == "__main__": # Продолжает текущий вызов, список или выражение: main(); порядок аргументов сохраняется. main()