tagged_directory のソースコード

"""外部参照用のモジュール"""

from pathlib import Path
import json
import re
import os
import shutil


[ドキュメント] class TaggedDirectory(object): """ディレクトリ内のファイルに対してタグ管理するためのクラス Attributes: path (pathlib.Path): 対象ディレクトリのパス json_path (pathlib.Path): jsonファイルのパス result_path (pathlib.Path): フィルター結果フォルダのパス data (dict[str, list[str]]): タグのデータ。キーがファイル名で、値がタグのリスト """ def __init__( self, path: str | Path, *, json_filename: str = "tagudu.json", result_directory_name: str = "tagudu_result", ): """ Args: path (str | pathlib.Path): 対象ディレクトリのパス json_filename (str, optional): タグのデータを格納するjsonファイルの名前 result_directory_name (str, optional): フィルター結果フォルダの名前 """ self.path = Path(path) self.json_path = self.path / json_filename self.result_path = self.path / result_directory_name # 対象ディレクトリが存在しない場合、例外を送出 if not self.path.is_dir(): raise FileNotFoundError("存在しないディレクトリです") # jsonファイルを読み込み if self.json_path.is_file(): with open(self.json_path, encoding="utf-8") as file: self.data = json.load(file) # jsonファイルが無い場合 else: self.data = {} # データ操作関係
[ドキュメント] def set_tags(self, filename: str, tags: list[str]): """ファイルに対してタグを設定 Args: filename (str): ファイル名。未登録のものも指定可 tags (list[str]): タグのリスト。設定済みのものも指定可 """ # tagsが空のリストの場合は何もしない if len(tags) == 0: return # 未登録のファイル名の場合 if filename not in self.data: self.data[filename] = [] # タグを設定 file_tags = self.data[filename] new_tags = list(set(tags) - set(file_tags)) file_tags += new_tags
[ドキュメント] def remove_tags(self, filename: str, tags: list[str]): """ファイルからタグを解除 Args: filename (str): ファイル名 tags (list[str]): タグのリスト """ # 指定ファイル名がデータに登録されていない場合は何もしない if filename not in self.data: return file_tags = self.data[filename] # タグを解除 for tag in tags: if tag in file_tags: file_tags.remove(tag) # タグのリストが空になった場合、項目を削除 if len(file_tags) == 0: del self.data[filename]
[ドキュメント] def remove_all_tags(self, filename: str): """ファイルからすべてのタグを解除 Args: filename (str): ファイル名 """ if filename in self.data: del self.data[filename]
[ドキュメント] def autoremove(self): """タグが付けられているが実在しないファイルを削除""" nonexisting_file_list = self.get_nonexisting_file_list() for filename in nonexisting_file_list: self.remove_all_tags(filename)
[ドキュメント] def save_json(self): """jsonファイルにデータを書き込む""" with open(self.json_path, "w", encoding="utf-8") as file: json.dump(self.data, file, indent=4, ensure_ascii=False)
# データ取得関係
[ドキュメント] def get_tag_list(self, filename: str) -> list[str]: """ファイルに設定されているタグのリストを取得 Args: filename (str): ファイル名 Returns: list[str]: タグのリスト """ if filename in self.data: return self.data[filename] else: return []
[ドキュメント] def get_file_list(self) -> list[str]: """タグがつけられているファイルのリストを取得 Returns: list[str]: ファイルのリスト """ return list(self.data.keys())
[ドキュメント] def get_existing_file_list(self) -> list[str]: """タグ付け可能な実在するファイルのリストを取得 Returns: list[str]: ファイルのリスト """ file_list = [ filename for filename in os.listdir(self.path) if (self.path / filename).is_file() ] if self.result_path.is_dir(): file_list += [ filename for filename in os.listdir(self.result_path) if (self.result_path / filename).is_file() ] return file_list
[ドキュメント] def get_nonexisting_file_list(self) -> list[str]: """タグが付けられているが実在しないファイルのリストを取得 Returns: list[str]: ファイルのリスト """ file_list = self.get_file_list() existing_file_list = self.get_existing_file_list() return list(set(file_list) - set(existing_file_list))
[ドキュメント] def get_unregistered_file_list(self) -> list[str]: """タグがつけられていないファイルのリストを取得 Returns: list[str]: ファイルのリスト """ file_list = self.get_file_list() existing_file_list = self.get_existing_file_list() return list(set(existing_file_list) - set(file_list))
[ドキュメント] def count_tags(self, search_word: str = "") -> dict[str, int]: """タグ数を集計 Args: search_word (str, optional): 検索ワード Returns: dict[str, int]: 集計結果 """ result = {} # 集計 for tag_list in self.data.values(): for tag in tag_list: if tag not in result: result[tag] = 0 result[tag] += 1 sorted_result = {} # ソート for tag in sorted(result, key=lambda tag: result[tag], reverse=True): sorted_result[tag] = result[tag] search_result = {} # 検索 for tag in filter(lambda tag: search_word in tag, sorted_result): search_result[tag] = sorted_result[tag] return search_result
# フィルター関係
[ドキュメント] def filter_by_tags( self, tags: list[str], *, mode: str = "or", file_operation: bool = True ) -> list[str]: """完全一致のタグによりファイルを絞り込み Args: tags (list[str]): タグのリスト mode (str, optional): 絞り込みモード。「or」か「and」。初期値は「or」 file_operation (bool): 絞り込み結果を結果ディレクトリに反映させるか。初期値は「True」 Returns: list[str]: 絞り込み結果 """ # モードの確認 if mode not in ("or", "and"): raise ValueError("モードは「or」か「and」でなければなりません") def func(filename): file_tags = self.data[filename] count = 0 for tag in tags: if tag in file_tags: count += 1 if mode == "or": return count >= 1 if mode == "and": return count == len(tags) result = list(filter(func, self.data)) # ファイル操作 if file_operation: self.reset_directory_structure() self._apply_filter_result(result) return result
[ドキュメント] def filter_by_regular_expression( self, pattern: str, *, file_operation: bool = True ) -> list[str]: """正規表現によりファイルを絞り込み Args: pattern (str): 正規表現 file_operation (bool): 絞り込み結果を結果ディレクトリに反映させるか。初期値は「True」 Returns: list[str]: 絞り込み結果 """ compiled_pattern = re.compile(pattern) def func(filename): file_tags = self.data[filename] for file_tag in file_tags: if compiled_pattern.match(file_tag): return True return False result = list(filter(func, self.data)) # ファイル操作 if file_operation: self.reset_directory_structure() self._apply_filter_result(result) return result
[ドキュメント] def filter_by_partial_tags( self, tags: list[str], *, file_operation: bool = True ) -> list[str]: """部分一致のタグによりファイルを絞り込み Args: tags (list[str]): タグのリスト file_operation (bool): 絞り込み結果を結果ディレクトリに反映させるか。初期値は「True」 Returns: list[str]: 絞り込み結果 """ def func(filename): file_tags = self.data[filename] for file_tag in file_tags: for tag in tags: if tag in file_tag: return True return False result = list(filter(func, self.data)) # ファイル操作 if file_operation: self.reset_directory_structure() self._apply_filter_result(result) return result
[ドキュメント] def reset_directory_structure(self): """ディレクトリ内のファイルの配置をリセットする""" # フィルター結果フォルダが無い場合は何もしない if not self.result_path.is_dir(): return items = os.listdir(self.result_path) # 結果フォルダ内のファイルのみを外に移動、 for item in items: item_path = self.result_path / item if item_path.is_file(): shutil.move(item_path, self.path) # 結果フォルダ内が空であれば結果フォルダを削除 try: os.rmdir(self.result_path) except OSError: pass
def _apply_filter_result(self, files: list[str]): """指定されたファイルをフィルター結果ディレクトリに移動する Args: files (list[str]): ファイル名のリスト """ os.makedirs(self.result_path, exist_ok=True) for filename in files: file_path = self.path / filename if file_path.is_file(): shutil.move(file_path, self.result_path)