import gradio as gr
import zipfile
import re
import os
import shutil
from git import Repo
import tempfile
import uuid
def minify_content(content, file_type):
"""インデント、改行、コメントを削除(最小化)"""
if file_type == "html":
content = re.sub(r'', '', content, flags=re.DOTALL)
elif file_type == "css":
content = re.sub(r'/\*.*?\*/', '', content, flags=re.DOTALL)
elif file_type == "js":
content = re.sub(r'/\*.*?\*/', '', content, flags=re.DOTALL)
content = re.sub(r'//.*?$', '', content, flags=re.MULTILINE)
# 余分な空白を削除
content = re.sub(r'\s+', ' ', content)
return content.strip()
def read_file_content(path, minify=False):
try:
# テキストファイルとして読み込みを試みる
try:
with open(path, 'r', encoding='utf-8') as f:
content = f.read()
if minify:
if path.endswith('.html'):
return minify_content(content, "html")
elif path.endswith('.css'):
return minify_content(content, "css")
elif path.endswith('.js'):
return minify_content(content, "js")
return content
except UnicodeDecodeError:
# バイナリファイルの場合
with open(path, 'rb') as f:
content = f.read()
try:
return content.hex() # 16進数で完全表示
except:
return str(content) # フォールバック
except Exception as e:
return f"[ファイル読み込みエラー: {str(e)}]"
def remove_git_directory(temp_dir):
"""指定されたディレクトリ内の.gitディレクトリを削除"""
git_dir = os.path.join(temp_dir, '.git')
if os.path.exists(git_dir):
shutil.rmtree(git_dir, ignore_errors=True)
return True
return False
def extract_files(source, is_zip=True, remove_git=False):
# セッションごとに一意のディレクトリ名を生成
temp_dir = os.path.join(tempfile.gettempdir(), f"file_viewer_{uuid.uuid4().hex}")
os.makedirs(temp_dir, exist_ok=True)
try:
if is_zip:
with zipfile.ZipFile(source, 'r') as zip_ref:
zip_ref.extractall(temp_dir)
else:
Repo.clone_from(source, temp_dir, depth=1) # shallow cloneでサイズを抑える
if remove_git:
remove_git_directory(temp_dir)
file_paths = []
for root, dirs, files in os.walk(temp_dir):
# .gitディレクトリをスキップ
if '.git' in dirs:
dirs.remove('.git')
for file in files:
full_path = os.path.join(root, file)
rel_path = os.path.relpath(full_path, temp_dir)
file_paths.append((rel_path, full_path))
return temp_dir, file_paths
except Exception as e:
# エラー発生時は一時ディレクトリを削除
shutil.rmtree(temp_dir, ignore_errors=True)
raise e
def render_files(file_list, show_flags, minify_flags):
if not file_list:
return "ファイルが読み込まれていません"
result = ""
for (rel_path, full_path), show, minify in zip(file_list, show_flags, minify_flags):
if show: # 表示チェックが入っているファイルのみ処理
content = read_file_content(full_path, minify=minify)
result += f"### {rel_path}\n```\n{content}\n```\n\n"
return result
def create_checkboxes(file_names, show_flags, minify_flags):
"""チェックボックスを作成し、状態を返す"""
if not file_names:
return [], [], gr.HTML("
ファイルが読み込まれていません
")
checkboxes = []
new_show_flags = []
new_minify_flags = []
with gr.Group():
with gr.Column():
for idx, (name, show, minify) in enumerate(zip(file_names, show_flags, minify_flags)):
with gr.Row():
gr.Textbox(value=name, label="ファイル名", interactive=False)
show_cb = gr.Checkbox(value=show, label="表示")
minify_cb = gr.Checkbox(
value=minify,
label="最小化",
interactive=name.endswith(('.html', '.css', '.js'))
new_show_flags.append(show_cb)
new_minify_flags.append(minify_cb)
return new_show_flags, new_minify_flags, gr.HTML("")
def load_zip(zip_file):
if zip_file is None:
return None, None, None, None, None, "ZIPファイルを選択してください"
try:
# 前回の一時ディレクトリがあれば削除
if hasattr(load_zip, 'last_temp_dir'):
shutil.rmtree(load_zip.last_temp_dir, ignore_errors=True)
base_dir, files = extract_files(zip_file, is_zip=True)
load_zip.last_temp_dir = base_dir # 後でクリーンアップできるように保存
file_names_list = [f[0] for f in files]
shows = [True] * len(files)
minifies = [False] * len(files)
return base_dir, files, file_names_list, shows, minifies, "ZIPファイルを読み込みました"
except Exception as e:
return None, None, None, None, None, f"ZIPファイルの読み込みに失敗しました: {str(e)}"
def load_git(url, remove_git):
if not url:
return None, None, None, None, None, "GitリポジトリURLを入力してください"
try:
# 前回の一時ディレクトリがあれば削除
if hasattr(load_git, 'last_temp_dir'):
shutil.rmtree(load_git.last_temp_dir, ignore_errors=True)
base_dir, files = extract_files(url, is_zip=False, remove_git=remove_git)
load_git.last_temp_dir = base_dir # 後でクリーンアップできるように保存
file_names_list = [f[0] for f in files]
shows = [True] * len(files)
minifies = [False] * len(files)
message = "Gitリポジトリをクローンしました"
if remove_git and remove_git_directory(base_dir):
message += " (.gitディレクトリを削除)"
return base_dir, files, file_names_list, shows, minifies, message
except Exception as e:
return None, None, None, None, None, f"Gitリポジトリのクローンに失敗しました: {str(e)}"
def cleanup_temp_dirs():
"""アプリ終了時に一時ディレクトリをクリーンアップ"""
if hasattr(load_zip, 'last_temp_dir'):
shutil.rmtree(load_zip.last_temp_dir, ignore_errors=True)
if hasattr(load_git, 'last_temp_dir'):
shutil.rmtree(load_git.last_temp_dir, ignore_errors=True)
with gr.Blocks(title="ZIP/Git ファイルビューア") as demo:
gr.Markdown("## 📦 ZIP または 🐙 Git からファイルを読み込み、チェックボックスで内容制御")
with gr.Tab("ZIPファイル"):
zip_file = gr.File(label="ZIPファイルをアップロード", file_types=[".zip"])
zip_load_btn = gr.Button("ファイルを読み込み")
zip_status = gr.Markdown()
zip_path_state = gr.State()
zip_file_state = gr.State([])
zip_file_names = gr.State([])
zip_show_flags = gr.State([])
zip_minify_flags = gr.State([])
zip_output = gr.Markdown()
zip_checkbox_group = gr.Group()
zip_file_display_area = gr.HTML()
zip_load_btn.click(
fn=load_zip,
inputs=zip_file,
outputs=[zip_path_state, zip_file_state, zip_file_names, zip_show_flags, zip_minify_flags, zip_status]
).then(
fn=create_checkboxes,
inputs=[zip_file_names, zip_show_flags, zip_minify_flags],
outputs=[zip_show_flags, zip_minify_flags, zip_file_display_area]
)
zip_display_btn = gr.Button("表示する")
zip_display_btn.click(
fn=render_files,
inputs=[zip_file_state, zip_show_flags, zip_minify_flags],
outputs=zip_output
)
with gr.Tab("Gitリポジトリ"):
git_input = gr.Textbox(label="GitリポジトリURL", placeholder="https://github.com/user/repo.git")
remove_git_checkbox = gr.Checkbox(label=".gitディレクトリを削除", value=False)
git_load_btn = gr.Button("クローン")
git_status = gr.Markdown()
git_path_state = gr.State()
git_file_state = gr.State([])
git_file_names = gr.State([])
git_show_flags = gr.State([])
git_minify_flags = gr.State([])
git_output = gr.Markdown()
git_checkbox_group = gr.Group()
git_file_display_area = gr.HTML()
git_load_btn.click(
fn=load_git,
inputs=[git_input, remove_git_checkbox],
outputs=[git_path_state, git_file_state, git_file_names, git_show_flags, git_minify_flags, git_status]
).then(
fn=create_checkboxes,
inputs=[git_file_names, git_show_flags, git_minify_flags],
outputs=[git_show_flags, git_minify_flags, git_file_display_area]
)
git_display_btn = gr.Button("表示する")
git_display_btn.click(
fn=render_files,
inputs=[git_file_state, git_show_flags, git_minify_flags],
outputs=git_output
)
demo.load(cleanup_temp_dirs)
if __name__ == "__main__":
demo.launch()