Sphinx作成支援プログラム
Sphinxマニュアル作成を支援するプログラムの使い方をまとめています。
生成AIを使うプログラムでは、API KEYを 環境変数 OPENAI_API_KEY あるいは GOOGLE_API_KEYに設定してください。また、AI modelのデフォルトは
explain_program5.iniで設定されていますが、このファイルは無くても問題ありません。explain_program5.py: 引数で与えたpythonプログラムのマニュアル(usage) Markdownを作成します。プロンプトは
explain_program5.iniで設定します。add_docstring.py: 引数で与えたpythonプログラムにsphinx互換のdocstringを追加します。プロンプトは
add_docstring.iniで設定します。make_sphinx_files.py: 引数で与えたpythonプログラムに対して、上記プログラムを呼び出すとともに、関連するSphinxファイルを作成します
save_clipboard.py: (Windowsのみ) クリップボードのテキストや画像をファイルに保存する
AI共通設定ファイル ai.env
ai.env
1account_inf_path=d:/MyWebs/Database/accounts.env
2
3min_translate_length = 0
4allowed_translation_length_ratio = 5.0
5html_template_path = 'template_translate.html'
6max_tokens = 5000
7
8#openai_model = "gpt-3.5-turbo"
9openai_model = "gpt-4o"
10#openai_model = "gpt-4.5"
11#openai_model5=gpt-5
12#openai_model5=gpt-5-mini
13openai_model5=gpt-5-nano
14#openai_model5=gpt-5-chat-latest
15
16temperature = 0.3
17#reasoning_effort=minimal
18reasoning_effort=low
19#reasoning_effort=medium
20#reasoning_effort=high
21
22
23#gemini_model = "gemini-1.5-pro-latest"
24gemini_model = "gemini-2.5-flash"
25tsleep_rpm = 4
explain_program5.py (プログラム)
explain_program5.py
1"""
2プログラムコードをAIでドキュメント化するツール。
3
4指定されたワイルドカードパターンに一致するソースコードファイルを読み込み、
5設定ファイルに基づいたプロンプトを使用してAI(OpenAIまたはGoogle Gemini)に
6ドキュメントを生成させ、指定された出力ファイルに書き出します。
7
8:doc:`explain_program5_usage`
9"""
10
11#!/usr/bin/env python3
12# -*- coding: utf-8 -*-
13
14import os
15import sys
16import argparse
17import glob
18import time
19import re
20from pathlib import Path
21
22# 既存のライブラリ(環境に合わせてパスを通してください)
23try:
24 from tkai_lib import read_ai_config
25 from tkai_lib import query_openai4, query_openai5, query_google
26 from tkai_lib import extract_openai5_text
27except ImportError:
28 print("Error: tkai_lib が見つかりません。パスを確認してください。", file=sys.stderr)
29 sys.exit(1)
30
31#=========================================================
32# INI Reading Logic (make_textbook5.py より継承)
33#=========================================================
34def search_file(infile=None):
35 """
36 指定されたファイルパスまたはデフォルトのINIファイルを探索します。
37
38 infileが指定されない場合、スクリプトの実行ディレクトリとカレントディレクトリから
39 デフォルトのINIファイル(スクリプト名.ini)を探します。
40 infileが指定された場合、そのパスが存在するか、またはスクリプトの実行ディレクトリからの
41 相対パスで存在するかを確認します。
42
43 :param infile: str, 探索するファイルパス。デフォルトはNoneで、その場合はデフォルトINIファイル名を使用。
44 :returns: str または None, 見つかったファイルの絶対パス。見つからない場合はNone。
45 """
46 script_path = os.path.abspath(sys.argv[0])
47 script_dir = os.path.dirname(script_path)
48 script_name = os.path.splitext(os.path.basename(script_path))[0]
49 default_ini = f"{script_name}.ini"
50
51 if infile is None:
52 for path in [os.getcwd(), script_dir]:
53 candidate = os.path.join(path, default_ini)
54 if os.path.isfile(candidate): return candidate
55 return None
56
57 if not os.path.isfile(infile):
58 candidate = os.path.join(script_dir, infile)
59 if os.path.isfile(candidate): return candidate
60 return None
61 return infile
62
63def read_ini(inifile=None):
64 """
65 指定されたINIファイルから設定を読み込み、変数を展開します。
66
67 key=value形式の行を解析し、3重引用符で囲まれた複数行の値をサポートします。
68 また、`$VARIABLE_NAME` 形式の変数を、既に読み込まれた設定値で展開します。
69 コメント行(`#` または `;` で始まる)と空行は無視されます。
70
71 :param inifile: str または None, 読み込むINIファイルのパス。Noneの場合はsearch_fileで探索される。
72 :returns: dict, 読み込まれた設定をキーと値のペアで格納した辞書。
73 :raises FileNotFoundError: INIファイルが見つからない場合。
74 """
75 path = search_file(inifile)
76 if path is None:
77 raise FileNotFoundError("INIファイルが見つかりませんでした")
78
79 result = {}
80 variables = {}
81 current_key = None
82 multiline_val = []
83 multiline_delim = None
84
85 with open(path, 'r', encoding='utf-8') as f:
86 for line in f:
87 line = line.rstrip()
88
89 if not line or line.startswith('#') or line.startswith(';'):
90 continue
91
92 # 複数行値の終了判定(stripで判定)
93 if multiline_delim:
94 if line.strip() == multiline_delim:
95 val = '\n'.join(multiline_val)
96 result[current_key] = val
97 variables[current_key] = val
98 current_key = None
99 multiline_val = []
100 multiline_delim = None
101 else:
102 multiline_val.append(line)
103 continue
104
105 # key=val の解析
106 if '=' in line:
107 key, val = map(str.strip, line.split('=', 1))
108 val = val.strip()
109
110 # 複数行値の開始判定(空文字でも対応)
111 if (val == '"""' or val == "'''" or
112 (val.startswith('"""') and not val.endswith('"""')) or \
113 (val.startswith("'''") and not val.endswith("'''")) ):
114 multiline_delim = val[:3]
115 content = val[3:]
116 multiline_val = [content] if content else []
117 current_key = key
118 continue
119
120 # 単一行の複数行値
121 if (val.startswith('"""') and val.endswith('"""')) or \
122 (val.startswith("'''") and val.endswith("'''")):
123 val = val[3:-3]
124
125 result[key] = val
126 variables[key] = val
127
128 # 変数展開(あとから一括処理)
129 for key, val in result.items():
130 def expand_var(match):
131 var_name = match.group(1)
132 return variables.get(var_name, match.group(0))
133 result[key] = re.sub(r"\$(\w+)\b", expand_var, val)
134
135 return result
136
137#=========================================================
138# Language Dictionary
139#=========================================================
140language_dict = {
141 ".py": "python", ".pl": "perl", ".pm": "perl", ".c" : "C",
142 ".cpp": "C++", ".pas": "pascal", ".f" : "fortran", ".js" : "Javascript",
143 ".java": "Java", ".go": "Go", ".sh": "bash script", ".html": "HTML"
144}
145
146def get_program_type(path):
147 """
148 プログラムファイルのパスからプログラムのタイプ(`main` または `lib`)を判定します。
149
150 ファイル拡張子が`.pm`の場合、またはファイル名が`tk`で始まる場合、ライブラリ(`lib`)と判定します。
151 それ以外の場合はメインプログラム(`main`)と判定します。
152
153 :param path: str, プログラムファイルのパス。
154 :returns: str, プログラムのタイプ (`'main'` または `'lib'`)。
155 """
156 base = os.path.basename(path)
157 name, ext = os.path.splitext(base)
158 if ext == ".pm" or base.startswith("tk"): return 'lib'
159 return 'main'
160
161def initialize():
162 """
163 コマンドライン引数パーサーを初期化し、設定します。
164
165 `argparse.ArgumentParser` を設定し、必要な引数とオプションを定義します。
166 `pattern` (必須, ワイルドカード), `output` (任意), `--inifile`, `--api`,
167 `-t` (`--program_type`), `-u` (`--update`), `-w` (`--overwrite`) オプションが含まれます。
168 デフォルトのINIファイルパスはスクリプト名から自動生成されます。
169
170 :returns: argparse.ArgumentParser, 初期化されたコマンドライン引数パーサーオブジェクト。
171 """
172 ini_path = os.path.splitext(sys.argv[0])[0] + ".ini"
173
174 parser = argparse.ArgumentParser(description="プログラムコードをAIでドキュメント化するツール")
175 parser.add_argument("pattern", help="対象ファイルのワイルドカード(例: '*.py')")
176 parser.add_argument("output", nargs="?", help="出力名(単一ファイル時のみ)")
177 parser.add_argument("--inifile", default=ini_path, help="プロンプト設定ファイル")
178 parser.add_argument("--api", choices=["openai", "openai5", "google", "gemini"], default='google')
179 parser.add_argument("-t", "--program_type", choices=["", "main", "lib"], default="")
180 parser.add_argument("-u", "--update", type=int, default=0)
181 parser.add_argument("-w", "--overwrite", type=int, default=0)
182 return parser
183
184def main():
185 """
186 プログラムの主要な処理を実行します。
187
188 `initialize` 関数で初期化された引数を解析し、AI設定ファイル (`ai.env`) と
189 プロンプトINIファイルを読み込みます。
190 指定されたパターンに一致するファイルを検索し、一つずつ処理します。
191 出力ファイルが既に存在し、更新フラグや上書きフラグが設定されていない場合はスキップします。
192 ソースコードを読み込み、言語タイプとプログラムタイプを判定し、
193 INIファイルの設定に基づいてプロンプトを構築します。
194 AIサービス(OpenAIまたはGoogle Gemini)に問い合わせてドキュメントを生成し、
195 生成されたドキュメントを出力ファイルに書き込みます。
196 各ファイル処理後に1秒間待機し、最後にユーザーからの終了入力を待ちます。
197
198 :returns: None
199 """
200 parser = initialize()
201 args = parser.parse_args()
202
203 # AI設定の読み込み
204 read_ai_config("ai.env")
205
206 # プロンプトINIの読み込み
207 try:
208 ini_data = read_ini(args.inifile)
209 print(f"Loaded INI: {search_file(args.inifile)}")
210 except Exception as e:
211 print(f"Error loading INI: {e}")
212 sys.exit(1)
213
214 files = glob.glob(args.pattern)
215 if not files: sys.exit(1)
216
217 outputs = [args.output] if args.output else [os.path.splitext(f)[0] + ".md" for f in files]
218
219 for inp, out in zip(files, outputs):
220 if os.path.exists(out) and not args.overwrite and (not args.update or os.path.getmtime(out) >= os.path.getmtime(inp)):
221 print(f"Skip: {out}")
222 continue
223
224 print(f"Processing: {inp} -> {out}")
225 try:
226 code = Path(inp).read_text(encoding="utf-8")
227 except:
228 code = Path(inp).read_text(encoding="shift-jis")
229
230 ext = os.path.splitext(inp)[1]
231 lang = language_dict.get(ext, "text")
232 p_type = args.program_type or get_program_type(inp)
233
234 # プロンプト構築
235 tpl_key = f"PROMPT_{p_type.upper()}"
236 template = ini_data.get(tpl_key, ini_data.get("PROMPT_MAIN"))
237 role = ini_data.get("SYSTEM_ROLE", "Assistant")
238
239 prompt = template.replace("{{script_name}}", inp).replace("{{code}}", code).replace("{{language}}", lang)
240
241 # AI呼び出し
242 if args.api == "openai5":
243 res = query_openai5(prompt, os.getenv("openai_model5"), instructions=role)
244 doc = extract_openai5_text(res)
245 elif args.api == "openai":
246 res = query_openai4(prompt, os.getenv("openai_model"), role=role)
247 doc = res.choices[0].message.content
248 else:
249 res = query_google(prompt, os.getenv("gemini_model"), role=role)
250 doc = res.text if res else None
251
252 if doc:
253 Path(out).write_text(doc, encoding="utf-8")
254 print(f"Done: {out}")
255 time.sleep(1)
256
257 input("\nPress ENTER to terminate>>\n")
258
259if __name__ == "__main__":
260 main()
explain_program5.ini (設定ファイル)
explain_program5.ini
1SYSTEM_ROLE = """
2You are a helpful assistant that generates clear, well-structured documentation for the given program.
3"""
4
5PROMPT_MAIN = """
6以下は {{language}}言語 で記述されたプログラム `{{script_name}}` のソースコードです。
7このコードを解析し、以下の「構成」と「出力形式の絶対条件」に従って、技術ドキュメント(Markdown)を作成してください。
8
9### 1. 全体要件(厳守)
101. **適切な改行**: 各セクションの間や、見出しの前後には必ず空行(連続した改行)を1行以上挿入してください。
112. **全体をコードブロックで囲わない**
123. **冒頭のタイトル**: 出力の1行目はMarkdownの大見出し # を使った以下の形式とし、直後に必ず1行の空行を入れてください。
13# {{script_name}} ドキュメント
14(ここに空行を入れる)
15
16### 1. 構成案
171. **見出し**: 以下の各章は、Markdownの第二段階見出し ## で始める
182. **プログラムの動作**: プログラムの目的、主な機能、および解決する課題。
193. **原理**: 使用されている数式・物理式やアルゴリズムの解説。
204. **必要な非標準ライブラリとインストール方法**: `pip` コマンド等を含めること。
215. **必要な入力ファイル**: 期待されるファイル形式やデータ構造。
226. **生成される出力ファイル**: 保存されるファイル名とその内容。
237. **コマンドラインでの使用例 (Usage)**: 基本的な実行コマンドと引数の説明。
248. **コマンドラインでの具体的な使用例**: 具体的な引数を与えた実行例とその実行結果の説明。
25
26
27### 2. 出力形式に関する絶対条件(厳守)
28- **禁止記号と代替表記(重要)**:
29 - **三重引用符( `"""` および `'''` )の出力は、いかなる理由があっても厳禁とします。**
30 - プログラミング上の記号として言及が必要な場合は、必ず **「3重引用符」** というテキストに置き換えて記述してください。
31 - 例:「docstringを3重引用符で囲む」とし、「docstringを """ で囲む」とは書かないこと。
32
33- **数式の共通仕様**:
34 - **インライン数式**: $a^2 + b^2 = c^2$ のように、ドル記号1つで囲む。
35 - **ブロック数式**: 独立した行に記述し、**その前後は必ず空行で挟んでください。**
36 例:
37
38 $$\rho = \frac{\pi d}{\ln 2} \cdot \frac{R_A + R_B}{2} \cdot f$$
39
40- **プログラム名**: マニュアル内では `{{script_name}}` として扱ってください。
41
42---
43### 対象プログラムコード
44```{{language}}
45{{code}}
46```
47"""
48
49
50PROMPT_LIB = """
51以下は {{language}}言語 で記述されたライブラリ `{{script_name}}` のソースコードです。
52このコードを解析し、以下の「構成」と「出力形式の絶対条件」に従って、技術ドキュメント(Markdown)を作成してください。
53
54### 1. 全体要件(厳守)
551. **適切な改行**: 各セクションの間や、見出しの前後には必ず空行(連続した改行)を1行以上挿入してください。
562. **全体をコードブロックで囲わない**
573. **冒頭のタイトル**: 出力の1行目はMarkdownの大見出し # を使った以下の形式とし、直後に必ず1行の空行を入れてください。
58# {{script_name}} ドキュメント
59(ここに空行を入れる)
60
61### 2. 構成案
621. **見出し**: 以下の各章は、Markdownの第二段階見出し ## で始める
632. **ライブラリの機能や目的**: ライブラリの目的、主な機能、および解決する課題。
643. **importする方法**: このライブラリを他のプログラムからimportする方法。
654. **必要な非標準ライブラリとインストール方法**: `pip` コマンド等を含めること。
665. **importできる変数と関数**: 変数には説明するコメントを、関数には関数の動作と引数・戻り値の説明を入れること。
676. **main scriptとして実行したときの動作**
68
69### 3. 出力形式に関する絶対条件(厳守)
70- **禁止記号と代替表記(重要)**:
71 - **三重引用符( `"""` および `'''` )の出力は、いかなる理由があっても厳禁とします。**
72 - プログラミング上の記号として言及が必要な場合は、必ず **「3重引用符」** というテキストに置き換えて記述してください。
73 - 例:「docstringを3重引用符で囲む」とし、「docstringを """ で囲む」とは書かないこと。
74
75- **数式の共通仕様**:
76 - **インライン数式**: $a^2 + b^2 = c^2$ のように、ドル記号1つで囲む。
77 - **ブロック数式**: 独立した行に記述し、**その前後は必ず空行で挟んでください。**
78 例:
79
80 $$\rho = \frac{\pi d}{\ln 2} \cdot \frac{R_A + R_B}{2} \cdot f$$
81
82- **ライブラリ名**: マニュアル内では `{{script_name}}` として扱ってください。
83
84---
85### 対象プログラムコード
86```{{language}}
87{{code}}
88```
89"""
add_docstring.py (プログラム)
add_docstring.py
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3"""
4PythonコードにSphinx形式のDocstringを自動追加するツール。
5
6このスクリプトは、指定されたPythonソースファイルの内容をAIモデルに送信し、
7Sphinx形式のDocstringが挿入された完成版のコードを生成します。
8コマンドライン引数で入力ファイルパターン、出力ファイル名、
9INIファイルによるプロンプト設定、使用するAIモデルなどを指定できます。
10生成されたDocstringは、関数やクラスの概要、詳細説明、引数、戻り値などを
11日本語で記述します。既存のファイルを上書きするか、更新日時でスキップするかなど、
12柔軟なファイル処理オプションを提供します。
13
14:doc:`add_docstring_usage`
15"""
16
17import os
18import sys
19import argparse
20import glob
21import time
22import re
23import traceback
24from pathlib import Path
25
26# AI連携用ライブラリのインポート
27try:
28 from tkai_lib import read_ai_config
29 from tkai_lib import query_openai4, query_openai5, query_google
30 from tkai_lib import extract_openai5_text
31except ImportError:
32 print("Error: tkai_lib.py が見つかりません。ライブラリのパスを確認してください。", file=sys.stderr)
33 sys.exit(1)
34
35# =========================================================
36# INI検索・読み込みロジック (explain_program5.py の設計を継承)
37# =========================================================
38def search_file(infile=None):
39 """
40 指定されたファイルを作業ディレクトリとスクリプトディレクトリから検索する。
41
42 `infile` が指定されない場合、実行スクリプト名からデフォルトのINIファイル名を生成します。
43 まずカレントディレクトリでファイルを検索し、見つからない場合はスクリプトが
44 配置されているディレクトリで検索します。
45
46 :param infile: str, optional: 検索対象のファイル名。デフォルトはNone。
47 :returns: str or None: 見つかったファイルの絶対パス、またはNone。
48 """
49 script_path = os.path.abspath(sys.argv[0])
50 script_dir = os.path.dirname(script_path)
51 script_name = os.path.splitext(os.path.basename(script_path))[0]
52 default_ini = f"{script_name}.ini"
53
54 search_target = infile if infile else default_ini
55
56 candidate1 = os.path.join(os.getcwd(), search_target)
57 if os.path.isfile(candidate1):
58 return candidate1
59
60 candidate2 = os.path.join(script_dir, search_target)
61 if os.path.isfile(candidate2):
62 return candidate2
63
64 return None
65
66def read_ini(inifile=None):
67 """
68 INIファイルの内容を読み込み、辞書として返す。
69
70 `search_file` を使用してINIファイルのパスを特定し、ファイルを読み込みます。
71 `#` または `;` で始まる行はコメントとしてスキップされます。
72 `key = value` 形式の行を解析し、トリプルクォート (`"""` または `'''`) を
73 使用した複数行の値をサポートします。また、読み込み後には `$VAR` 形式の
74 変数を展開します。
75
76 :param inifile: str, optional: 読み込むINIファイルのパス。デフォルトはNone。
77 :returns: dict: INIファイルから読み込んだ設定を格納する辞書。
78 :raises FileNotFoundError: 指定されたINIファイルが見つからない場合。
79 """
80 path = search_file(inifile)
81 if path is None:
82 raise FileNotFoundError(f"INIファイルが見つかりませんでした: {inifile}")
83
84 result = {}
85 variables = {}
86 current_key = None
87 multiline_val = []
88 multiline_delim = None
89
90 with open(path, 'r', encoding='utf-8') as f:
91 for line in f:
92 line = line.rstrip()
93 if not line or line.startswith('#') or line.startswith(';'):
94 continue
95
96 if multiline_delim:
97 if line.strip() == multiline_delim:
98 val = '\n'.join(multiline_val)
99 result[current_key] = val
100 variables[current_key] = val
101 current_key = None
102 multiline_val = []
103 multiline_delim = None
104 else:
105 multiline_val.append(line)
106 continue
107
108 if '=' in line:
109 key, val = map(str.strip, line.split('=', 1))
110 if (val == '\"\"\"' or val == "\'\'\'" or
111 (val.startswith('\"\"\"') and not val.endswith('\"\"\"')) or
112 (val.startswith("\'\'\'") and not val.endswith("\'\'\'"))):
113 multiline_delim = val[:3]
114 content = val[3:]
115 multiline_val = [content] if content else []
116 current_key = key
117 continue
118
119 if (val.startswith('\"\"\"') and val.endswith('\"\"\"')) or \
120 (val.startswith("\'\'\'") and val.endswith("\'\'\'")):
121 val = val[3:-3]
122
123 result[key] = val
124 variables[key] = val
125
126 for key, val in result.items():
127 def expand_var(match):
128 return variables.get(match.group(1), match.group(0))
129 result[key] = re.sub(r"\$(\w+)\b", expand_var, val)
130
131 return result
132
133# =========================================================
134# メイン処理
135# =========================================================
136def initialize():
137 """
138 コマンドライン引数パーサーを初期化し、設定する。
139
140 `argparse.ArgumentParser` を初期化し、ツールの説明を設定します。
141 必要な引数(`pattern`, `output`)とオプション引数(`--inifile`, `--api`,
142 `--update`, `--overwrite`, `--pause`)を追加します。
143 `default_ini_name` は実行スクリプト名に基づいて生成されます。
144
145 :returns: argparse.ArgumentParser: 設定済みのパーサーオブジェクト。
146 """
147 default_ini_name = os.path.splitext(os.path.basename(sys.argv[0]))[0] + ".ini"
148
149 parser = argparse.ArgumentParser(description="PythonコードにSphinx形式のDocstringを自動追加するツール")
150 # explain_program5.py に合わせた引数構成
151 parser.add_argument("pattern", help="対象ファイルのワイルドカード(例: '*.py')")
152 parser.add_argument("output", nargs="?", help="出力名(単一ファイル時のみ)")
153 parser.add_argument("--inifile", default=default_ini_name, help="プロンプト設定ファイルパス")
154 parser.add_argument("--api", choices=["openai", "openai5", "google", "gemini"], default='google')
155 parser.add_argument("-u", "--update", type=int, default=0, help="1の場合、ソースが新しい場合のみ更新")
156 parser.add_argument("-w", "--overwrite", type=int, default=0, help="1の場合、既存ファイルを上書き")
157 parser.add_argument("-p", "--pause", type=int, default=1)
158 return parser
159
160def read_args(parser):
161 """
162 ArgumentParserから引数を解析し、その値を表示する。
163
164 `parser.parse_args()` を呼び出して引数を解析し、
165 解析された引数の値を標準出力に表示します。
166
167 :param parser: argparse.ArgumentParser: 初期化済みのArgumentParserオブジェクト。
168 :returns: argparse.Namespace: 解析された引数を格納するオブジェクト。
169 """
170 args = parser.parse_args()
171 print("Args:")
172 print(f" {args.pattern=}")
173 print(f" {args.output=}")
174 print(f" {args.inifile=}")
175 print(f" {args.api=}")
176 print(f" {args.update=}")
177 print(f" {args.overwrite=}")
178 print(f" {args.pause=}")
179 return args
180
181def main():
182 """
183 スクリプトのメイン処理を実行する。
184
185 `initialize` と `read_args` を呼び出してコマンドライン引数を処理します。
186 AI設定ファイル (`ai.env`) を読み込み、指定されたINIファイルからプロンプト設定を取得します。
187 ワイルドカードパターンに合致するファイル群を処理し、各ファイルについて
188 更新・上書きルールに従って処理をスキップするか判断します。
189 ファイル内容を読み込み、プロンプトを構築してAIモデルを呼び出します。
190 AIからの応答からDocstringを抽出し、指定された出力ファイルに書き込みます。
191 エラー発生時はメッセージを表示し、スタックトレースを出力します。
192 API呼び出しの負荷軽減のため、ファイル処理ごとに1秒待機し、
193 `pause` 引数が設定されている場合、終了前にユーザー入力を待ちます。
194
195 :returns: None
196 :raises SystemExit: INIファイルの読み込み失敗時や、マッチするファイルがない場合に発生。
197 """
198 print()
199 print(f"=== {sys.argv[0]} ===")
200
201 parser = initialize()
202 args = read_args(parser)
203
204 read_ai_config("ai.env")
205
206 try:
207 ini_data = read_ini(args.inifile)
208 print(f"Loaded INI: {search_file(args.inifile)}")
209 except Exception as e:
210 print(f"Error loading INI: {e}")
211 sys.exit(1)
212
213 # ワイルドカード展開
214 files = glob.glob(args.pattern)
215 if not files:
216 print(f"No files matched pattern: {args.pattern}")
217 sys.exit(1)
218
219 # 出力ファイル名のリスト作成
220 if args.output and len(files) == 1:
221 outputs = [args.output]
222 else:
223 outputs = [os.path.splitext(f)[0] + "_docstring.py" for f in files]
224
225 for inp, out in zip(files, outputs):
226 # 更新・上書きチェックロジック
227 if os.path.exists(out) and not args.overwrite:
228 # updateモードかつ、出力ファイルの方が新しい場合はスキップ
229 if not args.update or os.path.getmtime(out) >= os.path.getmtime(inp):
230 print(f"Skip: {out}")
231 continue
232
233 print(f"Processing: {inp} -> {out}")
234
235 try:
236 code = Path(inp).read_text(encoding="utf-8")
237 except:
238 try:
239 code = Path(inp).read_text(encoding="shift-jis")
240 except Exception as e:
241 print(f"Error reading {inp}: {e}")
242 continue
243
244 base_name = os.path.splitext(os.path.basename(inp))[0]
245 template = ini_data.get("PROMPT_MAIN", "")
246 role = ini_data.get("SYSTEM_ROLE", "Assistant")
247
248 prompt = template.replace("{{script_name}}", inp)\
249 .replace("{{code}}", code)\
250 .replace("add_docstring", base_name)
251
252 try:
253 if args.api == "openai5":
254 res = query_openai5(prompt, os.getenv("openai_model5"), instructions=role)
255 doc = extract_openai5_text(res)
256 elif args.api == "openai":
257 res = query_openai4(prompt, os.getenv("openai_model"), role=role)
258 doc = res.choices[0].message.content
259 else:
260 res = query_google(prompt, os.getenv("gemini_model"), role=role)
261 doc = res.text if res else None
262
263 if doc:
264 # Markdownブロックの除去
265 doc = doc.strip()
266 if doc.startswith("```"):
267 doc = re.sub(r'^```[a-zA-Z]*\n', '', doc)
268 doc = re.sub(r'\n```$', '', doc)
269
270 Path(out).write_text(doc, encoding="utf-8")
271 print(f"Done: {out}")
272 else:
273 print(f"Error: No response from AI for {inp}")
274
275 except Exception:
276 print(f"\n!!! Error during AI processing for {inp} !!!")
277 traceback.print_exc()
278
279 time.sleep(1) # API連続呼び出しの負荷軽減
280
281 if args.pause:
282 input("\nPress ENTER to terminate>>\n")
283
284if __name__ == "__main__":
285 main()
add_docstring.ini (設定ファイル)
add_docstring.ini
1# システムの役割
2SYSTEM_ROLE = """
3あなたはPythonのドキュメンテーションエンジニアです。提供するソースコードの内容を解析し、
4Sphinx (reStructuredText) 形式に最適化された高品質なDocstringのみを生成してください。
5"""
6
7# メインのプロンプトテンプレート
8PROMPT_MAIN = """
9【絶対遵守ルール】
10 1. 既存のロジック(実行コード)は一切変更しないこと。
11 2. Docstringは日本語で出力すること。
12 3. 出力は、既存のコードにDocstringを挿入した「完成版のコード全体」として出力すること。
13 4. 各関数・クラスの冒頭に、適切な \"\"\"Docstring\"\"\" を追加すること。
14
15【Docstringの構成要素】
16 - 概要: 1行で何をする関数か記述。
17 - 詳細説明: 必要に応じて動作の詳細を記述。
18 - 引数 (Parameters): :param name: 形式で型と説明を記述。
19 - 戻り値 (Returns): :returns: 形式で型と説明を記述。
20 - 関連リンク: モジュールの冒頭に、別のドキュメントへのリンク(例: :doc:`{{base_name}}_usage`)を含めること。
21
22【対象ソースコード: {{script_name}}】
23{{code}}
24"""
make_sphinx_files.py (プログラム)
make_sphinx_files.py
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3"""
4Sphinxドキュメント自動生成スクリプト。
5
6指定されたPythonスクリプトファイルに対して、Docstringの追加、プログラム解説の生成、
7Sphinx用の各種RST/MDファイルの作成、および関連するファイルのバックアップと置換を自動化します。
8`add_docstring.py` と `explain_program5.py` を利用して、AIによるドキュメント生成を行います。
9
10:doc:`make_sphinx_files_usage`
11"""
12
13import os
14import sys
15import argparse
16import shutil
17from glob import glob
18import subprocess
19import traceback
20from pathlib import Path
21from datetime import datetime
22
23
24SCRIPT_FULLPATH = os.path.abspath(sys.argv[0])
25SCRIPT_DIR = os.path.dirname(SCRIPT_FULLPATH)
26SCRIPT_BASENAME = os.path.splitext(os.path.basename(SCRIPT_FULLPATH))[0]
27
28# 起動スクリプトのディレクトリに基づいたパス設定
29ADD_DOCSTRING_PATH = os.path.join(SCRIPT_DIR, "add_docstring.py")
30EXPLAIN_PROGRAM_PATH = os.path.join(SCRIPT_DIR, "explain_program5.py")
31DEFAULT_INI_PATH = os.path.join(SCRIPT_DIR, f"{SCRIPT_BASENAME}.ini")
32
33
34def relative_from_source(argv0: str) -> str:
35 """
36 ファイルパスから'source'ディレクトリ以下の相対パスを取得する。
37
38 指定されたファイルパスを解決し、そのパス中に 'source' ディレクトリがあれば、
39 その直下からの相対パス(ファイル名を除いたディレクトリ部分)を返す。
40 'source' が見つからない場合は空文字列を返す。
41
42 :param argv0: プログラムのパス。通常 `sys.argv[0]` が渡される。
43 :returns: 'source' ディレクトリ以下の相対パスの文字列。
44 """
45 p = Path(argv0).resolve()
46 parts = p.parts
47
48 # 最初に出現する "source" を探す
49 try:
50 idx = parts.index("source")
51 except ValueError:
52# raise RuntimeError("'source' ディレクトリがパスに含まれていません")
53 return ""
54
55 # source 以下のパス(ファイル名付き)
56 rel_path = Path(*parts[idx+1:])
57
58 # ファイル名を削除してディレクトリだけにする
59 return str(rel_path.parent)
60
61
62def run_step(message, cmd_list):
63 """
64 作業ステップを表示し、外部コマンドを実行します。
65
66 指定されたメッセージを表示した後、`subprocess.run` を使用してコマンドリストを実行します。
67 コマンドの実行結果が成功(終了コード0)であれば `True` を返し、
68 エラーが発生した場合はエラーメッセージを表示して `False` を返します。
69
70 :param message: 実行するステップのメッセージ。
71 :param cmd_list: 実行するコマンドとその引数を要素とするリスト。
72 :returns: コマンドが正常に実行された場合は `True`、それ以外は `False`。
73 """
74 print(f"\n>>> {message}")
75 print(f" コマンド: {' '.join(cmd_list)}")
76 try:
77 result = subprocess.run(cmd_list, text=True, errors='ignore')
78# result = subprocess.run(cmd_list, capture_output=True, text=True, encoding='utf-8', errors='ignore')
79 if result.returncode != 0:
80 print(f"!!! エラーが発生しました:\n{result.stderr}")
81 return False
82 return True
83 except Exception as e:
84 print(f"!!! 実行エラー: {e}")
85 return False
86
87def make_init_py(path):
88 """
89 指定されたパスに `__init__.py` ファイルを作成します。
90
91 既にファイルが存在する場合はその旨を表示し、存在しない場合は空の `__init__.py` ファイルを作成します。
92 これはPythonパッケージとして認識させるために必要です。
93
94 :param path: `__init__.py` を作成するパス。
95 """
96 if os.path.exists(path):
97 print(f">>> Step: {path} が見つかりました。")
98 else:
99 print(f">>> Step: {path} を作成中...")
100 with open(path, "w", encoding="utf-8") as f:
101 f.write("")
102 print(f" Done: {path}")
103
104
105def make_index_template(path, module_path):
106 """
107 `index.template` ファイルを作成または更新します。
108
109 プロジェクト全体のSphinxインデックスファイルとなる `index.template` を作成または追記します。
110 ファイルが存在しない場合は新規作成し、基本的なtoctree構造を含みます。
111 存在する場合は、指定された `module_path` をtoctreeに追加します。
112
113 :param path: `index.template` ファイルのパス。
114 :param module_path: Sphinxドキュメントのインデックスに追加するモジュールパス。
115 """
116 if os.path.exists(path):
117 print(f">>> Step: {path} に追加中...")
118 with open(path, "a", encoding="utf-8") as f:
119 f.write(f" {module_path}_index\n")
120 print(f" Done: {path}")
121 else:
122 print(f">>> Step: {path} を作成中...")
123 content = f"""\
124プロジェクト全体ドキュメント
125============================
126
127.. toctree::
128 :maxdepth: 2
129 :caption: メインメニュー:
130 :hidden:
131 :glob:
132
133 {module_path}_index
134"""
135 with open(path, "w", encoding="utf-8") as f:
136 f.write(content)
137 print(f" Done: {path}")
138
139def make_index_rst(index_rst, base_name, package_path):
140 """
141 Sphinxのモジュール別インデックス (`_index.rst`) ファイルを作成します。
142
143 指定された `base_name` と `package_path` を使用して、
144 モジュールごとのドキュメントのトップページとなる `.rst` ファイルを作成します。
145 このファイルには、usage, examples, apiへのリンクを含むtoctreeが定義されます。
146
147 :param index_rst: 作成する `.rst` ファイルのパス。
148 :param base_name: ベースとなるファイル名(拡張子なし)。
149 :param package_path: Pythonパッケージのフルパス。
150 """
151 print(f">>> Step: {index_rst} を作成中...")
152 index_content = f"""{base_name} ドキュメント
153============================================================================
154
155.. toctree::
156 :maxdepth: 1
157
158 {base_name}_usage
159 {base_name}_examples
160 {base_name}_api
161"""
162 with open(index_rst, "w", encoding="utf-8") as f:
163 f.write(index_content)
164 print(f" Done: {index_rst}")
165
166def make_api_rst(api_rst, base_name, package_path):
167 """
168 SphinxのAPIドキュメント (`_api.rst`) ファイルを作成します。
169
170 指定された `base_name` と `package_path` を使用して、
171 PythonモジュールのAPIリファレンスとなる `.rst` ファイルを作成します。
172 `automodule` ディレクティブを用いて、自動的にメンバー、非公開メンバー、継承情報を抽出する設定を行います。
173
174 :param api_rst: 作成するAPI `.rst` ファイルのパス。
175 :param base_name: ベースとなるファイル名(拡張子なし)。
176 :param package_path: Pythonパッケージのフルパス。
177 """
178 print(f">>> Step: {api_rst} を作成中...")
179 api_content = f"""{base_name} プログラム仕様
180============================================================================
181
182.. currentmodule:: {package_path}
183
184.. automodule:: {package_path}
185 :members:
186 :undoc-members:
187 :show-inheritance:
188"""
189 with open(api_rst, "w", encoding="utf-8") as f:
190 f.write(api_content)
191 print(f" Done: {api_rst}")
192
193def make_examples_md(examples_md, infile, base_name):
194 """
195 実行例を示すMarkdownファイル (`_examples.md`) を作成します。
196
197 指定された `infile` に対して `--help` オプションを実行してヘルプ出力を取得し、
198 現在のディレクトリ内の関連する画像ファイルやデータファイルを検出し、
199 それらを組み込んだMarkdown形式の実行例ファイルを作成します。
200
201 :param examples_md: 作成する実行例Markdownファイルのパス。
202 :param infile: ヘルプ出力を取得する対象のPythonスクリプトファイル。
203 :param base_name: ベースとなるファイル名(拡張子なし)。
204 """
205 print(f">>> Step: {examples_md} テンプレートを作成中...")
206
207# 画像ファイルの自動検出
208 image_files = sorted(
209 f for f in os.listdir(".")
210 if f.startswith(base_name) and f.lower().endswith((".png", ".jpg", ".jpeg"))
211 )
212
213# データファイルの自動検出(CSV / Excel / TXT)
214 data_files = sorted(
215 f for f in os.listdir(".")
216 if f.startswith(base_name) and f.lower().endswith((".csv", ".xlsx", ".xls", ".txt"))
217 )
218
219 print("Image files:", image_files)
220 print("Data files:", data_files)
221
222
223 # ヘルプ出力を取得
224 print(" help logを取得します")
225 result = subprocess.run(["python", infile, "--help"],
226 text=True, capture_output = True)
227 print(" return code:", result.returncode)
228 if result.returncode == 0:
229 help_log = result.stdout + "\n" + result.stderr
230# print(" help log:", help_log)
231 else:
232 help_log = "(ヘルプの自動取得に失敗しました。ここに実行ログを貼り付けてください)"
233
234 if data_files:
235 data_section = "## データファイル\n"
236 for df in data_files:
237 data_section += f"- [{df}](./{df})\n"
238 data_section += "\n"
239 else:
240 data_section = "## 生成されたデータファイル\n(データファイルが見つかりませんでした)\n\n"
241
242 if image_files:
243 image_section = "## 画像ファイル\n\n"
244 for img in image_files:
245 image_section += f"- [{img}](./{img})\n"
246 image_section += f"\n\n"
247 else:
248 image_section = "## 生成された画像一覧\n(画像ファイルが見つかりませんでした)\n\n"
249
250
251 examples_content = f"""# {base_name} 実行例
252
253## help出力 `{base_name}.py --help`
254
255<pre style="background-color: #f4f4f4; border: 1px solid #ccc; padding: 10px; border-radius: 5px; font-family: 'Courier New', Courier, monospace; overflow-x: auto;">
256{help_log}
257</pre>
258
259{data_section}
260
261{image_section}
262
263"""
264
265 with open(examples_md, "w", encoding="utf-8") as f:
266 f.write(examples_content)
267 print(f" Done: {examples_md}")
268
269
270def main(args):
271 """
272 Sphinxドキュメント生成の主要な処理を実行します。
273
274 入力ファイルから基本情報を抽出し、`__init__.py`, `index.template`,
275 `_index.rst`, `_api.rst`, `_examples.md` といったSphinx関連ファイルを生成します。
276 その後、`explain_program5.py` と `add_docstring.py` を実行して、
277 プログラム解説とDocstringを生成・追加し、最後に元のファイルをバックアップし、
278 Docstringが追加されたファイルで置き換えます。
279
280 :param args: コマンドライン引数を格納した `argparse.Namespace` オブジェクト。
281 """
282 infile = args.infile
283 if not os.path.exists(infile):
284 print(f"エラー: 入力ファイル '{infile}' が見つかりません。")
285 return
286
287 # 基本情報の整理
288 base_name = os.path.splitext(os.path.basename(infile))[0]
289 date_str = datetime.now().strftime("%Y%m%d")
290
291 # ファイル名定義
292 init_py = "__init__.py"
293 index_template = "index.template"
294 docstring_out = f"{base_name}_docstring.py"
295 backup_file = f"{base_name}_{date_str}.py"
296 usage_md = f"{base_name}_usage.md"
297 examples_md = f"{base_name}_examples.md"
298 index_rst = f"{base_name}_index.rst"
299 api_rst = f"{base_name}_api.rst"
300
301 # カレントディレクトリ名からパッケージパスを判定 (010...等の数値ディレクトリを想定)
302 current_dir_name = os.path.basename(os.getcwd())
303 # フォルダ名が 010xx_page のような形式ならモジュールパスに含める
304# module_path = f"{current_dir_name}.{base_name}" if "page" in current_dir_name else base_name
305 if args.subdir is not None and args.subdir != "":
306 module_path = os.path.join(args.subdir, base_name)
307 package_path = f"{args.subdir}.{base_name}"
308 else:
309 package_path = module_path
310 module_path = base_name
311
312 print("="*60)
313 print(f" プロジェクト: {base_name} のSphinxファイル自動生成を開始します")
314 print(f" モジュールパス: {module_path}")
315 print(f" パッケージパス: {package_path}")
316 print("="*60)
317
318 make_init_py(init_py)
319 make_index_template(index_template, module_path)
320 make_index_rst(index_rst, base_name, package_path)
321 make_api_rst(api_rst, base_name, package_path)
322 make_examples_md(examples_md, infile, base_name)
323
324 args_list = ["--api", args.api, "--update", str(args.update), "--overwrite", str(args.overwrite),
325 "--pause", str(args.pause)]
326
327 # explain_program.py の実行
328 if not run_step("Step: EXPLAIN_PROGRAM_PATH を実行してプログラム解説を生成中...",
329 ["python", EXPLAIN_PROGRAM_PATH, infile, *args_list]):
330 return
331
332 # add_docstring.py の実行
333 if not run_step("Step: ADD_DOCSTRING_PATH を実行してDocstringを追加中...",
334 ["python", ADD_DOCSTRING_PATH, infile, *args_list]):
335 return
336
337#======================================================================
338 # バックアップの作成
339 print(f">>> Step: オリジナルファイルのバックアップを作成中...")
340 if os.path.exists(infile):
341 shutil.copy2(infile, backup_file)
342 print(f" Done: {infile} -> {backup_file}")
343 else:
344 print("!!! バックアップ対象のファイルが見つかりません。")
345 return
346
347#======================================================================
348 # ファイルの置き換え
349 print(f">>> Step: 生成されたDocstring版ファイルを {infile} にリネーム中...")
350 if os.path.exists(docstring_out):
351 os.replace(docstring_out, infile)
352 print(f" Done: {docstring_out} -> {infile}")
353 with open(docstring_out, "w") as fp:
354 fp.write("")
355 print(f" ダミーの空ファイル {docstring_out} を作りました")
356 else:
357 print("!!! Docstring版ファイルが生成されていなかったため、リネームをスキップします。")
358 return
359
360
361 print("\n" + "="*60)
362 print(" 全ての自動生成プロセスが正常に終了しました。")
363 print("="*60)
364
365
366def initialize():
367 """
368 コマンドライン引数パーサーを初期化し、設定します。
369
370 `argparse.ArgumentParser` オブジェクトを生成し、スクリプトの説明、
371 必要な引数 (`infile`)、およびオプション引数 (`subdir`, `api`, `update`, `overwrite`, `pause`) を定義します。
372
373 :returns: 設定済みの `ArgumentParser` オブジェクト。
374 """
375 parser = argparse.ArgumentParser(
376 description="Sphinxドキュメント生成の一連のルーチン(Docstring追加、バックアップ、解説生成、RST作成)を自動化します。"
377 )
378 parser.add_argument("infile", help="対象となるPythonスクリプトファイル名 (例: mu_fit.py)")
379 parser.add_argument("--subdir", default=None, help="sourceディレクトリからの相対パス")
380
381 parser.add_argument("--api", choices=["openai", "openai5", "google", "gemini"], default='google')
382 parser.add_argument("-u", "--update", type=int, default=1)
383 parser.add_argument("-w", "--overwrite", type=int, default=0)
384 parser.add_argument("-p", "--pause", type=int, default=0)
385 return parser
386
387
388def read_args(parser):
389 """
390 コマンドライン引数を解析し、整形して返します。
391
392 与えられた `ArgumentParser` を使用してコマンドライン引数を解析します。
393 `subdir` が指定されていない場合は `relative_from_source` 関数を使って自動的に設定します。
394 解析された引数を表示し、`argparse.Namespace` オブジェクトとして返します。
395
396 :param parser: `argparse.ArgumentParser` オブジェクト。
397 :returns: 解析された引数を格納する `argparse.Namespace` オブジェクト。
398 """
399 args = parser.parse_args()
400 if args.subdir is None:
401 args.subdir = relative_from_source(args.infile)
402
403 print()
404 print("Args:")
405 print(f" {args.infile=}")
406 print(f" {args.subdir=}")
407 print(f" {args.api=}")
408 print(f" {args.update=}")
409 print(f" {args.overwrite=}")
410 print(f" {args.pause=}")
411
412 return args
413
414
415if __name__ == "__main__":
416 print()
417 print(f"=== {sys.argv[0]} ===")
418 print(f"{EXPLAIN_PROGRAM_PATH=}")
419 print(f"{ADD_DOCSTRING_PATH=}")
420
421 parser = initialize()
422 args = read_args(parser)
423
424 try:
425 main(args)
426 except Exception:
427 print("\n" + "!"*60)
428 print(" 予期せぬ致命的なエラーが発生しました。")
429 traceback.print_exc()
430 print("!"*60)
431 sys.exit(1)
save_clipboard.py (プログラム)
save_clipboard.py
1"""
2クリップボードの内容(テキストまたは画像)を取得し、ファイルに保存するスクリプト。
3
4このスクリプトはWindows API (User32, Ole32, Kernel32) を使用してクリップボードデータを処理し、
5PIL (Pillow) ライブラリを使って画像を保存します。
6テキストデータはUTF-8エンコーディングで保存され、DIB (Device Independent Bitmap) 形式の画像はPNG形式で保存されます。
7ファイル保存後、reStructuredText (rst) 形式でそのファイルを参照するためのスニペットを標準出力に表示します。
8
9Usage:
10 python save_clipboard.py [出力ファイル名のベース (拡張子なし)]
11
12例:
13 # クリップボードの内容を 'my_output.txt' または 'my_output.png' として保存
14 python save_clipboard.py my_output
15 # 引数なしの場合、'clipboard_output.txt' または 'clipboard_output.png' として保存
16 python save_clipboard.py
17
18:doc:`save_clipboard_usage`
19"""
20import ctypes
21import ctypes.wintypes as wintypes
22from ctypes import POINTER, byref, windll, c_void_p
23from PIL import Image
24import io
25import struct
26import sys
27import os
28
29# Windows APIの準備
30ole32 = windll.ole32
31kernel32 = windll.kernel32
32user32 = windll.user32 # テキスト取得用に追加
33
34# --- 64bitアドレスを正しく扱うための型定義 ---
35kernel32.GlobalLock.argtypes = [c_void_p]
36kernel32.GlobalLock.restype = c_void_p
37kernel32.GlobalUnlock.argtypes = [c_void_p]
38kernel32.GlobalSize.argtypes = [c_void_p]
39kernel32.GlobalSize.restype = ctypes.c_size_t
40
41# テキスト取得用API定義
42user32.OpenClipboard.argtypes = [wintypes.HWND]
43user32.OpenClipboard.restype = wintypes.BOOL
44user32.CloseClipboard.restype = wintypes.BOOL
45user32.GetClipboardData.argtypes = [wintypes.UINT]
46user32.GetClipboardData.restype = wintypes.HANDLE
47user32.IsClipboardFormatAvailable.argtypes = [wintypes.UINT]
48user32.IsClipboardFormatAvailable.restype = wintypes.BOOL
49
50# 定数定義
51TYMED_HGLOBAL = 1
52DVASPECT_CONTENT = 1
53CF_DIB = 8
54CF_UNICODETEXT = 13 # WindowsのUnicodeテキスト形式
55GMEM_MOVEABLE = 0x0002
56
57class FORMATETC(ctypes.Structure):
58 """
59 OLEデータ転送で使用される形式記述構造体。
60
61 クリップボードやOLEオブジェクトからのデータ取得時に、どのような形式
62 (クリップボード形式、メディアタイプ、表示側面など)のデータを要求するかを指定します。
63 """
64 _fields_ = [
65 ("cfFormat", wintypes.WORD),
66 ("ptd", c_void_p),
67 ("dwAspect", wintypes.DWORD),
68 ("lindex", wintypes.LONG),
69 ("tymed", wintypes.DWORD),
70 ]
71
72class STGMEDIUM(ctypes.Structure):
73 """
74 OLEデータ転送で使用されるストレージメディア構造体。
75
76 クリップボードやOLEオブジェクトからのデータ転送時に、実際のデータ
77 (ハンドル、ポインタ、ストリームなど)とそのタイプを保持します。
78 `u` メンバは、`tymed` の値に応じてHGLOBALハンドルなどを格納するために `c_void_p` として定義されています。
79 """
80 _fields_ = [
81 ("tymed", wintypes.DWORD),
82 ("u", c_void_p), # data handle (HGLOBAL)
83 ("pUnkForRelease", c_void_p),
84 ]
85
86class IDataObject(ctypes.Structure):
87 """
88 OLEデータ転送インターフェースの抽象基底クラス。
89
90 クリップボードやドラッグ&ドロップ操作など、データ転送オブジェクトが提供するデータを
91 管理するためのCOMインターフェースです。主に `GetData` メソッドを通じてデータを取得します。
92 """
93 pass
94IDataObject._fields_ = [("lpVtbl", POINTER(c_void_p))]
95
96def get_clipboard_text():
97 """
98 クリップボードからUnicodeテキストを取得します。
99
100 User32.dllのOpenClipboard, GetClipboardData, GlobalLock, GlobalUnlock, CloseClipboard関数を使用します。
101 CF_UNICODETEXT形式のデータを取得し、Pythonの文字列として返します。
102 クリップボードがロックされている場合や、テキストデータが見つからない場合はNoneを返します。
103
104 :returns: クリップボード上のテキストデータ (`str`)、またはNone。
105 :rtype: str | None
106 """
107 if not user32.OpenClipboard(None):
108 return None
109
110 try:
111 if not user32.IsClipboardFormatAvailable(CF_UNICODETEXT):
112 return None
113
114 h_global = user32.GetClipboardData(CF_UNICODETEXT)
115 if not h_global:
116 return None
117
118 ptr = kernel32.GlobalLock(c_void_p(h_global))
119 if not ptr:
120 return None
121
122 try:
123 # Unicode (UTF-16) としてデータを読み込む
124 text = ctypes.c_wchar_p(ptr).value
125 return text
126 finally:
127 kernel32.GlobalUnlock(c_void_p(h_global))
128 finally:
129 user32.CloseClipboard()
130
131def get_clipboard_image_fixed():
132 """
133 クリップボードからDIB (Device Independent Bitmap) 形式の画像を取得し、PIL Imageオブジェクトとして返します。
134
135 Ole32.dllのOleGetClipboardとReleaseStgMedium関数、およびIDataObjectインターフェースのGetDataメソッドを使用します。
136 取得したDIBデータは、BMPファイルヘッダを追加してPIL (Pillow) で読み込み可能な形式に変換されます。
137 この関数を呼び出す前に `ole32.OleInitialize(None)` が呼び出されている必要があります。
138
139 :returns: クリップボード上の画像データ (`PIL.Image.Image`)、またはNone。
140 :rtype: PIL.Image.Image | None
141 """
142 # 既にOleInitializeされている前提、または明示的に呼ぶ
143 # ole32.OleInitialize(None) # メイン側で制御
144
145 data_obj_ptr = POINTER(IDataObject)()
146 hr = ole32.OleGetClipboard(byref(data_obj_ptr))
147 if hr != 0:
148 return None
149
150 try:
151 # IDataObject::GetData のVtableインデックスは通常3
152 get_data_addr = data_obj_ptr.contents.lpVtbl[3]
153
154 GetDataProto = ctypes.WINFUNCTYPE(
155 ctypes.HRESULT,
156 POINTER(IDataObject),
157 POINTER(FORMATETC),
158 POINTER(STGMEDIUM)
159 )
160 get_data_func = GetDataProto(get_data_addr)
161 except (IndexError, AttributeError):
162 return None
163
164 fmt = FORMATETC()
165 fmt.cfFormat = CF_DIB
166 fmt.ptd = None
167 fmt.dwAspect = DVASPECT_CONTENT
168 fmt.lindex = -1
169 fmt.tymed = TYMED_HGLOBAL
170
171 stg = STGMEDIUM()
172 hr = get_data_func(data_obj_ptr, byref(fmt), byref(stg))
173
174 img = None
175 if hr == 0 and stg.tymed == TYMED_HGLOBAL:
176 h_global = stg.u
177 ptr = kernel32.GlobalLock(c_void_p(h_global))
178 size = kernel32.GlobalSize(c_void_p(h_global))
179
180 if ptr:
181 try:
182 dib_data = ctypes.string_at(ptr, size)
183 if len(dib_data) >= 40:
184 header_size = struct.unpack("<I", dib_data[0:4])[0]
185 offset_to_pixels = 14 + header_size
186 file_size = 14 + len(dib_data)
187
188 bmp_header = struct.pack("<2sIHHI", b"BM", file_size, 0, 0, offset_to_pixels)
189 img = Image.open(io.BytesIO(bmp_header + dib_data))
190 img.load() # メモリ上にデータをロード
191 finally:
192 kernel32.GlobalUnlock(c_void_p(h_global))
193 ole32.ReleaseStgMedium(byref(stg))
194
195 return img
196
197def print_rst_snippets_for_image(filepath):
198 """
199 指定された画像ファイル用のreStructuredText (rst) スニペットを表示します。
200
201 ファイルリンク、`image` ディレクティブ、`figure` ディレクティブの例を生成し、
202 標準出力に出力します。これはSphinxなどでドキュメントを作成する際に役立ちます。
203
204 :param filepath: スニペットを生成する画像ファイルのパス。
205 :type filepath: str
206 :returns: なし
207 :rtype: None
208 """
209 filename = os.path.basename(filepath)
210 rel_path = "./" + filename # 相対パスと仮定
211
212 print("\n" + "="*60)
213 print(f"--- reStructuredText (rst) 用表示コード ({filename}) ---")
214 print("="*60)
215
216 print("\n1. シンプルなファイルリンク:")
217 print(f"`{filename} <{rel_path}>`_")
218
219 print("\n2. 画像表示 (imageディレクティブ):")
220 print(f".. image:: {rel_path}\n :alt: {filename}")
221
222 print("\n3. 図表表示 (figureディレクティブ - キャプション付き):")
223 print(f".. figure:: {rel_path}\n :alt: {filename}\n :align: center\n\n {filename} の図面")
224 print("="*60 + "\n")
225
226def print_rst_snippets_for_text(filepath):
227 """
228 指定されたテキストファイル用のreStructuredText (rst) スニペットを表示します。
229
230 `dropdown` (sphinx-design拡張機能が必要)、`parsed-literal`、`literalinclude`
231 ディレクティブの例を生成し、標準出力に出力します。
232 言語ヒントはファイル拡張子から推測されます。
233
234 :param filepath: スニペットを生成するテキストファイルのパス。
235 :type filepath: str
236 :returns: なし
237 :rtype: None
238 """
239 filename = os.path.basename(filepath)
240 rel_path = "./" + filename # 相対パスと仮定
241
242 # ファイル拡張子から言語を推測(簡易版)
243 _, ext = os.path.splitext(filename)
244 lang_map = {'.py': 'python', '.js': 'javascript', '.bat': 'bat', '.sh': 'bash', '.cpp': 'cpp', '.h': 'cpp', '.txt': 'text'}
245 language = lang_map.get(ext.lower(), 'text')
246
247 print("\n" + "="*60)
248 print(f"--- reStructuredText (rst) 用表示コード ({filename}) ---")
249 print("="*60)
250
251 print("\n1. 折り畳み表示 (sphinx-design dropdown) ※要拡張機能:")
252 print(f".. dropdown:: {filename} の内容を表示(クリックで展開)\n :color: info\n :icon: file-code\n")
253 print(f" .. literalinclude:: {rel_path}\n :language: {language}\n :linenos:")
254
255 print("\n2. <pre>タグ相当での埋め込み (parsed-literal):")
256 print(".. parsed-literal::\n")
257 try:
258 with open(filepath, "r", encoding="utf-8") as f:
259 # 最初の数行だけサンプルとして表示
260 lines = f.readlines()
261 for line in lines[:5]:
262 print(f" {line.rstrip()}")
263 if len(lines) > 5:
264 print(" ...")
265 except Exception:
266 print(" (ファイル内容の読み込みに失敗しました)")
267
268 print("\n3. 通常の外部ファイル取り込み (literalinclude):")
269 print(f".. literalinclude:: {rel_path}\n :language: {language}")
270
271 print("="*60 + "\n")
272
273if __name__ == "__main__":
274 # OleInitializeはプロセス内で1回呼ぶ必要がある(OLE API使用のため)
275 ole32.OleInitialize(None)
276
277 # デフォルトのファイル名ベース
278 base_filename = "clipboard_output"
279
280 # 引数がある場合はそれをファイル名にする
281 if len(sys.argv) > 1:
282 # 安全なファイル名にするための簡易処理(パス区切り文字などを除去)
283 requested_name = sys.argv[1]
284 base_filename = os.path.splitext(os.path.basename(requested_name))[0]
285
286 try:
287 print(f"--- クリップボードの内容を確認中... ---")
288
289 # 1. まずテキストがあるか確認(優先度が高い場合が多い)
290 text_content = get_clipboard_text()
291 if text_content is not None:
292 print("[情報] クリップボード内にテキストが見つかりました。")
293
294 # テキストファイルとして保存
295 out_name = f"{base_filename}.txt"
296 # Windowsの改行コード(CRLF)で保存
297 with open(out_name, "w", encoding="utf-8", newline='\r\n') as f:
298 f.write(text_content)
299
300 print(f"成功: テキストを '{out_name}' として保存しました (UTF-8)。")
301 print(f"文字数: {len(text_content)}")
302
303 # rstスニペット表示
304 print_rst_snippets_for_text(out_name)
305 sys.exit(0)
306
307 # 2. 次に画像があるか確認
308 img = get_clipboard_image_fixed()
309 if img:
310 print("[情報] クリップボード内に画像(DIB)が見つかりました。")
311
312 # 画像ファイルとして保存 (PNG推奨)
313 out_name = f"{base_filename}.png"
314 img.save(out_name, "PNG")
315
316 print(f"成功: 画像を '{out_name}' として保存しました。")
317 print(f"画像サイズ: {img.size[0]}x{img.size[1]}, モード: {img.mode}")
318
319 # rstスニペット表示
320 print_rst_snippets_for_image(out_name)
321 sys.exit(0)
322
323 # 3. どちらもない場合
324 print("[情報] クリップボードにテキストまたは画像(CF_DIB)は見つかりませんでした。")
325 # 形式リストを取得して表示(デバッグ用)
326 if user32.OpenClipboard(None):
327 try:
328 formats = []
329 fnum = user32.EnumClipboardFormats(0)
330 while fnum:
331 formats.append(fnum)
332 fnum = user32.EnumClipboardFormats(fnum)
333 if formats:
334 print(f"利用可能な形式ID: {formats}")
335 finally:
336 user32.CloseClipboard()
337
338 except Exception as e:
339 import traceback
340 print(f"\n[エラー] 予期せぬエラーが発生しました。")
341 traceback.print_exc()
342 finally:
343 # OleUninitializeはプロセス終了時に呼ぶ
344 ole32.OleUninitialize()
Sphinxマニュアル作成支援メニュー: