今まで書いた記事を1つのスクリプトにまとめる。
下記の記事で実践したことをまとめる。
スクリプト
main.py
import os
import shutil
import service.config as config
import service.anti_multiple_variable_declaration as AMVD
import service.anti_unused_variable as AUV
import service.anti_nothing_elseblock as ANE
import service.anti_blank_default_case as ABDC
import service.anti_nothing_default_case as ANDC
def main():
target_dir, target_files = config.load_cfg("cfg.xml")
# バックアップファイルの作成.
backup_dir = target_dir + "_backup"
if os.path.exists(backup_dir):
shutil.rmtree(backup_dir)
shutil.copytree(target_dir, backup_dir)
for target_file in target_files:
target_path = os.path.join(target_dir, target_file)
AMVD.multiple_to_variable_declaration(target_path)
AUV.delete_unused_variable(target_path)
ANE.add_elseblock(target_path)
ABDC.add_comment_default_case(target_path)
ANDC.add_default_case(target_path)
if __name__ == "__main__":
main()
config.py
import xml.etree.ElementTree as ET
def load_cfg(filename:str)->tuple[str, list]:
# XMLファイルを読み込む
tree = ET.parse(filename)
root = tree.getroot()
target_dir = root.find('target_dir').text
target_file_items = root.find('target_file')
target_files = [item.text for item in target_file_items]
return target_dir, target_files
anti_unused_variable.py
import regex
def delete_unused_variable(filepath:str):
pattern = r"(?:(?:auto|register|static|extern)\s+)?(?:(?:const|volatile|restrict|signed|unsigned|short|long)\s+)*\w+\s+\w+\s*\([^()]*\)\s*(\{([^{}]|(?1))*\})"
pattern_val = r"[\t ]*(?:(?:auto|register|static|extern)\s+)?(?:(?:const|volatile|restrict|signed|unsigned|short|long)\s+)*\w+[\s*]+\w+\s*(?:=[^;]+)?;[^\n]*\n"
with open(filepath, "r", encoding="utf-8", errors="ignore") as file_obj:
codetext = file_obj.read()
functions = regex.finditer(pattern, codetext, regex.DOTALL)
for func in functions:
functext = func.group()
functext_edit = functext
values = regex.finditer(pattern_val, functext, regex.DOTALL)
for val in values:
valtext = val.group()
if "=" in valtext:
# 代入演算子がある場合は=の左にある文字列が変数名.
valname = regex.search(r"\w+(?=\s*=)", valtext).group()
else:
# 代入演算子がない場合は;の左にある文字列が変数名.
valname = regex.search(r"\w+(?=\s*;)", valtext).group()
valmatchs = regex.finditer(rf"\b{valname}\b", functext, regex.DOTALL)
usedcount = 0
for vallll in valmatchs:
usedcount += 1
if usedcount == 1:
# 一度しか記述されていない場合は未使用変数と判断して削除.
functext_edit = functext_edit.replace(valtext, "")
pass
codetext = codetext.replace(functext, functext_edit)
with open(filepath, "w", encoding="utf-8", errors="ignore") as file_obj:
file_obj.write(codetext)
anti_nothing_elseblock.py
import regex
def __elseblock_repl(match):
elseblock = "\nelse\n{\n\t/* 処理なし */\n}"
matchstr = match.group()
# if文のインデントを取得する
indent = regex.search(r"[\t ]*(?=\bif\b|\belse\b\s*\bif\b)", matchstr, regex.DOTALL).group()
return matchstr + elseblock.replace("\n", "\n" + indent)
def add_elseblock(filepath:str):
with open(filepath, "r", encoding="utf-8", errors="ignore") as file_obj:
codetext = file_obj.read()
edited_code = regex.sub(r"(?<=\n)\s*(?:\bif\b|\belse\b\s*\bif\b)\s*\([^()]*\)\s*(\{(?:[^{}]|(?1))*\})(?!\s*\belse\b\s*\bif\b|\s*\belse\b)", __elseblock_repl, codetext, regex.DOTALL)
with open(filepath, "w", encoding="utf-8", errors="ignore") as file_obj:
file_obj.write(edited_code)
anti_nothing_default_case.py
import regex
def __repl_add_default(match):
matchstr = match.group()
# 既にdefaultがある場合は、そのままの文字をreturn
if not regex.search("\s*default\s*:", matchstr, regex.DOTALL):
index = matchstr.rfind("}")
indent = regex.search(r"[\t ]*(?=\bswitch\b)", matchstr, regex.DOTALL).group()
matchstr = matchstr[:index] + "default: /* 処理なし */ break;\n"+ indent + matchstr[index:]
return matchstr
def add_default_case(filepath:str):
with open(filepath, "r", encoding="utf-8") as file_obj:
codetext = file_obj.read()
edited_code = regex.sub(r"(?<=\n)\s*switch\s*\([^()]*\)\s*(\{(?:[^{}]|(?1))*\})", __repl_add_default, codetext, flags=regex.DOTALL)
with open(filepath, "w", encoding="utf-8", errors="ignore") as file_obj:
file_obj.write(edited_code)
anti_multiple_variable_declaration.py
import regex
def __repldef(match:regex.Match)->str:
matchtext:str = match.group()
pattern = r"[\t ]*(?:(?:auto|register|static|extern)\s+)?(?:(?:const|volatile|restrict|signed|unsigned|short|long)\s+)*\w+\*?"
datatype = regex.search(pattern, matchtext, regex.DOTALL).group()
ret = matchtext.replace(",", f";\n{datatype}")
return ret
def multiple_to_variable_declaration(file_path:str):
pattern = r"(?<=[\n;])[\t ]*(?:(?:auto|register|static|extern)\s+)?(?:(?:const|volatile|restrict|signed|unsigned|short|long)\s+)*\w+[\s*]+\w+(?:\s*=[^,;]+)?\s*(,[\s*]*\w+(?:\s*=[^,;]+)?)+;"
with open(file_path, "r", encoding="utf-8", errors="ignore") as file_obj:
codetext = file_obj.read()
edited_code = regex.sub(pattern, __repldef, codetext, flags=regex.DOTALL)
with open(file_path, "w", encoding="utf-8", errors="ignore") as file_obj:
file_obj.write(edited_code)
anti_blank_default_case.py
import regex
def add_comment_default_case(filepath:str):
with open(filepath, "r", encoding="utf-8") as file_obj:
codetext = file_obj.read()
edited_code = regex.sub(r"(?<=default\s*:\s*);", "/* 処理なし */", codetext, flags=regex.DOTALL)
with open(filepath, "w", encoding="utf-8", errors="ignore") as file_obj:
file_obj.write(edited_code)
configファイル
こんな感じ
cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<config_data>
<target_dir>C:\Users\XXXX\code</target_dir>
<target_file>
<item>main.c</item>
<item>test.c</item>
<item>func.c</item>
</target_file>
</config_data>
ファイル構成
main.py
cfg.xml
service
┣ anti_blank_default_case.py
┣ anti_multiple_variable_declaration.py
┣ anti_nothing_default_case.py
┣ anti_nothing_elseblock.py
┣ anti_unused_variable.py
┣ config.py
以上