DeL-TaiseiOzaki commited on
Commit
ff0d7b1
·
verified ·
1 Parent(s): 8490a22

Update app.py

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