github actions 와 python을 통해서 repository안의 readme를 push 할때마다 자동으로 업데이트 되도록 설계하는 방법
동작과정
- 소스코드를 깃허브에 push 하게 되면 github actions 에서 변경을 감지하고 python 소스코드를 동작하게 된다.
- 파이썬으로 repository 각종 디렉토리를 탐색하게되고 조건문에 해당되는 데이터들을 자료구조에 저장한다.
- 저장된 데이터를 통해 ReadMe를 새로 작성하게 되고 기존 repo에 덮어쓰기된다.
결과화면
코드 설명
전체적인 소스코드는 아래에 남겼으니 그 부분을 참고하시면 되겠습니다.
세부적으로 하나씩 리뷰하겠습니다.
gitactions setting 방법
아래 패키지로 생성해도 소스만 바꿔도 무방하다.
- 파이썬 버전을 다운하고
- python 스크립트를 실행한다.
- 실행된 파일을 git add 하고 commit하고 push
python 소스
내부의 README와 data-problems는 dummy 파일이니까 상관 없다.
문제 클래스
객체 지향 공부를 하다보니 파이썬을 객체지향적으로 접근하기엔 다소 어려움이 있었습니다.
객체 반복하려면 iter 재정의한다던지, next 구문 작성해야된다던지 등등 파이썬을 잘 모르는 저에게 접근하는 방법이 많이 어려웠습니다. 그래서 기존 자료구조에 append 하는 방식으로 구현했습니다.
문제 클래스는 week, day, filename, address 를 가지고 `void__str__()` 메소드에 맞은 양식으로 출력하게됩니다.
id 같은 경우는 인덱스 용도로 사용하려 했으나 for 문 안에서 index를 불러 올 수 있는 기능을 찾아 사용하지 않습니다.
디렉토리 탐색 메소드
전역변수 dic(dictionary)와 매개변수 problems(list)를 받고 있습니다
global dic 은 주간 문제 푼 횟수를 key : value 타입으로 저장하고 있습니다 ( ex. week1 : 3 ) 자바로 보면 Map<String, Integer>
매개변수로 받은 list는 OS가 .java 파일에 접근할 때마다 Problem 객체를 만들어 list 에 저장하게 됩니다.
os.walk(dir) 를 통해서 해당 주소의 디렉토리와 파일들에 접근하게 됩니다.
if ext == '.java':
print("%s %s" % (root, filename))
split_dir = root.split("/")
address = root.replace("../","")
address += "/"+filename
if len(split_dir) >= 4:
week = str(split_dir[2])
dic_key = dic.get(week)
if dic_key is None:
dic[week] = 1
else:
dic[week] = dic.get(week) + 1
day = str(split_dir[3])
filename = str(filename)
if week and day and filename:
problems.append(Problem(str(value),week,day,filename,address))
value += 1
조건문안에서 root 를 출력했을때 위 ../src/week/day 순으로 문자열이 나타나게되고
각각 split 을 통해서 배열 size 가 4인 문자열 split을 추려냈습니다.
`if len(split_dir) >= 4` 배열 size가 4 미만일 경우 indexOutOfError 가 발생 할 수 있기 때문에
디렉토리 규칙에 따른 src/ week / day 순으로 데이터 파일을 저장하도록 합시다
split 을 할 경우 해당 java 파일의 주소에서 문자열의 2번째와 3번째에는 week 와 day 의 대한 정보가 추출되게됩니다.
dictionary 자료구조에서(dic) key는 weekN 으로 저장하고 value는 없으면 1을 있으면 해당 value값을 가져와서 1을 추가하게 됩니다. 이는 주간 몇문제 풀었는지에 대한 정보가 담기게 됩니다
`if week and day and filename` 만약 week, day, filename 에 대한 정보가 있을 경우
list 자료구조인 (problems)에 Problem 객체를 생성해서 리스트에 append 하게 됩니다.
value 같은 경우는 id 값을 넣어주려 했으나 사용하진 않습니다
readMe 만드는 메소드
def make_info_header(dic):
info = f"| # | week | day |\n"
info += f"|---|---|---| \n"
for index in range(0,len(dic)):
info += f"| {index+1} | {dic[index][0]} | {dic[index][1]} | \n"
print(info)
return info
def make_info_data(problems):
info = f"### 총 푼 문제수 = {len(problems)} 🎉\n\n"
info += f"| # | week | day | problem |\n| ------------- | ------------- | ------------- | ------------- |\n"
for index in range( 0, len(problems)):
temp = f"| {index+1} {problems[index]}"
info += temp
info += """"""
return info
dictionary(dic)와 list(problems) 자료구조를 받아 반복문을 돌리면서 readMe의 문자열을 완성시킵니다.
make_info_header에는 주간별로 몇문제 풀었는지
make_info_data에는 총 푼 문제수와 문제 내용들이 저장되게 됩니다.
메인 메소드
if __name__ == "__main__":
problems = []
personal_dir = "../src/"
print_files_in_dir(personal_dir, "",problems)
projects = sorted(problems, key=attrgetter('week','day'),reverse=False)
sorted_dic = sorted(dic.items(),key= lambda item: item[0],reverse= False)
info = make_info_data(projects)
header = make_info_header(sorted_dic)
with open("../README.md", 'w', encoding='utf-8') as f:
f.write(title_project + "\n")
f.write(sub_project + "\n")
f.write(header + "\n")
f.write(info)
f.close()
problems의 초기화 그리고 탐색할 디렉토리의 초기값
메소드를 호출해서 정보들을 자료구조에 담습니다
객체를 정렬하려고 하다보니 비교에 있어서 모호한 부분이 있어 단계정렬을 위해 attrgetter 라이브러리를 활용하여 전체 문제에 대한 정보들을 week - day 순으로 정렬 했스빈다.
주간 푼 문제수 같은 경우도 마찬가지로 단계정렬은 아니고 sorted 메소드 규격에 맞게 람다식으로 정렬을 구현했습니다.
이후 해당 데이터들로 파일처리를 진행하고 github acitons 에서 commit 하게 됩니다.
마주친 에러
- 정렬문제
객체들을 정렬할 수 없으니 sorted 함수와 라이브러리를 활용
034 다양한 기준으로 정렬하려면? ― operator.itemgetter
operator.itemgetter는 주로 sorted와 같은 함수의 key 매개변수에 적용하여 다양한 기준으로 정렬할 수 있도록 하는 모듈이다. ## 문제 학생의 이름, …
wikidocs.net
- 파일시스템 탐색 문제
os.walk 사용방법
https://codechacha.com/ko/python-walk-files/
Python - os.walk()를 사용하여 디렉토리, 파일 탐색
os.listdir() 또는 os.walk()를 사용하면 어떤 경로의 모든 하위 디렉토리를 탐색할 수 있습니다. os.walk()는 기본적으로 top-down 방식으로 상위 폴더부터 출력하며 bottom-up 방향으로 출력하고 싶다면 topdo
codechacha.com
리팩토링 및 발전방향
- 다른 문제푸는 사이트와 연동하고 싶다.
파일명이나 디렉토리명으로 분류해서 백준, 프로그래머스, SWEA 등등 다양한 플랫폼에서 푼 문제들을 정렬해보기
- github organization 에서 팀원들과 함께하는 레포지토리 안에서 몇문제 풀었는지 확인하는 시스템 구현하기
추후 반영
- 클린코드 작성
변수도 난잡하게 사용하고 있고 파이썬에 대한 문법도 모르고 있어서 클린코드라고 말할 순 없을 것 같다.
남들이 보기에 쉽고 이용할 수 있는 코드를 짤 수있도록 추 후 리팩토링
주의사항
- 디렉토리 구조
해당 소스를 잘 보면 데이터 추출 과정에서 split을 통해서 각 디렉토리 명들이 String 배열에 담기게된다.
디렉토리 구조를 src/week/day/.java 순으로 만들지 않는다면 제대로 된 조회를 할 수 없게 된다.
따라서 파이썬 라이브러리들을 더 찾아봐서 주소디렉토리를 추출 할 수 있는 기능을 만들면 좋을 듯 하다.
소스코드
update-readme.py
import os
from operator import itemgetter, attrgetter
title_project = "# EveryDay - Practice"
sub_project = "### push 후 자동으로 README 수정 기능"
dic = {}
class Problem:
def __init__(self,id, week, day, filename, address):
self.id =id
self.week = week
self.day = day
self.filename = filename
self.address = address
def get_week(self):
return self.week
def get_day(self):
return self.day
def get_filename(self):
return self.filename
def __str__(self) -> str:
return " | " + self.week + " | " + self.day + " | [" + self.filename + "](" + self.address + ")" + "|\n"
def print_files_in_dir(root_dir, prefix ,problems):
value = 1
global dic
try:
for (root, dirs, files) in os.walk(root_dir):
for filename in files:
ext = os.path.splitext(filename)[-1]
if ext == '.java':
print("%s %s" % (root, filename))
split_dir = root.split("/")
address = root.replace("../","")
address += "/"+filename
if len(split_dir) >= 4:
week = str(split_dir[2])
dic_key = dic.get(week)
if dic_key is None:
dic[week] = 1
else:
dic[week] = dic.get(week) + 1
day = str(split_dir[3])
filename = str(filename)
if week and day and filename:
problems.append(Problem(str(value),week,day,filename,address))
value += 1
except PermissionError:
pass
def make_info_header(dic):
info = f"| # | week | day |\n"
info += f"|---|---|---| \n"
for index in range(0,len(dic)):
info += f"| {index+1} | {dic[index][0]} | {dic[index][1]} | \n"
print(info)
return info
def make_info_data(problems):
info = f"### 총 푼 문제수 = {len(problems)} 🎉\n\n"
info += f"| # | week | day | problem |\n| ------------- | ------------- | ------------- | ------------- |\n"
for index in range( 0, len(problems)):
temp = f"| {index+1} {problems[index]}"
info += temp
info += """"""
return info
if __name__ == "__main__":
problems = []
personal_dir = "../src/"
print_files_in_dir(personal_dir, "",problems)
projects = sorted(problems, key=attrgetter('week','day'),reverse=False)
sorted_dic = sorted(dic.items(),key= lambda item: item[0],reverse= False)
info = make_info_data(projects)
header = make_info_header(sorted_dic)
with open("../README.md", 'w', encoding='utf-8') as f:
f.write(title_project + "\n")
f.write(sub_project + "\n")
f.write(header + "\n")
f.write(info)
f.close()
update-readme.yml
name: Update readme
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.10
uses: actions/setup-python@v3
with:
python-version: "3.10"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
- name: Run update-readme.py
working-directory: script
run: |
python update-readme.py
- name: Commit changes
run: |
git config --global user.name 'userName'
git config --global user.email 'userId@gmail.com'
git add -A
git commit -am "auto-update README.md"
- name: Push changes
run: |
git push
참고
git repository
https://github.com/rnrudejr9/every_practice/tree/master
GitHub - rnrudejr9/every_practice: 매일매일 공부하는 습관
매일매일 공부하는 습관. Contribute to rnrudejr9/every_practice development by creating an account on GitHub.
github.com
'2023년 > ETC💁♂️' 카테고리의 다른 글
[ETC] 윈도우 화면에서 바로 gif 생성하는 툴 (0) | 2023.10.06 |
---|---|
[SQLD] 정리 요약 및 예상문제 정리본 (0) | 2023.08.24 |
[Eclipse] git repository 에서 clone 한 프로젝트를 이클립스에서 관리하기! (0) | 2023.07.14 |
[ETC] 노션(notion) 사용자 글꼴로 변경하여 사용하기! (배달의 민족 글씨체 적용) (0) | 2023.07.13 |
[git] github repository description 추가 또는 바꾸는 방법 (0) | 2023.05.25 |