링크 유효성 검사
스크립트
- check_link.py
import os
import re
import urllib.parse
# 파일이 존재하는지 확인하는 함수
def file_exists(source_dir, file_path):
file_path = urllib.parse.unquote(file_path) # URL 디코딩
file_path = file_path.replace('/', os.sep).replace('\\', os.sep)
return os.path.exists(os.path.join(source_dir, file_path))
# 마크다운 파일에서 링크를 추출하는 함수
def extract_links(content):
# 코드 블럭 패턴
code_block_pattern = re.compile(r'(```[\s\S]+?```|`[^`]+`)')
# 코드 블럭 안의 내용을 제거
content_without_code_blocks = code_block_pattern.sub('', content)
# 이미지 링크, 옵시디언 링크, 일반 링크 추출
md_pattern = re.compile(r'!\[([^\]]*)\]\(([^)]+)\)') # 이미지 링크 패턴
obsidian_pattern = re.compile(r'!\[\[([^\]]+)\]\]') # 옵시디언 링크 패턴
link_pattern = re.compile(r'\[([^\]]+)\]\(([^)]+)\)') # 일반 링크 패턴
links = set() # 중복 제거를 위한 set 사용
links.update(md_pattern.findall(content_without_code_blocks))
links.update(obsidian_pattern.findall(content_without_code_blocks))
links.update(link_pattern.findall(content_without_code_blocks))
return [link[1] if isinstance(link, tuple) else link for link in links]
# 마크다운 파일을 처리하는 함수
def process_markdown_files(base_path, image_directory):
broken_links = {
"Anchor": [],
"Image": [],
"Internal": [],
}
for md_root, dirs, md_files in os.walk(base_path):
# _나 .으로 시작하는 디렉토리 제외
dirs[:] = [d for d in dirs if not d.startswith('.') and not d.startswith('_')]
for md_file in md_files:
if md_file.endswith(".md") and md_file != "_index.md": # _index.md 파일 제외
md_file_path = os.path.join(md_root, md_file)
with open(md_file_path, 'r', encoding='utf-8') as f:
content = f.read()
links = extract_links(content)
for link in links:
actual_path = os.path.join(md_root, urllib.parse.unquote(link.replace('/', os.sep)))
if link.startswith('#'):
# 앵커 링크는 현재 파일 내에 해당 앵커가 존재하는지 확인
anchor = link[1:]
if not re.search(r'^#+\s+' + re.escape(anchor), content, re.MULTILINE):
broken_links["Anchor"].append((md_file_path, link, actual_path))
elif link.endswith(('.png', '.jpg', '.jpeg', '.gif', '.bmp')):
# 이미지 링크
image_path = os.path.join(image_directory, urllib.parse.unquote(link.replace('/', os.sep)))
if not os.path.exists(image_path):
broken_links["Image"].append((md_file_path, link, image_path))
elif not re.match(r'http[s]?://', link):
# 내부 링크
if not file_exists(base_path, link):
broken_links["Internal"].append((md_file_path, link, actual_path))
return broken_links
# 디렉토리 경로 설정
source_dir = os.environ.get('SOURCE_DIR', r"D:\obsidian")
source_static_dir = os.environ.get('SOURCE_STATIC_DIR', os.path.join(source_dir, "resources"))
# 마크다운 파일 처리 및 깨진 링크 찾기
broken_links = process_markdown_files(source_dir, source_static_dir)
# 깨진 링크 출력
print("\nBroken links check start")
for link_type, links in broken_links.items():
if links:
print(f"\t{link_type} Links:")
for md_file, link, actual_path in links:
print(f"\t\tFile: {md_file}, Link: {link}, Path: {actual_path}")
print("\nBroken links check end")
print()
기능
파일 존재 확인
file_exists
함수는 주어진 경로에 파일이 존재하는지 확인합니다.- 링크를 URL 디코딩하고, 경로 구분자를 운영체제에 맞게 변환하여 파일의 실제 존재 여부를 체크합니다.
링크 추출
extract_links
함수는 Markdown 파일에서 코드 블록을 제외한 이미지 링크, Obsidian 링크, 및 일반 링크를 추출합니다.- 코드 블록 내의 링크는 무시하여 실제 문서 내에서 유효성을 검증할 수 있습니다.
Markdown 파일 처리
process_markdown_files
함수는 모든 Markdown 파일을 순회하면서 각 파일에서 추출한 링크의 유효성을 확인합니다.- 링크의 종류에 따라 다음과 같이 처리합니다:
- 앵커 링크: 파일 내에 해당 앵커가 존재하는지 확인합니다.
- 이미지 링크: 이미지 파일이
resources
디렉토리에 존재하는지 확인합니다. - 내부 링크: 해당 파일이 존재하는지 확인하며, 외부 URL은 무시합니다.
필터
.
이나_
로 시작하는 폴더는 제외됩니다._index.md
파일은 제외됩니다.
결과 출력
- 깨진 링크가 발견되면, 파일 경로와 링크 경로, 실제 경로를 콘솔에 출력하며, 그 외 조치는 취하지 않습니다.