AtCoderStudyBooster 0.3__py3-none-any.whl → 0.3.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
atcdr/util/fileops.py ADDED
@@ -0,0 +1,102 @@
1
+ import functools
2
+ import glob
3
+ import os
4
+ from typing import List, Tuple
5
+
6
+ import questionary as q
7
+ import rich_click as click
8
+
9
+ from atcdr.util.filetype import FILE_EXTENSIONS, Lang
10
+
11
+
12
+ def collect_files(
13
+ patterns: Tuple[str, ...],
14
+ exts: Tuple[str, ...],
15
+ recursive: bool,
16
+ ) -> List[str]:
17
+ # 1) ベース候補
18
+ if recursive:
19
+ candidates = []
20
+ for root, _, files in os.walk('.'):
21
+ for f in files:
22
+ candidates.append(os.path.join(root, f))
23
+ else:
24
+ candidates = [f for f in os.listdir('.') if os.path.isfile(f)]
25
+
26
+ # 2) パターン+glob 展開
27
+ matched = set()
28
+ pats = patterns or ['*']
29
+ for pat in pats:
30
+ for m in glob.glob(pat, recursive=recursive):
31
+ if os.path.isfile(m):
32
+ matched.add(m)
33
+ if '*' not in pat and os.path.isfile(pat):
34
+ matched.add(pat)
35
+
36
+ if exts:
37
+ matched = {f for f in matched if os.path.splitext(f)[1] in exts}
38
+
39
+ return sorted(matched)
40
+
41
+
42
+ def select_files_interactively(files: List[str]) -> List[str]:
43
+ target_file = q.select(
44
+ message='複数のファイルが見つかりました.ファイルを選択してください:',
45
+ choices=[q.Choice(title=file, value=file) for file in files],
46
+ instruction='\n 十字キーで移動, [enter]で実行',
47
+ pointer='❯',
48
+ qmark='',
49
+ style=q.Style(
50
+ [
51
+ ('qmark', 'fg:#2196F3 bold'),
52
+ ('question', 'fg:#2196F3 bold'),
53
+ ('answer', 'fg:#FFB300 bold'),
54
+ ('pointer', 'fg:#FFB300 bold'),
55
+ ('highlighted', 'fg:#FFB300 bold'),
56
+ ('selected', 'fg:#FFB300 bold'),
57
+ ]
58
+ ),
59
+ ).ask()
60
+ return target_file
61
+
62
+
63
+ def add_file_selector(
64
+ arg_name: str,
65
+ filetypes: list[Lang],
66
+ ):
67
+ def decorator(f):
68
+ @click.argument(arg_name, nargs=-1, type=click.STRING)
69
+ @click.pass_context
70
+ @functools.wraps(f)
71
+ def wrapper(ctx: click.Context, **kwargs):
72
+ # Click から渡される元のパターン一覧を取得
73
+ patterns: tuple[str, ...] = kwargs.pop(arg_name)
74
+
75
+ # 1) 拡張子リストを作成
76
+ exts = [FILE_EXTENSIONS[lang] for lang in filetypes]
77
+
78
+ # 2) ファイル収集 (非再帰固定)
79
+ files = collect_files(patterns, tuple(exts), recursive=False)
80
+ if not files:
81
+ click.echo('対象ファイルが見つかりません。')
82
+ ctx.exit(1)
83
+
84
+ # 3) 候補が1つなら即実行
85
+ if len(files) == 1:
86
+ return ctx.invoke(f, **{arg_name: files}, **kwargs)
87
+
88
+ # 4) 引数なしなら対話選択、それ以外はまとめて渡す
89
+ if not patterns:
90
+ selected = select_files_interactively(files)
91
+ if not selected:
92
+ click.echo('ファイルが選択されませんでした。')
93
+ ctx.exit(1)
94
+ selected_list = [selected]
95
+ return ctx.invoke(f, **{arg_name: selected_list}, **kwargs)
96
+
97
+ # 5) patterns 指定ありならマッチ全件を渡す
98
+ return ctx.invoke(f, **{arg_name: files}, **kwargs)
99
+
100
+ return wrapper
101
+
102
+ return decorator
atcdr/util/filetype.py CHANGED
@@ -1,6 +1,6 @@
1
1
  import os
2
2
  from enum import Enum
3
- from typing import Dict, List, Optional, TypeAlias
3
+ from typing import Dict, List, TypeAlias
4
4
 
5
5
  # ファイル名と拡張子の型エイリアスを定義
6
6
  Filename: TypeAlias = str
@@ -8,98 +8,98 @@ Extension: TypeAlias = str
8
8
 
9
9
 
10
10
  class Lang(Enum):
11
- PYTHON = 'Python'
12
- JAVASCRIPT = 'JavaScript'
13
- JAVA = 'Java'
14
- C = 'C'
15
- CPP = 'C++'
16
- CSHARP = 'C#'
17
- RUBY = 'Ruby'
18
- PHP = 'php'
19
- GO = 'Go'
20
- RUST = 'Rust'
21
- HTML = 'HTML'
22
- MARKDOWN = 'markdown'
23
- JSON = 'json'
11
+ PYTHON = 'Python'
12
+ JAVASCRIPT = 'JavaScript'
13
+ JAVA = 'Java'
14
+ C = 'C'
15
+ CPP = 'C++'
16
+ CSHARP = 'C#'
17
+ RUBY = 'Ruby'
18
+ PHP = 'php'
19
+ GO = 'Go'
20
+ RUST = 'Rust'
21
+ HTML = 'HTML'
22
+ MARKDOWN = 'markdown'
23
+ JSON = 'json'
24
24
 
25
25
 
26
26
  # ファイル拡張子と対応する言語の辞書
27
27
  FILE_EXTENSIONS: Dict[Lang, str] = {
28
- Lang.PYTHON: '.py',
29
- Lang.JAVASCRIPT: '.js',
30
- Lang.JAVA: '.java',
31
- Lang.C: '.c',
32
- Lang.CPP: '.cpp',
33
- Lang.CSHARP: '.cs',
34
- Lang.RUBY: '.rb',
35
- Lang.PHP: '.php',
36
- Lang.GO: '.go',
37
- Lang.RUST: '.rs',
38
- Lang.HTML: '.html',
39
- Lang.MARKDOWN: '.md',
40
- Lang.JSON: '.json',
28
+ Lang.PYTHON: '.py',
29
+ Lang.JAVASCRIPT: '.js',
30
+ Lang.JAVA: '.java',
31
+ Lang.C: '.c',
32
+ Lang.CPP: '.cpp',
33
+ Lang.CSHARP: '.cs',
34
+ Lang.RUBY: '.rb',
35
+ Lang.PHP: '.php',
36
+ Lang.GO: '.go',
37
+ Lang.RUST: '.rs',
38
+ Lang.HTML: '.html',
39
+ Lang.MARKDOWN: '.md',
40
+ Lang.JSON: '.json',
41
41
  }
42
42
 
43
43
  # ドキュメント言語のリスト
44
44
  DOCUMENT_LANGUAGES: List[Lang] = [
45
- Lang.HTML,
46
- Lang.MARKDOWN,
47
- Lang.JSON,
45
+ Lang.HTML,
46
+ Lang.MARKDOWN,
47
+ Lang.JSON,
48
48
  ]
49
49
 
50
50
  # コンパイル型言語のリスト
51
51
  COMPILED_LANGUAGES: List[Lang] = [
52
- Lang.JAVA,
53
- Lang.C,
54
- Lang.CPP,
55
- Lang.CSHARP,
56
- Lang.GO,
57
- Lang.RUST,
52
+ Lang.JAVA,
53
+ Lang.C,
54
+ Lang.CPP,
55
+ Lang.CSHARP,
56
+ Lang.GO,
57
+ Lang.RUST,
58
58
  ]
59
59
 
60
60
  # インタプリター型言語のリスト
61
61
  INTERPRETED_LANGUAGES: List[Lang] = [
62
- Lang.PYTHON,
63
- Lang.JAVASCRIPT,
64
- Lang.RUBY,
65
- Lang.PHP,
62
+ Lang.PYTHON,
63
+ Lang.JAVASCRIPT,
64
+ Lang.RUBY,
65
+ Lang.PHP,
66
66
  ]
67
67
 
68
68
 
69
69
  def str2lang(lang: str) -> Lang:
70
- lang_map = {
71
- 'py': Lang.PYTHON,
72
- 'python': Lang.PYTHON,
73
- 'js': Lang.JAVASCRIPT,
74
- 'javascript': Lang.JAVASCRIPT,
75
- 'java': Lang.JAVA,
76
- 'c': Lang.C,
77
- 'cpp': Lang.CPP,
78
- 'c++': Lang.CPP,
79
- 'csharp': Lang.CSHARP,
80
- 'cs': Lang.CSHARP,
81
- 'c#': Lang.CSHARP,
82
- 'rb': Lang.RUBY,
83
- 'ruby': Lang.RUBY,
84
- 'php': Lang.PHP,
85
- 'go': Lang.GO,
86
- 'rs': Lang.RUST,
87
- 'rust': Lang.RUST,
88
- 'html': Lang.HTML,
89
- 'md': Lang.MARKDOWN,
90
- 'markdown': Lang.MARKDOWN,
91
- 'json': Lang.JSON,
92
- }
93
- return lang_map[lang.lower()]
70
+ lang_map = {
71
+ 'py': Lang.PYTHON,
72
+ 'python': Lang.PYTHON,
73
+ 'js': Lang.JAVASCRIPT,
74
+ 'javascript': Lang.JAVASCRIPT,
75
+ 'java': Lang.JAVA,
76
+ 'c': Lang.C,
77
+ 'cpp': Lang.CPP,
78
+ 'c++': Lang.CPP,
79
+ 'csharp': Lang.CSHARP,
80
+ 'cs': Lang.CSHARP,
81
+ 'c#': Lang.CSHARP,
82
+ 'rb': Lang.RUBY,
83
+ 'ruby': Lang.RUBY,
84
+ 'php': Lang.PHP,
85
+ 'go': Lang.GO,
86
+ 'rs': Lang.RUST,
87
+ 'rust': Lang.RUST,
88
+ 'html': Lang.HTML,
89
+ 'md': Lang.MARKDOWN,
90
+ 'markdown': Lang.MARKDOWN,
91
+ 'json': Lang.JSON,
92
+ }
93
+ return lang_map[lang.lower()]
94
94
 
95
95
 
96
96
  def lang2str(lang: Lang) -> str:
97
- return lang.value
97
+ return lang.value
98
98
 
99
99
 
100
- def detect_language(path: str) -> Optional[Lang]:
101
- ext = os.path.splitext(path)[1] # ファイルの拡張子を取得
102
- lang = next(
103
- (lang for lang, extension in FILE_EXTENSIONS.items() if extension == ext), None
104
- )
105
- return lang
100
+ def detect_language(path: str) -> Lang:
101
+ ext = os.path.splitext(path)[1] # ファイルの拡張子を取得
102
+ lang = next(
103
+ (lang for lang, extension in FILE_EXTENSIONS.items() if extension == ext)
104
+ )
105
+ return lang
atcdr/util/gpt.py CHANGED
@@ -1,112 +1,118 @@
1
1
  import os
2
+ from enum import Enum
2
3
  from typing import Dict, List, Optional
3
4
 
4
5
  import requests
5
6
 
6
- from atcdr.util.cost import CostType, Currency, Model, Rate
7
+
8
+ class Model(Enum):
9
+ GPT4O = 'gpt-4o'
10
+ GPT41 = 'gpt-4.1'
11
+ GPT41_MINI = 'gpt-4.1-mini'
12
+ GPT41_NANO = 'gpt-4.1-nano'
13
+ GPT4O_MINI = 'gpt-4o-mini'
14
+ O1_PREVIEW = 'o1-preview'
15
+ O1 = 'o1'
16
+ O3 = 'o3'
17
+ O1_MINI = 'o1-mini'
18
+ O3_MINI = 'o3-mini'
19
+ O4_MINI = 'o4-mini'
7
20
 
8
21
 
9
22
  def set_api_key() -> Optional[str]:
10
- api_key = os.getenv('OPENAI_API_KEY')
11
- if api_key and validate_api_key(api_key):
12
- return api_key
13
- elif api_key:
14
- print('環境変数に設定されているAPIキーの検証に失敗しました ')
15
- else:
16
- pass
17
-
18
- api_key = input(
19
- 'https://platform.openai.com/api-keys からchatGPTのAPIキーを入手しましょう。\nAPIキー入力してください: '
20
- )
21
- if validate_api_key(api_key):
22
- print('APIキーのテストに成功しました。')
23
- print('以下, ~/.zshrcにAPIキーを保存しますか? [y/n]')
24
- if input() == 'y':
25
- zshrc_path = os.path.expanduser('~/.zshrc')
26
- with open(zshrc_path, 'a') as f:
27
- f.write(f'export OPENAI_API_KEY={api_key}\n')
28
- print(
29
- f'APIキーを {zshrc_path} に保存しました。次回シェル起動時に読み込まれます。'
30
- )
31
- os.environ['OPENAI_API_KEY'] = api_key
32
- return api_key
33
- else:
34
- print('コード生成にはAPIキーが必要です。')
35
- return None
23
+ api_key = os.getenv('OPENAI_API_KEY')
24
+ if api_key and validate_api_key(api_key):
25
+ return api_key
26
+ elif api_key:
27
+ print('環境変数に設定されているAPIキーの検証に失敗しました ')
28
+ else:
29
+ pass
30
+
31
+ api_key = input(
32
+ 'https://platform.openai.com/api-keys からchatGPTのAPIキーを入手しましょう。\nAPIキー入力してください: '
33
+ )
34
+ if validate_api_key(api_key):
35
+ print('APIキーのテストに成功しました。')
36
+ print('以下, ~/.zshrcにAPIキーを保存しますか? [y/n]')
37
+ if input() == 'y':
38
+ zshrc_path = os.path.expanduser('~/.zshrc')
39
+ with open(zshrc_path, 'a') as f:
40
+ f.write(f'export OPENAI_API_KEY={api_key}\n')
41
+ print(
42
+ f'APIキーを {zshrc_path} に保存しました。次回シェル起動時に読み込まれます。'
43
+ )
44
+ os.environ['OPENAI_API_KEY'] = api_key
45
+ return api_key
46
+ else:
47
+ print('コード生成にはAPIキーが必要です。')
48
+ return None
36
49
 
37
50
 
38
51
  def validate_api_key(api_key: str) -> bool:
39
- headers = {
40
- 'Content-Type': 'application/json',
41
- 'Authorization': f'Bearer {api_key}',
42
- }
52
+ headers = {
53
+ 'Content-Type': 'application/json',
54
+ 'Authorization': f'Bearer {api_key}',
55
+ }
43
56
 
44
- response = requests.get('https://api.openai.com/v1/models', headers=headers)
57
+ response = requests.get('https://api.openai.com/v1/models', headers=headers)
45
58
 
46
- if response.status_code == 200:
47
- return True
48
- else:
49
- print('APIキーの検証に失敗しました。')
50
- return False
59
+ if response.status_code == 200:
60
+ return True
61
+ else:
62
+ print('APIキーの検証に失敗しました。')
63
+ return False
51
64
 
52
65
 
53
66
  class ChatGPT:
54
- API_URL = 'https://api.openai.com/v1/chat/completions'
55
-
56
- # APIの使い方 https://platform.openai.com/docs/api-reference/making-requests
57
- def __init__(
58
- self,
59
- api_key: Optional[str] = None,
60
- model: Model = Model.GPT4O_MINI,
61
- max_tokens: int = 3000,
62
- temperature: float = 0.7,
63
- messages: Optional[List[Dict[str, str]]] = None,
64
- system_prompt: str = 'You are a helpful assistant.',
65
- ) -> None:
66
- self.api_key = api_key or os.getenv('OPENAI_API_KEY')
67
- self.model = model
68
- self.max_tokens = max_tokens
69
- self.temperature = temperature
70
- self.messages = (
71
- messages
72
- if messages is not None
73
- else [{'role': 'system', 'content': system_prompt}]
74
- )
75
-
76
- self.sum_cost: Currency = Currency(usd=0)
77
- self.__headers = {
78
- 'Content-Type': 'application/json',
79
- 'Authorization': f'Bearer {self.api_key}',
80
- }
81
-
82
- def tell(self, message: str) -> str:
83
- self.messages.append({'role': 'user', 'content': message})
84
-
85
- settings = {
86
- 'model': self.model.value,
87
- 'messages': self.messages,
88
- 'max_tokens': self.max_tokens,
89
- 'temperature': self.temperature,
90
- }
91
-
92
- response = requests.post(self.API_URL, headers=self.__headers, json=settings)
93
- responsej = response.json()
94
- try:
95
- reply = responsej['choices'][0]['message']['content']
96
- except KeyError:
97
- print('Error:レスポンスの形式が正しくありません. \n' + str(responsej))
98
- return 'Error: Unable to retrieve response.'
99
-
100
- self.messages.append({'role': 'assistant', 'content': reply})
101
-
102
- usage = responsej['usage']
103
- input_tokens = usage.get('prompt_tokens', 0)
104
- output_tokens = usage.get('completion_tokens', 0)
105
- self.sum_cost += Rate.calc_cost(
106
- model=self.model, cost_type=CostType.INPUT, token_count=input_tokens
107
- )
108
- self.sum_cost += Rate.calc_cost(
109
- model=self.model, cost_type=CostType.OUTPUT, token_count=output_tokens
110
- )
111
-
112
- return reply
67
+ API_URL = 'https://api.openai.com/v1/chat/completions'
68
+
69
+ # APIの使い方 https://platform.openai.com/docs/api-reference/making-requests
70
+ def __init__(
71
+ self,
72
+ api_key: Optional[str] = None,
73
+ model: Model = Model.GPT41_MINI,
74
+ max_tokens: int = 3000,
75
+ temperature: float = 0.7,
76
+ messages: Optional[List[Dict[str, str]]] = None,
77
+ system_prompt: str = 'You are a helpful assistant.',
78
+ ) -> None:
79
+ self.api_key = api_key or os.getenv('OPENAI_API_KEY')
80
+ self.model = model
81
+ self.max_tokens = max_tokens
82
+ self.temperature = temperature
83
+ self.messages = (
84
+ messages
85
+ if messages is not None
86
+ else [{'role': 'system', 'content': system_prompt}]
87
+ )
88
+
89
+ self.__headers = {
90
+ 'Content-Type': 'application/json',
91
+ 'Authorization': f'Bearer {self.api_key}',
92
+ }
93
+
94
+ def tell(self, message: str) -> str:
95
+ self.messages.append({'role': 'user', 'content': message})
96
+
97
+ settings = {
98
+ 'model': self.model.value,
99
+ 'messages': self.messages,
100
+ 'max_tokens': self.max_tokens,
101
+ 'temperature': self.temperature,
102
+ }
103
+
104
+ response = requests.post(self.API_URL, headers=self.__headers, json=settings)
105
+ responsej = response.json()
106
+ try:
107
+ reply = responsej['choices'][0]['message']['content']
108
+ except KeyError:
109
+ print('Error:レスポンスの形式が正しくありません. \n' + str(responsej))
110
+ return 'Error: Unable to retrieve response.'
111
+
112
+ self.messages.append({'role': 'assistant', 'content': reply})
113
+
114
+ # usage = responsej['usage']
115
+ # input_tokens = usage.get('prompt_tokens', 0)
116
+ # output_tokens = usage.get('completion_tokens', 0)
117
+
118
+ return reply