今回の自作ツールは、「フォルダコピーツール」です。フォルダコピーツールは数多く存在しますが、拡張子の指定はもとより、指定したキーワードがフォルダ名やファイル名に含まれているものだけをコピー対象にすることができます。
また、フォルダコピー処理はクラス化されているため、コピペして再利用することが可能です。
既存のフォルダコピー機能では満足できない方、自分独自の条件でフォルダコピーしたい方は、是非本記事を活用してください。
自作ツールを作るためのポイントと今後公開予定の自作ツール一覧を「【実践】ちょっと便利なツールをPythonで作ろう!」で紹介していますので、併せてご覧ください。
フォルダコピーツールの紹介
コピー条件(コピー元、コピー先、拡張子、キーワード)を記述したテキストファイル(コピー条件ファイル)とコピー方法を指定し、「フォルダコピー」ボタンをクリックすれば、その条件に従ってフォルダをコピーします。
- コピー条件ファイルには、複数のコピー条件の記述が可能です。
- 動作確認スイッチをONにすることで、指定した条件でコピーされるファイルの確認が可能です。
- コピー対象の拡張子、コピー除外の拡張子の指定が可能です。
- ファイル名やフォルダ名に特定の文字列が含まれている場合のみコピー又は除外することが可能です。
- コピー方法として「差分コピー」「単純コピー」「更新日指定コピー」「現時点より〇〇以降のコピー」が選択可能です。
- 画面から入力した内容は保持され、次回起動時に復元されます(動作確認スイッチは除く)。
画面レイアウト
使い方
操作手順は次の通りです。
- コピー条件ファイルを指定し、内容を読み込んで表示する
- 表示された条件に問題ないことを確認する
- コピー方法を選択する
- 「フォルダコピー」ボタンをクリックする
実行中のコピー処理を中止したい場合は、コンソール画面にてキーボードからコントロール+Cを押してください。
コピー条件ファイルの読み込みと保存
「ファイルを選択ボタン」をクリックし、ファイルの選択ダイアログでコピー定義ファイルを選択すると、その内容が画面に表示され編集可能になります。
編集結果は「保存」ボタンでファイルに書き込むことができます。
直接手入力するかコピー条件ファイルをドラッグ&ドロップした場合はコピー定義ファイルの内容が自動で読み込まれないため、「読込」ボタンをクリックしてください。
選択コピー条件ファイルフォーマット
コピー条件ファイルには「識別名」、「コピー元」、「コピー」を必ず記述し、必要に応じて「階層」、「対象拡張子」、「除外拡張子」、「対象パターン」、「除外パターン」を記述します。
# 任意のコメント
@識別名
コピー元=コピー元フォルダ名
コピー先=コピー先フォルダ名
階層=True 又は False
対象拡張子=拡張子1;拡張子2;・・・
除外拡張子=拡張子1;拡張子2;・・・
対象パターン=パターン1;パターン2;・・・
除外パターン=パターン1;パターン2;・・・
#--------コメント----------
@識別名
コピー元=I:\gitHub\
コピー先=D:\My Documents\gitHub
階層=True
対象拡張子=*.xaml;*.cs
除外拡張子=*.bin;*.dll
対象パターン=%priject;%release%
除外パターン=%.i.g.cs;%.g.cs
識別名 | コピー条件の名前。複数記述する場合は重複しないように注意すること。 |
---|---|
コピー元 | コピー元のフォルダ名 |
コピー先 | コピー先のフォルダ名 |
階層 | Trueを指定すると階層ごとコピー、Falseを指定すると階層下はコピーしない |
対象拡張子 | コピー対象にしたい拡張子をセミコロン( ; ) で区切って記述 |
除外拡張子 | コピーしたくない拡張子をセミコロン( ; ) で区切って記述 |
対象パターン | 指定した文字列がフォルダまたはファイル名に含まれている場合、コピー対象となる。 前方一致(%文字列)後方一致(%文字列%)後方一致(文字列%)から指定可能。複数指定する場合はセミコロン( ; ) で区切る。 |
除外パターン | 指定した文字列がフォルダまたはファイル名に含まれている場合、コピー対象外とする。 除外パターンの指定方法は上記と同じ。 |
2つ以上の条件を指定する場合は次の様に記述します。
# -------- 条件1-----------
@フォルダコピー1
コピー元=I:\gitHub\
コピー先=D:\My Documents\gitHub
階層=True
対象拡張子=.xaml;.cs
# -------- 条件2-----------
@フォルダコピー2
コピー元=J:\source\
コピー先=D:\My Documents\source
対象パターン=%.i.g.cs
除外拡張子=*.bin;*.dll;*.txt
指定した条件でコピーされるファイルの確認
動作確認スイッチをONにして「フォルダコピー」をクリックすると、指定した条件によってコピーされるファイルの一覧が表示されます。
コピー方法の指定
コピー方法には次の4種類が指定可能です。
差分コピー | コピー元ファイルよりコピー先ファイルの更新日付が古いか、サイズが異なるファイルがコピー対象となります。 |
---|---|
単純コピー | コピー元ファイルとコピー先ファイルの更新日付、サイズに関係なく、無条件にコピーします。 |
更新日付が yyyy/mm/dd hh:mm:ss 以降のファイルを単純コピー | コピー元ファイルの更新日付が、yyyy/mm/dd hh:mm:ss で指定した日時以降のファイルがコピー対象となります。 |
更新日付が現時点より xx 以降のファイルを単純コピー | コピー元ファイルの更新日付が、現時点(=現在時刻)を起点にxx で指定した期間以降のファイルがコピー対象となります。 xxには秒/分/時/日/月/年 の指定が可能で、それぞれ s/m/h/d/M/y が記述できます。 例えば、xx に 1d を指定すると更新日付が1日前のファイルがコピー対象となり、3h を指定すると3時間前のファイルがコピー対象となります。 |
プログラムのダウンロードと動作環境の設定
Pythonのインストールが必要です。既に構築済みの方は読み飛ばしてください。プログラムを修正する場合は、VSCode Portable のインストールも必要です。
自作ツールのためのポータブルPython開発・実行環境を作ろう! | プログラム実行時に必要 |
WinPythonにVSCode Portable版を入れよう! | プログラム修正時に必要 |
Python 環境に下記のライブラリをインストールします。 command.bat を実行後、下記のコマンドを実行してください。
pip install customtkinter
pip install tkinterdnd2
pip install CTkMessagebox
下記のリンクからZipファイルをダウンロード&解凍します。次に、Python環境の任意の場所にフォルダを作成し、そこに解凍したファイルを丸ごとコピーしてください。
フォルダコピーツール
実行方法
次の手順で実行してください。
- WinPython のインストールフォルダ内にある command.bat を実行
- ダウンロードしたプログラムファイルをコピーした場所にカレントディレクトリを移動
- Python ui.py を実行
Python ui.py
下記の画面が表示されます。本記事の最初で説明した手順に従って操作してください。実行中の途中経過は随時コンソールに出力されます。
コピーが完了すると「コピーが終わりました」のメッセージがコンソールに出力されます。この時、コピーを失敗したファイルが存在すると、続けてエラー内容が出力されます。例えばコピー先に存在しないフォルダを指定した場合、次のような内容がコンソール出力されます。
フォルダコピーツールの設計
以下は、プログラム作成の際に使用を取りまとめた内容(=設計)です。単にフォルダコピーツールをダウンロードして使いたい方は無視してください。
DIYプログラミングの参考になる内容ですので、興味のある方は最後までお読みいただければと思います。
画面レイアウト
設計段階で考えた画面レイアウトです。当初はコピー条件の保存は考えてなかったのですが、ツール作成途中で保存機能を盛り込みました。また動作確認スイッチも当初は予定していませんでしたが、正しくファイルがコピーされるかどうかを確認する際に都合がよいので追加しました。
仕様
- コピー元とコピー先のフォルダを指定することでフォルダ内のファイルコピーが行えること
- コピー方法として次の4つが選択できること
①全てのファイルを置き換えてしまう「単純コピー」
②更新日付が古いかサイズが異なるファイルだけコピーする「差分コピー」
③更新日付が指定日時以降のファイルをコピーする
④更新日付が現在時刻からn日前以降ファイルをコピーする - コピー条件はテキストファイル(コピー条件ファイル)に記述できるようにすること
- 1つのコピー条件ファイルには、複数のフォルダに関するコピー条件が記述できること
- 指定した拡張子を持つファイルをコピー対象や除外対象にできること
- 指定した文字列がコピー元ファイルのフルパスに含まれる場合、それをコピー対象や除外対象にできること
- 指定した文字列がフルパスに含まれているかを指定する際、前方一致、後方一致、曖昧検索のいづれかが指定できること
- 画面に入力した値は、次回起動時に復元すること
実現方法
- 画面はCustomTkinter で作成
- ファイル一覧の取得は階層下まで遡ることが容易なglobライブラリを使う
- 拡張子による対象/除外の判断は、
- ファイルコピー処理の引数にコールバック関数が指定できるようにし、コールバック関数を差し替えることで様々なコピー方法が実現できるようにする
- 指定した文字列がフルパスに含まれているか判断する方法として、正規表現ライブラリ(re)を用いる
プログラムの構成
本プログラムは4つの py ファイルで構成しています。csv_spliter.py には split_file関数のみ記述していますが、これは他のプログラムで流用することを考慮しているからです。
モジュール名 | 役割 |
---|---|
ui.py | フォルダコピーツールのユーザインターフェース。 |
folder_copy.py | フォルダコピーするための処理クラス。 FolderCopy クラスの copy_filesメソッドがフォルダコピー処理を担っている。FolderCopyCondition クラスは差分コピーなどのコピー方法を担当している。 |
customtkintercontrols.py | ファイル選択やフォルダ選択、ドラッグ&ドロップを実現するためのクラス。 |
appconfig.py | 画面に入力された値をJson形式のファイルに保存/読込するためのクラス。 |
個々の処理手順
下図は、フォルダコピーボタンがクリックされた時に呼び出される処理( FolderCopyクラスの copy_files メソッド)の簡易的なフローチャートです。
ソースコード
ui.py
import os
from datetime import datetime
import customtkinter as ctk
from CTkMessagebox import CTkMessagebox
from customtkintercontrols import App
from customtkintercontrols import FileSelectionControl
from customtkintercontrols import EntryControl
from customtkintercontrols import TextBoxControl
from folder_copy import FolderCopy
from folder_copy import FolderCopyCondition
from appconfig import AppConfig
# -----------------------------------------------
# コールバック関数の定義
# -----------------------------------------------
# ウィンドウを閉じる時に呼ばれる関数
def close_window():
# 画面入力値を保存
conf.set("cond_file",cond_file.get())
conf.set("condition",condition.get())
conf.set("copy_mode",copy_mode.get())
conf.set("after_date",after_date.get())
conf.write()
# プログラムの終了
app.quit()
# 保存ボタンが押された時呼ばれる関数
def save_cond_file():
file = cond_file.get()
message = file
msg = CTkMessagebox(master=app,title="確認", message=message+" に編集内容を保存しますか?",icon="question", option_1="いいえ", option_2="はい")
response = msg.get()
if response == "はい":
if len(file.strip()) > 0:
cond = condition.get()
with open(file, 'w',encoding="shift-jis") as f:
f.write(cond)
# ファイルが選択された時に呼ばれる関数
def load_cond_file():
file = cond_file.get()
if os.path.exists(file):
with open(file,"r") as f:
condition.set(f.read())
# 変換ボタンが押された時に呼ばれる関数
def copy():
sections = analyze(condition.get())
cc = FolderCopyCondition()
fc = FolderCopy()
dbg = True if dbg_mode.get() == 1 else False
mode = copy_mode.get()
if mode == 0:
func = cc.updated()
elif mode == 1:
func = None
elif mode == 2:
func = cc.newer_than(newer_date.get())
elif mode == 3:
func = cc.modified_span(after_date.get())
else :
mode = None
errlist = []
print("コピーを開始します。")
for key,val in sections.items():
dic = val
print(dic)
include = dic.get("対象拡張子")
exclude = dic.get("除外拡張子")
inc_pttn = dic.get("対象パターン")
exc_pttn = dic.get("除外パターン")
res = fc.copy_files(dic["コピー元"],dic["コピー先"],bool(dic["階層"]),func,include,exclude,inc_pttn,exc_pttn,debug=dbg)
errlist += res
print("コピーが終わりました。")
if len(errlist) > 0:
print("次のファイルのコピーに失敗しました-----")
for err in errlist:
print(err)
print("------------------------------------")
# -----------------------------------------------
# コピー条件解析関数の定義
# -----------------------------------------------
def analyze(text):
lines = text.split('\n')
sections = {x:{} for x in lines if x.startswith("@")}
for line in lines:
if len(line.strip()) == 0 or line[0] == "#":
continue
if line[0] == "@":
section = sections[line]
continue
pos = line.find("=")
if pos < 0 :
continue
key = line[:pos].strip()
section[key] = line[pos+1:].strip()
return sections
# -----------------------------------------------
# メインウインドウの定義
# -----------------------------------------------
# 前回入力値の保存・復元用インスタンスの生成
conf = AppConfig("./config.json")
# メインウィンドウの作成
app = App()
app.minsize(width=800, height=620)
app.geometry(f"850x620")
app.title("フォルダコピー")
# customtkinterをダークモードに設定
ctk.set_appearance_mode("dark")
# ウィンドウを閉じる時に呼びたい関数を登録
app.protocol("WM_DELETE_WINDOW", close_window)
# -----------------------------------------------
# 画面レイアウト(ウィジェット)の定義
# -----------------------------------------------
group = ctk.CTkFrame(app)
# コピー条件ファイル選択ダイアログを配置
child_group = ctk.CTkFrame(group)
cond_file = FileSelectionControl(child_group,command=load_cond_file)
cond_file.label.configure(text=" コピー条件")
cond_file.pack(padx=10,pady=10,side="left",fill="x",expand=True)
# 保存ボタン
load_button = ctk.CTkButton(child_group,text="読込",height=40,width=40,command=load_cond_file)
load_button.pack(padx=5,pady=10,side="left")
# 保存ボタン
save_button = ctk.CTkButton(child_group,text="保存",height=40,width=40,command=save_cond_file)
save_button.pack(padx=0,pady=10,side="left")
# 確認のみスイッチの配置
dbg_mode = ctk.CTkSwitch(child_group,text="動作確認",width=80)
dbg_mode.pack(padx=15,pady=10,side="left")
# 実行ボタンの配置
split_button = ctk.CTkButton(child_group,text="フォルダコピー",height=40,width=120,command=copy)
split_button.pack(padx=10,pady=10,side="right")
child_group.pack(padx=10,pady=10,fill="x")
# コピー条件編集用テキストボックスの配置
condition = TextBoxControl(group)
condition.pack(padx=10,pady=10,fill="both", expand=True)
group.pack(padx=10,pady=10,fill="both",expand=True)
# コピーモードラジオボタンの配置
group = ctk.CTkFrame(app)
copy_mode=ctk.IntVar()
child_group = ctk.CTkFrame(group)
is_diff_copy = ctk.CTkRadioButton(child_group,text="差分コピー(更新日付が古いかサイスが異なればコピー)",variable=copy_mode,value=0,width=90)
is_diff_copy.pack(padx=10,pady=10,side="left")
child_group.pack(padx=10,pady=10,fill="x")
child_group = ctk.CTkFrame(group)
is_simple_copy = ctk.CTkRadioButton(child_group,text="単純コピー(コピー元のファイルでコピー先を置き換え)",variable=copy_mode,value=1,width=90)
is_simple_copy.pack(padx=10,pady=10,side="left")
child_group.pack(padx=10,pady=10,fill="x")
child_group = ctk.CTkFrame(group)
is_newer_copy = ctk.CTkRadioButton(child_group,text="更新日付が",variable=copy_mode,value=2,width=90)
is_newer_copy.pack(padx=10,pady=10,side="left")
newer_date = EntryControl(child_group)
newer_date.pack(padx=10,pady=10,side="left")
label = ctk.CTkLabel(child_group,text="以降のファイルを単純コピー")
label.pack(padx=10,pady=10,side="left")
child_group.pack(padx=10,pady=10,fill="x")
child_group = ctk.CTkFrame(group)
is_after_copy = ctk.CTkRadioButton(child_group,text="更新日付が現時点より",variable=copy_mode,value=3,width=50)
is_after_copy.pack(padx=10,pady=10,side="left")
after_date = EntryControl(child_group)
after_date.pack(padx=10,pady=10,side="left")
label = ctk.CTkLabel(child_group,text="以降のファイルを単純コピー")
label.pack(padx=10,pady=10,side="left")
child_group.pack(padx=10,pady=10,fill="x")
group.pack(padx=10,pady=10,fill="both")
# -----------------------------------------------
# 前回実行時の画面入力値を復元
# -----------------------------------------------
condition.set(conf.get("condition",""))
copy_mode.set(conf.get("copy_mode"))
newer_date.set(datetime.now().strftime("%Y/%m/%d %H:%M:%S"))
after_date.set(conf.get("after_date","1d"))
path = conf.get("cond_file","")
cond_file.set(path)
load_cond_file()
# -----------------------------------------------
# CustomTkinterの起動
# -----------------------------------------------
# メインループを開始
app.mainloop()
folder_copy.py
import os
import re
import shutil
import glob
from datetime import datetime
from dateutil.relativedelta import relativedelta
class FolderCopyCondition:
"""
分類条件クラス
"""
def updated(self):
"""
ファイルの属性を比較して、異なる場合のみTrueを返すコールバック関数を返すメソッド
:return: ファイルの属性が異なる場合はTrue、同じ場合はFalse
"""
def filter_func(sour_file, dest_file):
"""
指定された日付よりも新しいファイルをフィルタリングするためのコールバック関数
"""
# コピー先にファイルが無ければ無条件にコピー
if not os.path.exists(dest_file):
return True
# ファイル情報を取得
sour_inf = os.stat(sour_file)
dest_inf = os.stat(dest_file)
# ファイルサイズが異なる場合はコピー
if sour_inf.st_size != dest_inf.st_size:
return True
# 最終アクセス日を取得
sour_modified_time = datetime.fromtimestamp(sour_inf.st_mtime)
dest_modified_time = datetime.fromtimestamp(dest_inf.st_mtime)
# 最終アクセス日がコピー元より古い(小さい)場合はコピー
if dest_modified_time < sour_modified_time:
return True
return False
return filter_func
def modified_span(self,span="2d"):
"""
指定された時間スパン以内に更新されたファイルか否かの判定用コールバック関数を返すメソッド
:param span: 時間スパンの指定 (例: "2d"は2日前以降に更新されたファイルをコピー)
"""
# 指定された時間スパンから日時を計算
dtm = self._calculate_timespan(span)
def filter_func(sour_file, dest_file):
"""
指定された日時以降に更新されたファイルをコピーするかどうかを判定するコールバック関数
"""
sour_inf = os.stat(sour_file)
sour_modified_time = datetime.fromtimestamp(sour_inf.st_mtime)
return sour_modified_time >= dtm
# コールバック関数を返す
return filter_func
def newer_than(self,date_str):
"""
指定された日付よりも新しいファイルをフィルタリングするためのコールバック関数を返すメソッド
date_str (str): 日付を表す文字列。形式は 'YYYY-MM-DD HH:MM:SS'。
:return: コールバック関数。
"""
# 日付文字列を datetime オブジェクトに変換
specified_date = datetime.strptime(date_str.replace("/","-"), '%Y-%m-%d %H:%M:%S')
def filter_func(sour_file,dest_file):
# ファイルの最終更新時刻を取得し、指定された日付よりも新しいかどうかを確認
file_mtime = os.path.getmtime(sour_file)
file_mtime_datetime = datetime.fromtimestamp(file_mtime)
return file_mtime_datetime > specified_date
return filter_func
def _calculate_timespan(self, input_str):
"""
指定された時間スパンに基づいて日時を計算する関数
:param input_str: "Xs"、"Xm"、"Xh"、"Xd"、"XM"、"Xy" といった文字列形式の時間スパン
:return: 計算された日時
"""
num = int(input_str[:-1]) # 文字列から数値を取得
unit = input_str[-1] # 時間単位を取得
current_date = datetime.now() # 現在日時を取得
if unit == 's':
timespan = relativedelta(seconds=-num) # 指定された秒数を計算
elif unit == 'm':
timespan = relativedelta(minutes=-num) # 指定された分数を計算
elif unit == 'h':
timespan = relativedelta(hours=-num) # 指定された時間を計算
elif unit == 'd':
timespan = relativedelta(days=-num) # 指定された日数を計算
elif unit == 'M':
timespan = relativedelta(months=-num) # 指定された月数を計算
elif unit == 'y':
timespan = relativedelta(years=-num) # 指定された年数を計算
else:
timespan = relativedelta(days=-1) # デフォルトは1日前
return current_date + timespan # 現在日からタイムスパンを引いた日時を計算
class FolderCopy:
"""
ファイル分類クラス
"""
def copy_files(self,source_folder, dest_folder, recursive=True, callback=None,
include_ext=None,exclude_ext=None,include_patterns=None,exclude_patterns=None,
debug=False):
"""
ワイルドカードを使用してファイルをコピーする関数
:param source_folder: コピー元のフォルダ
:param dest_folder: コピー先のフォルダ
:param recursive: 再帰的にコピーするかどうかのフラグ
:param callback: コールバック関数
:param include_ext: コピー対象の拡張子 "*.jpg;*.png;*.py"
:param exclude_ext: コピー除外の拡張子 "*.jpg;*.png;*.py"
:param include_patterns: コピー対象のパターン "aaa%;%bbb%;%ccc" (前方一致、曖昧検索、後方一致)
:param exclude_patterns: コピー除外のパターン "aaa%;%bbb%;%ccc" (前方一致、曖昧検索、後方一致)
:return: エラーに失敗したファイル名の詳細情報をリストで返す
"""
error=[]
# 対象パターンと除外パターンをリストに分解する
if include_patterns is not None:
inc_list = [x.strip() for x in include_patterns.split(";")]
if exclude_patterns is not None:
exc_list = [x.strip() for x in exclude_patterns.split(";")]
# 指定したフォルダに存在するファイルを全て取得
files = self._get_files(source_folder, recursive)
# source_folderにファイルが指定されていたら、ワイルドカードが指定されていると判断し、フォルダを取り出す
if not os.path.isdir(source_folder):
source_folder, name = os.path.split(source_folder)
# 取得したファイル数だけループ
for source_file in files:
if os.path.isfile(source_file):
# 拡張子を取得
_,ext = os.path.splitext(source_file)
# include_ext で指定された拡張子でなければループの先頭に戻る
if include_ext is not None and ext not in include_ext:
continue
# exclude_ext で指定された拡張子の場合はループの先頭に戻る
if exclude_ext is not None and ext in exclude_ext:
continue
# include_patterns で指定された文字列パターンでなければループの先頭に戻る
if include_patterns is not None and not self._match_patterns(inc_list,source_file):
continue
# exclude_patterns で指定された文字列パターンの場合はループの先頭に戻る
if exclude_patterns is not None and self._match_patterns(exc_list,source_file):
continue
# コピー先のパスを作成
relative_path = os.path.relpath(source_file, source_folder)
destination_file = os.path.join(dest_folder, relative_path)
# コールバック関数が指定されている場合、コールバック関数を呼び出して条件を確認
if callback is not None:
if callback(source_file, destination_file) == False:
continue
# デバッグならコピーせずループの先頭に移動
if debug :
source_info = self._get_debug_info(source_file)
dest_info = self._get_debug_info(destination_file)
print("---------------------------------------")
print(f"コピー元:{source_info}")
print(f"コピー先:{dest_info}")
continue
try:
if source_file == 'D:\\My Documents\\VisualStudio2022\\Projects\\gitHub\\ksas-analyze\\WPF&NET6\\MultipurposeScriptLauncher\\MultipurposeScriptLauncher\\bin\\Debug\\net6.0-windows\\MultipurposeScriptLauncher.exe.WebView2\\EBWebView\\TrustTokenKeyCommitments\\2023.9.4.1\\_metadata\\verified_contents.json':
pass
# フォルダ作成&ファイルコピー
os.makedirs(os.path.dirname(destination_file), exist_ok=True)
shutil.copyfile(source_file, destination_file)
print(f"{source_file}⇒{destination_file}")
except Exception as e:
error.append(f"{e}")
print(e)
return error
def _get_files(self,folder,recursive=False,include_hidden=True):
"""
指定されたフォルダ内のファイルを再帰的に取得する関数
:param folder: フォルダのパス
:param recursive: 再帰的に取得するかどうかのフラグ
:param include_hidden: 隠しファイルを含めるかどうかのフラグ
:return: ファイルのリスト
"""
if os.path.isdir(folder):
folder = os.path.join(folder,"*")
if recursive:
dir,name = os.path.split(folder)
folder = os.path.join(dir,"**",name)
return glob.glob(folder,recursive=recursive,include_hidden=include_hidden)
def _match_patterns(self,patterns, text):
"""
テキストとパターンのリストを受け取り、いずれかのパターンがテキストにマッチするかどうかを返す関数
:param patterns: パターンのリスト(* を含むワイルドカードパターン)
:param text: マッチさせるテキスト
:return: いずれかのパターンがテキストにマッチする場合は True、そうでない場合は False
"""
regex_patterns = []
# パターンを正規表現に変換してリストに追加
for pattern in patterns:
if pattern.startswith('%') and pattern.endswith('%'):
regex_patterns.append(re.compile(re.escape(pattern[1:-1]))) # % で囲まれたパターンを部分一致として扱う
elif pattern.startswith('%'):
regex_patterns.append(re.compile(pattern[1:] + '$')) # % で始まるパターンを後方一致として扱う
elif pattern.endswith('%'):
regex_patterns.append(re.compile('^' + pattern[:-1])) # % で終わるパターンを前方一致として扱う
else:
regex_patterns.append(re.compile(re.escape(pattern))) # 通常の文字列として扱う
# 各正規表現パターンについてテキストとマッチングを確認
for pattern in regex_patterns:
if pattern.search(text):
return True
return False # マッチするパターンが見つからなかった場合は False を返す
def _get_debug_info(self,file_path):
"""
ファイルの更新日とサイズを取得する関数
:param file_path: ファイルのパス
:return: 更新日とサイズのタプル (modification_date, size)
"""
# ファイルが存在するかチェック
if not os.path.exists(file_path) :
return f"ファイルが存在しません {file_path}"
modification_time = os.path.getmtime(file_path) # 更新日時を取得(エポック秒)
size = os.path.getsize(file_path) # ファイルサイズを取得(バイト単位)
# 更新日時をフォーマット変換
modification_date = datetime.fromtimestamp(modification_time).strftime('%Y-%m-%d %H:%M:%S')
return f"{modification_date} ({size}byte) {file_path}"
if __name__ == '__main__':
fc = FolderCopy()
fc.copy_files("o:/data","p:/data",callback=FolderCopyCondition().updated())
fc.copy_files("o:/data","p:/data",callback=FolderCopyCondition().modified_span("6M"))
fc.copy_files("o:/data","p:/data",callback=FolderCopyCondition().newer_than("2024-07-06 00:00:00"))
まとめ
今回は、「フォルダコピーツール」について、使い方から環境構築に至るまでの手順と、ツールを作るにあたって事前に検討した内容(設計)を紹介しました。
フォルダコピーを実現するために FolderCopy クラスとFolderCopyConditionクラスを用意しました。FolderCopyはフォルダをコピーするメソッド copy_files を実装しており、引数で渡されるコールバック関数の中身を用意することで、任意の条件でファイルのコピーが可能です。
FolderCopyクラスはcopy_files 用のコールバック関数を返すメソッドを実装しており、この2つのクラスをコピーして頂ければ、本記事で紹介したコピー方法が簡単に実現できるようになっています。
もしフリーソフトの機能では要求が満たされない場合は、本記事を参考に自作してみてはいかがでしょうか?
コメント