Spaces:
Sleeping
Sleeping
File size: 8,644 Bytes
b7b8cc1 227e75d b7b8cc1 227e75d b7b8cc1 396927c 31a278f b7b8cc1 227e75d b7b8cc1 749e7c0 b7b8cc1 0520d98 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
# app.py
import streamlit as st
import tempfile
import shutil
from pathlib import Path
import git # GitPython
from core.file_scanner import FileScanner, FileInfo
# =====================================
# セッション状態の初期化
# =====================================
if 'scanned_files' not in st.session_state:
st.session_state.scanned_files = [] # スキャンしたFileInfoリスト
if 'selected_files' not in st.session_state:
st.session_state.selected_files = set() # ユーザーが選択中のファイルパス (相対パス)
if 'cloned_repo_dir' not in st.session_state:
st.session_state.cloned_repo_dir = None # クローン先ディレクトリの絶対パス文字列
# =====================================
# タイトル等
# =====================================
st.title("Gitリポジトリ スキャナー")
st.markdown("**ディレクトリ構造をツリー表示し、ファイルを選んでMarkdownダウンロードできます**\n(**ワイドモード推奨**)")
# =====================================
# ツリー構造を生成する関数
# =====================================
def build_tree(paths):
"""
相対パス(Pathオブジェクト)のリストからツリー状のネスト構造を構築する。
戻り値は {要素名 -> 子要素のdict or None} という入れ子の辞書。
"""
tree = {}
for p in paths:
parts = p.parts
current = tree
for i, part in enumerate(parts):
if i == len(parts) - 1:
# ファイルやフォルダの末端
current[part] = None
else:
if part not in current:
current[part] = {}
if isinstance(current[part], dict):
current = current[part]
else:
# もしNoneだった場合(同名のファイル/フォルダがあるなど) → 無理やりdictに
current[part] = {}
current = current[part]
return tree
def format_tree(tree_dict, prefix=""):
"""
build_tree()で作ったネスト構造をASCIIアートのツリー文字列にする。
"""
lines = []
entries = sorted(tree_dict.keys())
for i, entry in enumerate(entries):
is_last = (i == len(entries) - 1)
marker = "└── " if is_last else "├── "
# 子要素がある(=dict)ならフォルダ、Noneならファイル
if isinstance(tree_dict[entry], dict):
# フォルダとして表示
lines.append(prefix + marker + entry + "/")
# 次の階層のプレフィックスを用意
extension = " " if is_last else "│ "
sub_prefix = prefix + extension
# 再帰的に生成
lines.extend(format_tree(tree_dict[entry], sub_prefix))
else:
# ファイルとして表示
lines.append(prefix + marker + entry)
return lines
# =====================================
# ユーザー入力
# =====================================
repo_url = st.text_input("GitリポジトリURL (例: https://github.com/username/repo.git)")
st.subheader("スキャン対象拡張子")
available_exts = [".py", ".js", ".ts", ".sh", ".md", ".txt", ".java", ".cpp", ".json",".yaml",""]
chosen_exts = []
for ext in available_exts:
default_checked = (ext in [".py", ".md"]) # デモ用に .py と .md を初期ON
if st.checkbox(ext, key=f"ext_{ext}", value=default_checked):
chosen_exts.append(ext)
# =====================================
# スキャン開始ボタン
# =====================================
if st.button("スキャン開始"):
if not repo_url.strip():
st.error("リポジトリURLを入力してください。")
else:
# 既にクローン済フォルダがあれば削除
if st.session_state.cloned_repo_dir and Path(st.session_state.cloned_repo_dir).exists():
shutil.rmtree(st.session_state.cloned_repo_dir, ignore_errors=True)
# 一時フォルダを作成してクローン
tmp_dir = tempfile.mkdtemp()
clone_path = Path(tmp_dir) / "cloned_repo"
try:
st.write(f"リポジトリをクローン中: {clone_path}")
git.Repo.clone_from(repo_url, clone_path)
st.session_state.cloned_repo_dir = str(clone_path)
except Exception as e:
st.error(f"クローン失敗: {e}")
st.session_state.cloned_repo_dir = None
st.session_state.scanned_files = []
st.stop()
# スキャン
scanner = FileScanner(base_dir=clone_path, target_extensions=set(chosen_exts))
found_files = scanner.scan_files()
st.session_state.scanned_files = found_files
st.session_state.selected_files = set()
st.success(f"スキャン完了: {len(found_files)}個のファイルを検出")
# =====================================
# クローン削除ボタン
# =====================================
if st.session_state.cloned_repo_dir:
if st.button("クローン済みデータを削除"):
shutil.rmtree(st.session_state.cloned_repo_dir, ignore_errors=True)
st.session_state.cloned_repo_dir = None
st.session_state.scanned_files = []
st.session_state.selected_files = set()
st.success("クローンしたディレクトリを削除しました")
# =====================================
# スキャン結果がある場合 → ツリー表示 + ファイル選択
# =====================================
if st.session_state.scanned_files:
base_path = Path(st.session_state.cloned_repo_dir)
# --- ツリーを作る ---
# scanned_files は「指定拡張子」だけ取得されているので、そのファイルパスのみでツリーを構築
rel_paths = [f.path.relative_to(base_path) for f in st.session_state.scanned_files]
tree_dict = build_tree(rel_paths)
tree_lines = format_tree(tree_dict)
ascii_tree = "\n".join(tree_lines)
st.write("## スキャン結果")
col_tree, col_files = st.columns([1, 2]) # 左:ツリー, 右:ファイル一覧
with col_tree:
st.markdown("**ディレクトリ構造 (指定拡張子のみ)**")
st.markdown(f"```\n{ascii_tree}\n```")
with col_files:
st.markdown("**ファイル一覧 (チェックボックス)**")
col_btn1, col_btn2 = st.columns(2)
with col_btn1:
if st.button("すべて選択"):
st.session_state.selected_files = set(rel_paths)
with col_btn2:
if st.button("すべて解除"):
st.session_state.selected_files = set()
for file_info in st.session_state.scanned_files:
rel_path = file_info.path.relative_to(base_path)
checked = rel_path in st.session_state.selected_files
new_checked = st.checkbox(
f"{rel_path} ({file_info.formatted_size})",
value=checked,
key=str(rel_path) # keyの重複回避
)
if new_checked:
st.session_state.selected_files.add(rel_path)
else:
st.session_state.selected_files.discard(rel_path)
# =====================================
# 選択ファイルをまとめてMarkdown化 & ダウンロード
# =====================================
def create_markdown_for_selected(files, selected_paths, base_dir: Path) -> str:
output = []
for f in files:
rel_path = f.path.relative_to(base_dir)
if rel_path in selected_paths:
output.append(f"## {rel_path}")
output.append("------------")
if f.content is not None:
output.append(f.content)
else:
output.append("# Failed to read content")
output.append("") # 空行
return "\n".join(output)
if st.session_state.scanned_files:
st.write("## 選択ファイルをダウンロード")
if st.button("選択ファイルをMarkdownとしてダウンロード(整形後,下にダウンロードボタンが出ます)"):
base_path = Path(st.session_state.cloned_repo_dir)
markdown_text = create_markdown_for_selected(
st.session_state.scanned_files,
st.session_state.selected_files,
base_path
)
st.download_button(
label="Markdownダウンロード",
data=markdown_text,
file_name="selected_files.md",
mime="text/markdown"
)
|