DeL-TaiseiOzaki commited on
Commit
227e75d
1 Parent(s): 0b1e3e4

first commit

Browse files
README copy.md ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # get_repository_info_by_llm
2
+
3
+ プログラミング関連ファイルを再帰的にスキャンし、内容を単一のテキストファイルにエクスポートするツールです。GitHubリポジトリまたはローカルディレクトリに対応しています。
4
+
5
+ ## 機能
6
+
7
+ - GitHubリポジトリのクローンとスキャン
8
+ - ローカルディレクトリのスキャン
9
+ - 再帰的なファイル検索
10
+ - 主要なプログラミング言語ファイルの検出
11
+ - UTF-8/CP932エンコーディングの自動検出
12
+ - 結果のテキストファイル出力
13
+
14
+ ## 必要条件
15
+
16
+ - Python 3.7以上
17
+ - Git(GitHubリポジトリをスキャンする場合)
18
+
19
+ ## インストール
20
+
21
+ 1. リポジトリをクローン
22
+ ```bash
23
+ git clone [このリポジトリのURL]
24
+ cd directory-scanner
25
+ ```
26
+
27
+ 2. 必要なディレクトリを作成
28
+ ```bash
29
+ mkdir output
30
+ ```
31
+
32
+ ## 使用方法
33
+
34
+ ### コマンドライン
35
+ ```bash
36
+ # GitHubリポジトリをスキャン
37
+ python main.py https://github.com/username/repository.git
38
+
39
+ # ローカルディレクトリをスキャン
40
+ python main.py /path/to/directory
41
+ ```
42
+
43
+ ### シェルスクリプトを使用
44
+ ```bash
45
+ # スクリプトに実行権限を付与
46
+ chmod +x scan.sh
47
+
48
+ # GitHubリポジトリをスキャン
49
+ ./scan.sh https://github.com/username/repository.git
50
+
51
+ # ローカルディレクトリをスキャン
52
+ ./scan.sh /path/to/directory
53
+ ```
54
+
55
+ ## 出力形式
56
+
57
+ スキャン結果は `output` ディレクトリに保存され、以下の形式で出力されます:
58
+
59
+ ```
60
+ #ファイルパス
61
+ path/to/file.py
62
+ ------------
63
+ ファイルの内容
64
+ ```
65
+
66
+ ## スキャン対象
67
+
68
+ ### 対象となるファイル拡張子
69
+ - Python (.py)
70
+ - JavaScript (.js)
71
+ - Java (.java)
72
+ - C/C++ (.c, .h, .cpp, .hpp)
73
+ - Go (.go)
74
+ - Rust (.rs)
75
+ - PHP (.php)
76
+ - Ruby (.rb)
77
+ - TypeScript (.ts)
78
+ - その他 (.scala, .kt, .cs, .swift, .m, .sh, .pl, .r)
79
+
80
+ ### 除外されるディレクトリ
81
+ - .git
82
+ - __pycache__
83
+ - node_modules
84
+ - venv
85
+ - .env
86
+ - build
87
+ - dist
88
+ - target
89
+ - bin
90
+ - obj
91
+
92
+ ## 注意事項
93
+
94
+ - GitHubリポジトリをスキャンする場合、一時的にローカルにクローンされます
95
+ - スキャン完了後、クローンされたリポジトリは自動的に削除されます
96
+ - 大きなファイルや特殊なエンコーディングのファイルは読み取れない場合があります
app.py ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import tempfile
3
+ import git
4
+ from pathlib import Path
5
+ from datetime import datetime
6
+ import time
7
+ from dotenv import load_dotenv
8
+ from core.file_scanner import FileScanner
9
+ from services.llm_service import LLMService
10
+
11
+ # 環境変数の読み込み
12
+ load_dotenv()
13
+
14
+ # ページ設定
15
+ st.set_page_config(
16
+ page_title="Repository Code Analysis",
17
+ page_icon="🔍",
18
+ layout="wide"
19
+ )
20
+
21
+ # カスタムCSS
22
+ st.markdown("""
23
+ <style>
24
+ .stAlert {
25
+ padding: 1rem;
26
+ margin: 1rem 0;
27
+ }
28
+ .css-1v0mbdj.ebxwdo61 {
29
+ width: 100%;
30
+ max-width: 800px;
31
+ }
32
+ </style>
33
+ """, unsafe_allow_html=True)
34
+
35
+ def clone_repository(repo_url: str) -> Path:
36
+ """リポジトリをクローンして一時ディレクトリに保存"""
37
+ temp_dir = Path(tempfile.mkdtemp())
38
+ git.Repo.clone_from(repo_url, temp_dir)
39
+ return temp_dir
40
+
41
+ # セッション状態の初期化
42
+ if 'repo_content' not in st.session_state:
43
+ st.session_state.repo_content = None
44
+ if 'temp_dir' not in st.session_state:
45
+ st.session_state.temp_dir = None
46
+ if 'llm_service' not in st.session_state:
47
+ st.session_state.llm_service = None
48
+
49
+ # メインのUIレイアウト
50
+ st.title("🔍 リポジトリ解析・質問システム")
51
+
52
+ # OpenAI APIキーの設定
53
+ api_key = st.sidebar.text_input("OpenAI APIキー", type="password", key="api_key")
54
+ if api_key:
55
+ st.session_state.llm_service = LLMService(api_key)
56
+
57
+ # URLの入力
58
+ repo_url = st.text_input(
59
+ "GitHubリポジトリのURLを入力",
60
+ placeholder="https://github.com/username/repository.git"
61
+ )
62
+
63
+ # スキャン実行ボタン
64
+ if st.button("スキャン開始", disabled=not repo_url):
65
+ try:
66
+ with st.spinner('リポジトリをクローン中...'):
67
+ temp_dir = clone_repository(repo_url)
68
+ st.session_state.temp_dir = temp_dir
69
+
70
+ with st.spinner('ファイルをスキャン中...'):
71
+ scanner = FileScanner(temp_dir)
72
+ files_content = scanner.scan_files()
73
+
74
+ if st.session_state.llm_service:
75
+ st.session_state.repo_content = LLMService.format_code_content(files_content)
76
+
77
+ st.success(f"スキャン完了: {len(files_content)}個のファイルを検出")
78
+
79
+ except Exception as e:
80
+ st.error(f"エラーが発生しました: {str(e)}")
81
+
82
+ # スキャン完了後の質問セクション
83
+ if st.session_state.repo_content and st.session_state.llm_service:
84
+ st.divider()
85
+ st.subheader("💭 コードについて質問する")
86
+
87
+ query = st.text_area(
88
+ "質問を入力してください",
89
+ placeholder="例: このコードの主な機能は何ですか?"
90
+ )
91
+
92
+ if st.button("質問する", disabled=not query):
93
+ with st.spinner('回答を生成中...'):
94
+ response, error = st.session_state.llm_service.get_response(
95
+ st.session_state.repo_content,
96
+ query
97
+ )
98
+
99
+ if error:
100
+ st.error(error)
101
+ else:
102
+ st.markdown("### 回答:")
103
+ st.markdown(response)
104
+
105
+ # セッション終了時のクリーンアップ
106
+ if st.session_state.temp_dir and Path(st.session_state.temp_dir).exists():
107
+ try:
108
+ import shutil
109
+ shutil.rmtree(st.session_state.temp_dir)
110
+ except:
111
+ pass
112
+
113
+ # サイドバー情報
114
+ with st.sidebar:
115
+ st.subheader("📌 使い方")
116
+ st.markdown("""
117
+ 1. OpenAI APIキーを入力
118
+ 2. GitHubリポジトリのURLを入力
119
+ 3. スキャンを実行
120
+ 4. コードについて質問
121
+ """)
122
+
123
+ st.subheader("🔍 スキャン対象")
124
+ st.markdown("""
125
+ - Python (.py)
126
+ - JavaScript (.js)
127
+ - Java (.java)
128
+ - C/C++ (.c, .h, .cpp, .hpp)
129
+ - その他の主要なプログラミング言語
130
+ """)
config/__init__.py ADDED
File without changes
config/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (153 Bytes). View file
 
config/__pycache__/setting.cpython-310.pyc ADDED
Binary file (1.02 kB). View file
 
config/__pycache__/settings.cpython-310.pyc ADDED
Binary file (1.03 kB). View file
 
config/settings.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+ from datetime import datetime
3
+
4
+ class Settings:
5
+ DEFAULT_OUTPUT_DIR = Path("output")
6
+ TIMESTAMP_FORMAT = "%Y%m%d_%H%M%S"
7
+
8
+ @classmethod
9
+ def get_timestamp(cls) -> str:
10
+ return datetime.now().strftime(cls.TIMESTAMP_FORMAT)
11
+
12
+ @classmethod
13
+ def get_clone_dir(cls, timestamp: str) -> Path:
14
+ return cls.DEFAULT_OUTPUT_DIR / f"repo_clone_{timestamp}"
15
+
16
+ @classmethod
17
+ def get_output_file(cls, timestamp: str) -> Path:
18
+ return cls.DEFAULT_OUTPUT_DIR / f"scan_result_{timestamp}.txt"
core/__init__.py ADDED
File without changes
core/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (151 Bytes). View file
 
core/__pycache__/file_scanner.cpython-310.pyc ADDED
Binary file (2.55 kB). View file
 
core/__pycache__/git_manager.cpython-310.pyc ADDED
Binary file (1.3 kB). View file
 
core/file_scanner.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+ from typing import List, Dict, Optional
3
+ from dataclasses import dataclass
4
+
5
+ @dataclass
6
+ class FileInfo:
7
+ path: Path
8
+ content: Optional[str] = None
9
+
10
+ class FileScanner:
11
+ # スキャン対象の拡張子
12
+ TARGET_EXTENSIONS = {
13
+ '.py', '.js', '.java', '.cpp', '.hpp', '.c', '.h',
14
+ '.go', '.rs', '.php', '.rb', '.ts', '.scala', '.kt',
15
+ '.cs', '.swift', '.m', '.sh', '.pl', '.r'
16
+ }
17
+
18
+ # スキャン対象から除外するディレクトリ
19
+ EXCLUDED_DIRS = {
20
+ '.git', '__pycache__', 'node_modules', 'venv', '.env',
21
+ 'build', 'dist', 'target', 'bin', 'obj'
22
+ }
23
+
24
+ def __init__(self, base_dir: Path):
25
+ self.base_dir = base_dir
26
+
27
+ def _should_scan_file(self, path: Path) -> bool:
28
+ if any(excluded in path.parts for excluded in self.EXCLUDED_DIRS):
29
+ return False
30
+ return path.suffix.lower() in self.TARGET_EXTENSIONS
31
+
32
+ def _read_file_content(self, file_path: Path) -> Optional[str]:
33
+ try:
34
+ # まずUTF-8で試す
35
+ try:
36
+ with file_path.open('r', encoding='utf-8') as f:
37
+ return f.read()
38
+ except UnicodeDecodeError:
39
+ # UTF-8で失敗したらcp932を試す
40
+ with file_path.open('r', encoding='cp932') as f:
41
+ return f.read()
42
+ except (OSError, UnicodeDecodeError):
43
+ return None
44
+
45
+ def scan_files(self) -> List[FileInfo]:
46
+ if not self.base_dir.exists():
47
+ raise FileNotFoundError(f"Directory not found: {self.base_dir}")
48
+
49
+ files = []
50
+
51
+ for entry in self.base_dir.rglob('*'):
52
+ if entry.is_file() and self._should_scan_file(entry):
53
+ content = self._read_file_content(entry)
54
+ if content is not None:
55
+ files.append(FileInfo(
56
+ path=entry.relative_to(self.base_dir),
57
+ content=content
58
+ ))
59
+
60
+ return sorted(files, key=lambda x: str(x.path))
core/git_manager.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+ from pathlib import Path
3
+
4
+ class GitManager:
5
+ def __init__(self, repo_url: str, target_dir: Path):
6
+ self.repo_url = repo_url
7
+ self.target_dir = target_dir
8
+
9
+ def clone_repository(self) -> bool:
10
+ try:
11
+ if self.target_dir.exists():
12
+ raise FileExistsError(f"Directory already exists: {self.target_dir}")
13
+
14
+ self.target_dir.parent.mkdir(parents=True, exist_ok=True)
15
+
16
+ subprocess.run(
17
+ ["git", "clone", self.repo_url, str(self.target_dir)],
18
+ check=True,
19
+ capture_output=True,
20
+ text=True
21
+ )
22
+ return True
23
+
24
+ except subprocess.CalledProcessError as e:
25
+ raise RuntimeError(f"Clone error: {e.stderr}")
26
+
27
+ def cleanup(self):
28
+ if self.target_dir.exists():
29
+ subprocess.run(
30
+ ["rm", "-rf", str(self.target_dir)],
31
+ check=True,
32
+ capture_output=True,
33
+ text=True
34
+ )
main.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ from pathlib import Path
3
+ from config.settings import Settings
4
+ from core.git_manager import GitManager
5
+ from core.file_scanner import FileScanner
6
+ from utils.file_writer import FileWriter
7
+
8
+ def main():
9
+ # コマンドライン引数からパスを取得
10
+ if len(sys.argv) != 2:
11
+ print("Usage: python main.py <github_url or directory_path>")
12
+ return 1
13
+
14
+ target_path = sys.argv[1]
15
+ timestamp = Settings.get_timestamp()
16
+ output_file = Settings.get_output_file(timestamp)
17
+
18
+ # GitHubのURLかローカルパスかを判定
19
+ is_github = target_path.startswith(('http://', 'https://')) and 'github.com' in target_path
20
+
21
+ try:
22
+ if is_github:
23
+ # GitHubリポジトリの場合
24
+ clone_dir = Settings.get_clone_dir(timestamp)
25
+ print(f"Cloning repository: {target_path}")
26
+
27
+ git_manager = GitManager(target_path, clone_dir)
28
+ git_manager.clone_repository()
29
+
30
+ scanner = FileScanner(clone_dir)
31
+ cleanup_needed = True
32
+ else:
33
+ # ローカルディレクトリの場合
34
+ target_dir = Path(target_path)
35
+ if not target_dir.exists():
36
+ print(f"Error: Directory not found: {target_dir}")
37
+ return 1
38
+
39
+ scanner = FileScanner(target_dir)
40
+ cleanup_needed = False
41
+
42
+ # ファイルスキャンと保存
43
+ print("Scanning files...")
44
+ files = scanner.scan_files()
45
+
46
+ print(f"Writing contents to {output_file}")
47
+ writer = FileWriter(output_file)
48
+ writer.write_contents(files)
49
+
50
+ print(f"Found {len(files)} files")
51
+ print(f"Results saved to {output_file}")
52
+
53
+ except Exception as e:
54
+ print(f"Error: {e}")
55
+ return 1
56
+
57
+ finally:
58
+ # GitHubリポジトリの場合はクリーンアップ
59
+ if is_github and cleanup_needed and 'git_manager' in locals():
60
+ try:
61
+ git_manager.cleanup()
62
+ print("Cleanup completed")
63
+ except Exception as e:
64
+ print(f"Cleanup error: {e}")
65
+
66
+ return 0
67
+
68
+ if __name__ == "__main__":
69
+ exit(main())
output/scan_result_20241030_210745.txt ADDED
@@ -0,0 +1,242 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #ファイルパス
2
+ Get_URL_list/get_url_list.py
3
+ ------------
4
+ import json
5
+ import requests
6
+ from bs4 import BeautifulSoup
7
+
8
+ # Load URLs from JSON file
9
+ with open('ideabte_scraping/Get_URL_list/URL_json_output/debate_urls.json', 'r') as f:
10
+ json_urls = json.load(f)
11
+
12
+ # Function to get sub-page URLs from a main theme URL
13
+ def get_debate_topic_urls(main_url):
14
+ response = requests.get(main_url)
15
+ soup = BeautifulSoup(response.text, 'html.parser')
16
+
17
+ # Extract all links from the main URL page
18
+ links = soup.find_all('a', href=True)
19
+
20
+ # Filter for links that are debate topics
21
+ topic_urls = [link['href'] for link in links if link['href'].startswith('/')]
22
+
23
+ # Make URLs absolute
24
+ full_urls = [f"https://idebate.net{url}" for url in topic_urls if "~b" in url]
25
+
26
+ return full_urls
27
+
28
+ # Dictionary to store all debate topic URLs for each main theme URL
29
+ all_debate_topic_urls = {}
30
+ for theme_url in json_urls:
31
+ theme_name = theme_url.split("/")[-2].replace("~", "_")
32
+ all_debate_topic_urls[theme_name] = get_debate_topic_urls(theme_url)
33
+
34
+ # Output the results
35
+ with open('ideabte_scraping/Get_URL_list/output/debate_topic_urls.json', 'w') as f:
36
+ json.dump(all_debate_topic_urls, f, indent=4)
37
+
38
+ print("Debate topic URLs have been saved to debate_topic_urls.json")
39
+
40
+ #ファイルパス
41
+ scraping_idebate/run_main.sh
42
+ ------------
43
+ #!/bin/bash
44
+
45
+ # Set default paths
46
+ JSON_FILE="ideabte_scraping/Get_URL_list/output/debate_topic_urls.json"
47
+ OUTPUT_DIR="ideabte_scraping/scraping_idebate/output"
48
+
49
+ # Check if the JSON file exists
50
+ if [ ! -f "$JSON_FILE" ]; then
51
+ echo "Error: JSON file '$JSON_FILE' does not exist."
52
+ exit 1
53
+ fi
54
+
55
+ # Create the output directory if it doesn't exist
56
+ mkdir -p "$OUTPUT_DIR"
57
+
58
+ # Run the Python script
59
+ python3 ideabte_scraping/scraping_idebate/src/scraping.py "$JSON_FILE" "$OUTPUT_DIR"
60
+
61
+ echo "Scraping completed. Output files are stored in $OUTPUT_DIR"
62
+
63
+ #ファイルパス
64
+ scraping_idebate/src/scraping.py
65
+ ------------
66
+ import requests
67
+ from bs4 import BeautifulSoup
68
+ import json
69
+ import os
70
+ import sys
71
+ from urllib.parse import urlparse
72
+
73
+ def scrape_url(url, output_dir):
74
+ response = requests.get(url)
75
+ response.raise_for_status()
76
+
77
+ soup = BeautifulSoup(response.content, 'html.parser')
78
+ topic = soup.find("h1", class_="blog-post__title").get_text(strip=True)
79
+
80
+ points_list = []
81
+
82
+ def extract_points(section, section_name):
83
+ accordion_items = section.find_next_sibling('div', class_='accordion').find_all('div', class_='accordion__item')
84
+ for item in accordion_items:
85
+ point_subtitle = item.find('h4', class_='accordion__subtitle').get_text().strip()
86
+ point_body = item.find('div', class_='accordion__body').find('p').get_text().strip()
87
+ points_list.append({
88
+ "topic": topic,
89
+ "section": section_name,
90
+ "context": f"**{point_subtitle}**\n{point_body}"
91
+ })
92
+
93
+ points_for_section = soup.find('div', class_='points-vote points-vote--for')
94
+ if points_for_section:
95
+ extract_points(points_for_section, "Points For")
96
+
97
+ points_against_section = soup.find('div', class_='points-vote points-vote--against')
98
+ if points_against_section:
99
+ extract_points(points_against_section, "Points Against")
100
+
101
+ # Generate a unique filename based on the URL
102
+ parsed_url = urlparse(url)
103
+ filename = f"{parsed_url.path.strip('/').replace('/', '_')}.json"
104
+ output_path = os.path.join(output_dir, filename)
105
+
106
+ with open(output_path, "w", encoding="utf-8") as f:
107
+ json.dump(points_list, f, ensure_ascii=False, indent=4)
108
+
109
+ print(f"Data saved to {output_path}")
110
+
111
+ if __name__ == "__main__":
112
+ if len(sys.argv) != 3:
113
+ print("Usage: python script.py <json_file> <output_dir>")
114
+ sys.exit(1)
115
+
116
+ json_file = sys.argv[1]
117
+ output_dir = sys.argv[2]
118
+
119
+ os.makedirs(output_dir, exist_ok=True)
120
+
121
+ with open(json_file, 'r') as f:
122
+ url_data = json.load(f)
123
+
124
+ for category, urls in url_data.items():
125
+ for url in urls:
126
+ try:
127
+ scrape_url(url, output_dir)
128
+ except Exception as e:
129
+ print(f"Error scraping {url}: {str(e)}")
130
+
131
+ #ファイルパス
132
+ scraping_idebate/src/scraping_test.py
133
+ ------------
134
+ import requests
135
+ from bs4 import BeautifulSoup
136
+
137
+ url = "https://idebate.net/this-house-would-make-all-museums-free-of-charge~b641/"
138
+
139
+ # ウェブページを取得
140
+ response = requests.get(url)
141
+ response.raise_for_status() # エラーチェック
142
+
143
+ # HTMLを解析
144
+ soup = BeautifulSoup(response.content, 'html.parser')
145
+
146
+ # Points Forのdiv要素を取得
147
+ points_for_section = soup.find('div', class_='points-vote points-vote--for')
148
+
149
+ # ポイントを含むアコーディオン要素を取得
150
+ accordion_items = points_for_section.find_next_sibling('div', class_='accordion').find_all('div', class_='accordion__item')
151
+
152
+ # 各ポイントのテキストを抽出
153
+ points = []
154
+ for item in accordion_items:
155
+ point_subtitle = item.find('h4', class_='accordion__subtitle').get_text().strip()
156
+ point_body = item.find('div', class_='accordion__body').find('p').get_text().strip()
157
+ points.append(f"**{point_subtitle}**\n{point_body}")
158
+
159
+ # 抽出したポイントを出力
160
+ for point in points:
161
+ print(point)
162
+ print("-" * 20) # 区切り線
163
+
164
+
165
+ #ファイルパス
166
+ scraping_idebate/src/scraping_tqdm.py
167
+ ------------
168
+ import requests
169
+ from bs4 import BeautifulSoup
170
+ import json
171
+ import os
172
+ import sys
173
+ from urllib.parse import urlparse
174
+ from tqdm import tqdm
175
+
176
+ def scrape_url(url, output_dir):
177
+ response = requests.get(url)
178
+ response.raise_for_status()
179
+
180
+ soup = BeautifulSoup(response.content, 'html.parser')
181
+ topic = soup.find("h1", class_="blog-post__title").get_text(strip=True)
182
+
183
+ points_list = []
184
+
185
+ def extract_points(section, section_name):
186
+ accordion_items = section.find_next_sibling('div', class_='accordion').find_all('div', class_='accordion__item')
187
+ for item in accordion_items:
188
+ point_subtitle = item.find('h4', class_='accordion__subtitle').get_text().strip()
189
+ point_body = item.find('div', class_='accordion__body').find('p').get_text().strip()
190
+ points_list.append({
191
+ "topic": topic,
192
+ "section": section_name,
193
+ "context": f"**{point_subtitle}**\n{point_body}"
194
+ })
195
+
196
+ points_for_section = soup.find('div', class_='points-vote points-vote--for')
197
+ if points_for_section:
198
+ extract_points(points_for_section, "Points For")
199
+
200
+ points_against_section = soup.find('div', class_='points-vote points-vote--against')
201
+ if points_against_section:
202
+ extract_points(points_against_section, "Points Against")
203
+
204
+ # Generate a unique filename based on the URL
205
+ parsed_url = urlparse(url)
206
+ filename = f"{parsed_url.path.strip('/').replace('/', '_')}.json"
207
+ output_path = os.path.join(output_dir, filename)
208
+
209
+ with open(output_path, "w", encoding="utf-8") as f:
210
+ json.dump(points_list, f, ensure_ascii=False, indent=4)
211
+
212
+ return output_path
213
+
214
+ if __name__ == "__main__":
215
+ if len(sys.argv) != 3:
216
+ print("Usage: python script.py <json_file> <output_dir>")
217
+ sys.exit(1)
218
+
219
+ json_file = sys.argv[1]
220
+ output_dir = sys.argv[2]
221
+
222
+ os.makedirs(output_dir, exist_ok=True)
223
+
224
+ with open(json_file, 'r') as f:
225
+ url_data = json.load(f)
226
+
227
+ total_urls = sum(len(urls) for urls in url_data.values())
228
+
229
+ with tqdm(total=total_urls, desc="Scraping Progress") as pbar:
230
+ for category, urls in url_data.items():
231
+ for url in urls:
232
+ try:
233
+ output_path = scrape_url(url, output_dir)
234
+ pbar.set_postfix_str(f"Saved: {output_path}")
235
+ pbar.update(1)
236
+ except Exception as e:
237
+ pbar.set_postfix_str(f"Error: {url}")
238
+ print(f"\nError scraping {url}: {str(e)}")
239
+ pbar.update(1)
240
+
241
+ print("\nScraping completed. All data saved to the output directory.")
242
+
rquirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ streamlit>=1.24.0
2
+ openai>=0.27.0
3
+ pathlib>=1.0.1
4
+ chardet>=4.0.0
scan.sh ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # エラーが発生した場合に停止
4
+ set -e
5
+
6
+ # デフォルトのターゲットパスを設定
7
+ # ここを変更することで対象を変更できます
8
+ TARGET_PATH="https://github.com/DeL-TaiseiOzaki/idebate_scraping.git" # 例: Linuxカーネル
9
+ # TARGET_PATH="/path/to/your/directory" # ローカルディレクトリの例
10
+
11
+ # 必要なディレクトリの存在確認
12
+ if [ ! -d "output" ]; then
13
+ mkdir output
14
+ fi
15
+
16
+ # Pythonの存在確認
17
+ if ! command -v python3 &> /dev/null; then
18
+ echo "Error: Python3 is not installed"
19
+ exit 1
20
+ fi
21
+
22
+ # GitHubリポジトリの場合、Gitの存在確認
23
+ if [[ $TARGET_PATH == http* ]] && [[ $TARGET_PATH == *github.com* ]]; then
24
+ if ! command -v git &> /dev/null; then
25
+ echo "Error: Git is not installed"
26
+ exit 1
27
+ fi
28
+ echo "Scanning GitHub repository: $TARGET_PATH"
29
+ else
30
+ if [ ! -d "$TARGET_PATH" ]; then
31
+ echo "Error: Directory not found: $TARGET_PATH"
32
+ exit 1
33
+ fi
34
+ echo "Scanning local directory: $TARGET_PATH"
35
+ fi
36
+
37
+ # スキャンの実行
38
+ echo "Starting directory scan..."
39
+ python3 main.py "$TARGET_PATH"
40
+
41
+ exit_code=$?
42
+
43
+ if [ $exit_code -eq 0 ]; then
44
+ echo "Scan completed successfully!"
45
+ echo "Results are saved in the 'output' directory"
46
+ else
47
+ echo "Scan failed with exit code: $exit_code"
48
+ exit $exit_code
49
+ fi
services/llm_service.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+ import openai
3
+ from pathlib import Path
4
+
5
+ class LLMService:
6
+ def __init__(self, api_key: str):
7
+ """
8
+ LLMサービスの初期化
9
+ Args:
10
+ api_key: OpenAI APIキー
11
+ """
12
+ self.api_key = api_key
13
+ openai.api_key = api_key
14
+
15
+ def create_prompt(self, content: str, query: str) -> str:
16
+ """
17
+ プロンプトを生成
18
+ Args:
19
+ content: コードの内容
20
+ query: ユーザーからの質問
21
+ Returns:
22
+ 生成されたプロンプト
23
+ """
24
+ return f"""以下はGitHubリポジトリのコード解析結果です。このコードについて質問に答えてください。
25
+
26
+ コード解析結果:
27
+ {content}
28
+
29
+ 質問: {query}
30
+
31
+ できるだけ具体的に、コードの内容を参照しながら回答してください。"""
32
+
33
+ def get_response(self, content: str, query: str) -> tuple[str, Optional[str]]:
34
+ """
35
+ LLMを使用して回答を生成
36
+ Args:
37
+ content: コードの内容
38
+ query: ユーザーからの質問
39
+ Returns:
40
+ (回答, エラーメッセージ)のタプル
41
+ """
42
+ try:
43
+ prompt = self.create_prompt(content, query)
44
+
45
+ response = openai.ChatCompletion.create(
46
+ model="gpt-3.5-turbo-16k",
47
+ messages=[
48
+ {
49
+ "role": "system",
50
+ "content": "あなたはコードアナリストとして、リポジトリの解析と質問への回答を行います。"
51
+ },
52
+ {
53
+ "role": "user",
54
+ "content": prompt
55
+ }
56
+ ]
57
+ )
58
+
59
+ return response.choices[0].message.content, None
60
+
61
+ except Exception as e:
62
+ return None, f"エラーが発生しました: {str(e)}"
63
+
64
+ @staticmethod
65
+ def format_code_content(files_content: dict) -> str:
66
+ """
67
+ ファイル内容をプロンプト用にフォーマット
68
+ Args:
69
+ files_content: ファイルパスと内容の辞書
70
+ Returns:
71
+ フォーマットされたテキスト
72
+ """
73
+ formatted_content = []
74
+ for file_path, content in files_content.items():
75
+ formatted_content.append(
76
+ f"#ファイルパス\n{file_path}\n------------\n{content}\n"
77
+ )
78
+ return "\n".join(formatted_content)
utils/__init__.py ADDED
File without changes
utils/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (152 Bytes). View file
 
utils/__pycache__/content_exporter.cpython-310.pyc ADDED
Binary file (2.36 kB). View file
 
utils/__pycache__/file_writer.cpython-310.pyc ADDED
Binary file (1.13 kB). View file
 
utils/file_writer.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+ from typing import List
3
+ from core.file_scanner import FileInfo
4
+
5
+ class FileWriter:
6
+ def __init__(self, output_file: Path):
7
+ self.output_file = output_file
8
+
9
+ def write_contents(self, files: List[FileInfo]) -> None:
10
+ self.output_file.parent.mkdir(parents=True, exist_ok=True)
11
+
12
+ with self.output_file.open('w', encoding='utf-8') as f:
13
+ for file_info in files:
14
+ # ファイルパスのセクション
15
+ f.write("#ファイルパス\n")
16
+ f.write(str(file_info.path))
17
+ f.write("\n------------\n")
18
+
19
+ # ファイル内容
20
+ if file_info.content is not None:
21
+ f.write(file_info.content)
22
+ else:
23
+ f.write("# Failed to read content")
24
+ f.write("\n\n")
utils/logger.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+ from typing import List
3
+ from datetime import datetime
4
+ from core.file_scanner import FileInfo
5
+
6
+ class ScanLogger:
7
+ def __init__(self, log_file: Path):
8
+ self.log_file = log_file
9
+
10
+ def write_log(self, repo_url: str, files: List[FileInfo], stats: dict):
11
+ """スキャン結果をログファイルに書き込みます"""
12
+ self.log_file.parent.mkdir(parents=True, exist_ok=True)
13
+
14
+ with self.log_file.open('w', encoding='utf-8') as f:
15
+ f.write(f"スキャン日時: {datetime.now()}\n")
16
+ f.write(f"リポジトリ: {repo_url}\n")
17
+ f.write(f"ファイル数: {len(files)}\n\n")
18
+
19
+ f.write("=== ファイル種類の統計 ===\n")
20
+ for ext, count in stats.items():
21
+ f.write(f"{ext}: {count}個\n")
22
+ f.write("\n")
23
+
24
+ f.write("=== ファイルパス一覧 ===\n")
25
+ for file_info in files:
26
+ f.write(f"{file_info.path} ({file_info.formatted_size})\n")