본문 바로가기
작업실/도구

AutoReplace.py: 표현식 기반 탐색 후 변환 도구

by Corin Choi 2021. 9. 13.

표현식 기반 탐색 후 변환(Search & Replace) 도구

변수만 다르고 형태는 같은 코드가 여러 개 있을 때 사용하면 손을 덜 귀찮게 해줍니다.

AutoReplace.py
0.00MB


어떤 일을 하는 도구인지 예시로 설명을 시작하겠습니다.

 

리스트를 세 개 만들었다고 하면,

new_listA = ds_list_create();
new_listB = ds_list_create();
new_listC = ds_list_create();

이 리스트를 지우기 위해서

ds_list_destroy(new_listA);
ds_list_destroy(new_listB);
ds_list_destroy(new_listC);

위와 같이 세 줄의 코드가 추가되었습니다. 여기까지는 쉽습니다.

이제 이 리스트를 21개 더 만들었다고 생각해봅시다. 그러면 리스트 삭제 코드를 21번 더 붙여 넣기를 해야 합니다. 생각만 해도 귀찮은 작업입니다. 하지만 이 도구를 사용한다면 파일 위치를 복사하는 게 더 귀찮은 일이 될 겁니다.


도구를 실행하면, 먼저 읽어낼 파일 경로와 내보낼 파일 경로를 입력해야 합니다.

이후에는 두 개의 표현식을 필요로 합니다.

첫 번째는 변경할 대상을 찾는 표현식 (탐색 표현식)
두 번째는 변환하여 나타내질 표현식입니다. (변환 표현식)

앞서 리스트 생성 코드와 리스트 삭제 코드를 보면,

new_listA = ds_list_create();
ds_list_destroy(new_listA);

수식 부분은 바뀌었지만 변수는 같은 것을 알 수 있습니다.

이에 맞춰 식을 쓴다면 다음처럼 나타낼 수 있습니다.

변수1 = ds_list_create();
ds_list_destroy(변수1);

 

이 도구에서는 표현식 내의 변수를 <?> 형태로 정의합니다. 즉, 첫 번째 변수(변수1)은 <0> 으로 나타내야 합니다. 이에 맞춰 다시 표현 식을 고치면, 다음과 같이 나타낼 수 있습니다.

<0> = ds_list_create();
ds_list_destroy(<0>);

 

그러면 리스트 생성 코드만 작성해도 이 도구를 이용하여 바로 리스트 삭제 코드를 만들어 낼 수 있습니다.

//input.txt
aaa = ds_list_create();
bbb = ds_list_create();
//expression
<0> = ds_list_create();
ds_list_destroy(<0>);
//ouput.txt
ds_list_destroy(aaa);
ds_list_destroy(bbb);

 


주석을 이용한다면 더 많은 것도 할 수 있습니다.

다시 한 번 리스트 세 개를 만들었습니다.

//input
new_listA = ds_list_create(); // 1 
new_listB = ds_list_create(); // 2 
new_listC = ds_list_create(); // 3
//output
ds_list_add(new_listA, 1);
ds_list_add(new_listB, 2);
ds_list_add(new_listC, 3);

위의 결과가 나오려면 표현식을 어떻게 써야 할까요?

//expression
변수1 = ds_list_create(); // 변수2 
ds_list_add(변수1, 변수2);
//expression
<0> = ds_list_create(); // <1> 
ds_list_add(<0>, <1>);

조금만 고민한다면 이렇게 일반화된 표현식을 생각할 수 있습니다.


사실 이 도구는 lerp 때문에 화가 나서 만들게 되었습니다.

//fileA.txt
a_value = 0;
a_target = 10;
a_velocity = 10;

a_value += (a_target - a_value)/a_velocity;
//fileB.txt
a_value = 0;
a_target = 10;
a_velocity = 10;


a_value = lerp(a_value, a_target, 1/a_velocity);

두 코드는 a_value가 부드럽게 변경될 수 있도록 하는 코드입니다.

그러나 fileA처럼 쓰기보다는 fileB처럼 쓰면 더 깔끔하고 보기 편하다는 장점이 있습니다. 무엇보다 속력 값이 0 ~ 1로 제한되어 있어 fileA처럼 속력 값이 음수이거나 소수점 아래일 때 값이 튕겨나가는 오류는 발생하지 않습니다.

  fileA의 코드를 fileB처럼 바꾸려면 일단 a_value 를 a_target 의 왼쪽으로 옮기고 - 를 지웁니다. 그리고 그 사이에 쉼표를 넣고, a_target 오른쪽에도 쉼표를 넣습니다. 이제 닫는 소괄호를 a_velocity 와 ; 사이로 옮기고, += 를 = 으로 바꿉니다. 그리고 이걸 파일 내의 모든 같은 형태의 코드에 다시 반복해야 합니다.

생각만 해도 화가 나지 않습니까?

그렇다면 제 노가다를 대신 뛰어주는 멋진 도구를 사용해봅시다.

fileA의 수식을 fileB처럼 바꾸려면 어떻게 일반화를 해야 할까요?

//expression
<0> += (<1> - <0>)/<2>;
<0> = lerp(<0>, <1>, 1/<2>);

짜잔, 위와 같은 표현식으로 간단하게 일반화 할 수 있습니다. 복사 붙여 넣기를 하느라 키보드와 마우스를 혹사 시키지 않아도 된다구요.


안타깝게도 버그 테스트조차 하지 않았습니다. 제가 쓰는 코드에는 그렇게 멋진 기능이 필요하진 않았으니까요.

몇 가지 주의 사항이 있습니다.

1. 표현식과 완전히 동일한 항목만 탐색 (띄어쓰기가 다르다면 탐색 되지 않습니다.)
2. 변환할 때 사용된 변수의 개수 <= 탐색할 때 사용된 변수의 개수
3. 내용이 바뀐 줄에 대해 들여쓰기 사라짐 (탭 키 누르는 것이 코드 고치는 것 보다 쉽잖아요.)
4. Python이 있어야 함.


코드 전문 - AutoReplace.py

from io import FileIO

#import sys
#import os
#import time

import re

#import codecs
from typing import Pattern

def string2re(_string) -> str:
	_string = _string \
	.replace("[", "[[]").replace("]", "[]]").replace("[[[]]","[[]") \
	.replace("(", "[(]").replace(")", "[)]") \
	.replace("{", "[{]").replace("}", "[}]") \
	.replace("|", "[|]").replace("^", "[^]").replace("?", "[?]").replace("!", "[!]").replace("$", "[$]") \
	.replace("+", "[+]").replace("-", "[-]").replace("*", "[*]") \
	.replace(".", "[.]").replace("\\", r"[\]") \
	.replace(" ", "")
	
	return _string


def run() -> None:
	file_directory_input: str = input("Input File Directory: \n")
	file_directory_output: str = input("Output File Directory: \n")

	file_input: FileIO = open(file_directory_input, "r", encoding = "UTF8")
	file_output: FileIO = open(file_directory_output, "w", encoding = "UTF8")

	file_content: list[str] = file_input.read().splitlines()

	file_input.close()

	find_index = None
	find_value = list()

	expression_find_str: str = string2re(input("Input Expression to Find: \n"))
	expression_find_re: Pattern = re.compile("<[0-9]?>")
	expression_find_str_: str = expression_find_re.sub("<?>", expression_find_str).replace("<?>", r"(.+)")
	expression_find_re_: Pattern = re.compile(expression_find_str_)
	print("Find with '{}'".format(expression_find_str_))

	find_index = expression_find_re.findall(expression_find_str)
	for _index in range(0, len(find_index)):
		find_index[_index] = int(find_index[_index].replace("<", "").replace(">",""))
	
	for _index in find_index:
		find_value.append("")
	
	expression_replace_str: str = input("Input Expression to Replace: \n")

	for _index in range(0, len(file_content)):
		_content = file_content[_index].replace("\t","").replace("\n","").rstrip().replace(" ","")
		_search = expression_find_re_.search(_content)

		if _search:
			print(_search.group(0), end = " => ")
			for __index in range(0, len(find_index)):
				find_value[find_index[__index]] = _search.group(__index+1)
			file_content[_index] = expression_replace_str
			for __index in find_index:
				file_content[_index] = file_content[_index].replace("<{}>".format(__index), find_value[__index])
			print(file_content[_index])
	
	for _content in file_content:
		file_output.write(_content+"\n")
	file_output.close()
	
	### Expressions
	### <0> = lerp(<0>, <1>, <2>);
	### <0> += (<1> - <0>)/<2>;

run()

댓글