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()