"""
指定されたディレクトリ以下のファイルを検索し、コンテンツをMarkdown形式で結合するスクリプトです。
複数のファイルを一つの、または複数のMarkdownファイルに結合し、
各ファイルのコンテンツをMarkdownのセクションとして追加します。
指定されたバイト数を超えると、新しいMarkdownファイルが自動的に作成されます。
関連リンク: :doc:`programs2single_md_usage`
"""
import os
import fnmatch
import argparse
[ドキュメント]
def find_output_filename(template, idx):
"""
指定されたテンプレートとインデックスに基づき、未使用の出力ファイルパスを生成します。
生成されたファイルパスが既に存在する場合は、インデックスを増やして新しいパスを生成し直します。
ファイルが存在しないパスが見つかるまでこの処理を繰り返します。
:param template: 出力ファイル名のテンプレート文字列(例: "output_{:04d}.md")。
インデックスは {:04d} のようにフォーマットされます。
:type template: str
:param idx: 開始インデックス。
:type idx: int
:returns: 未使用の出力ファイルパスと、次に使用すべきインデックス。
:rtype: tuple[str, int]
"""
while True:
output_path = template.format(idx)
if os.path.exists(output_path):
idx += 1
continue
break
return output_path, idx + 1
[ドキュメント]
def create_markdown_from_files(root_dir, filemasks, max_bytes, prefix):
"""
指定された条件でファイルを検索し、その内容をMarkdownファイルに保存します。
検索は指定されたルートディレクトリから開始され、指定されたファイルマスクに一致するファイルのみが対象となります。
各ファイルのコンテンツはMarkdown形式で整形され、指定された最大バイト数を超えないように複数の出力ファイルに分割されることがあります。
:param root_dir: 検索を開始するルートディレクトリのパス。
:type root_dir: str
:param filemasks: ファイルマスクのセミコロン区切りリスト (例: "*.py;*.js")。
これらのマスクのいずれかに一致するファイルが処理対象となります。
:type filemasks: str
:param max_bytes: 各Markdownファイルの最大バイト数。この値を超えると新しいMarkdownファイルが作成されます。
:type max_bytes: int
:param prefix: 出力ファイルのベース名。出力ファイルは "{prefix}_{0001}.md" のようになります。
:type prefix: str
:returns: None
:rtype: None
"""
# 出力ファイル名とインデックスを初期化
output_filename_template = prefix + "_{:04d}.md"
file_index = 1
current_file_size = 0
output_path, file_index = find_output_filename(output_filename_template, file_index)
def filematch(filename, filemasks):
"""
指定されたファイル名が、セミコロン区切りのファイルマスクのいずれかに一致するかどうかを判定します。
:param filename: 検査対象のファイル名。
:type filename: str
:param filemasks: セミコロンで区切られたファイルマスクの文字列(例: "*.py;*.txt")。
:type filemasks: str
:returns: ファイル名がマスクのいずれかに一致する場合は True、そうでない場合は False。
:rtype: bool
"""
masks = filemasks.split(';')
for fmask in masks:
if fnmatch.fnmatch(filename, fmask):
return True
return False
# 書き込みモード
output_file = open(output_path, "a", encoding="utf-8")
try:
# ディレクトリツリーを巡回
for dirpath, _, filenames in os.walk(root_dir):
for filename in filenames:
if filematch(filename, filemasks):
full_path = os.path.join(dirpath, filename)
try:
with open(full_path, 'r', encoding='utf-8') as f:
content = f.read()
# Markdown形式のコンテンツを作成
markdown_content = f"### {full_path}\n\n#=======\n{content}\n#=======\n\n"
markdown_bytes = markdown_content.encode('utf-8')
# ファイルサイズが最大バイト数を超えた場合、新しいファイルを作成
if current_file_size + len(markdown_bytes) > max_bytes:
output_file.close()
output_path, file_index = find_output_filename(output_filename_template, file_index)
output_file = open(output_path, "a", encoding="utf-8")
current_file_size = 0
# Markdownファイルを書き込み
output_file.write(markdown_content)
current_file_size += len(markdown_bytes)
print(f"Added {full_path} to {output_path}")
except UnicodeDecodeError:
print(f"Skipped {full_path} due to encoding error.")
finally:
# 最後に開いているファイルをクローズ
if output_file and not output_file.closed:
output_file.close()
[ドキュメント]
def main():
"""
コマンドライン引数を解析し、ファイルからMarkdownを生成するメイン処理を実行します。
この関数は、`argparse`モジュールを使用してコマンドライン引数を定義および解析し、
`create_markdown_from_files`関数を適切な引数で呼び出します。
:returns: None
:rtype: None
"""
# ArgumentParserオブジェクトを作成
parser = argparse.ArgumentParser(description="指定されたディレクトリ以下のファイルをMarkdownに保存します。")
# 引数を追加
parser.add_argument("root_dir", type=str, default=".", help="検索を開始するルートディレクトリ")
parser.add_argument("filemasks", type=str, default="*.py", help="ファイルマスク(例: *.py;*.p?)")
parser.add_argument("--max_bytes", type=int, default=20000000, help="各Markdownファイルの最大バイト数")
parser.add_argument("--prefix", type=str, default="output", help="出力ファイルのベース名")
# 引数を解析
args = parser.parse_args()
# メインの関数を呼び出し
create_markdown_from_files(args.root_dir, args.filemasks, args.max_bytes, args.prefix)
if __name__ == "__main__":
main()