libs.utils.textutil
libs/utils/textutil.py
1""" 2libs/utils/textutil.py 3""" 4 5import os 6import unicodedata 7from math import ceil, floor 8from typing import TYPE_CHECKING, Literal 9 10import libs.global_value as g 11 12if TYPE_CHECKING: 13 from pathlib import Path 14 15 16def len_count(text: str) -> int: 17 """文字数をカウント(全角文字は2) 18 19 Args: 20 text (str): 判定文字列 21 22 Returns: 23 int: 文字数 24 """ 25 26 count = 0 27 for c in text: 28 if unicodedata.east_asian_width(c) in "FWA": 29 count += 2 30 else: 31 count += 1 32 33 return count 34 35 36def str_conv(text: str, kind: Literal["h2z", "z2h", "h2k", "k2h"]) -> str: 37 """文字列変換 38 39 Args: 40 text (str): 変換対象文字列 41 kind (str): 変換種類 42 - h2z: 半角文字を全角文字に変換(数字のみ) 43 - z2h: 全角文字を半角文字に変換(数字のみ) 44 - h2k: ひらがなをカタカナに変換 45 - k2h: カタカナをひらがなに変換 46 47 Returns: 48 str: 変換後の文字列 49 """ 50 51 zen = "".join(chr(0xff10 + i) for i in range(10)) 52 han = "".join(chr(0x30 + i) for i in range(10)) 53 hira = "".join(chr(0x3041 + i) for i in range(86)) 54 kana = "".join(chr(0x30a1 + i) for i in range(86)) 55 56 match kind: 57 case "h2z": # 半角文字を全角文字に変換(数字のみ) 58 trans_table = str.maketrans(han, zen) 59 case "z2h": # 全角文字を半角文字に変換(数字のみ) 60 trans_table = str.maketrans(zen, han) 61 case "h2k": # ひらがなをカタカナに変換 62 trans_table = str.maketrans(hira, kana) 63 case "k2h": # カタカナをひらがなに変換 64 trans_table = str.maketrans(kana, hira) 65 case _: 66 return text 67 68 return text.translate(trans_table) 69 70 71def count_padding(data): 72 """プレイヤー名一覧の中の最も長い名前の文字数を返す 73 74 Args: 75 data (list, dict): 対象プレイヤー名の一覧 76 77 Returns: 78 int: 文字数 79 """ 80 81 name_list = [] 82 83 if isinstance(data, list): 84 name_list = data 85 86 if isinstance(data, dict): 87 for i in data.keys(): 88 for name in [data[i][x]["name"] for x in ("東家", "南家", "西家", "北家")]: 89 if name not in name_list: 90 name_list.append(name) 91 92 if name_list: 93 return max(len_count(x) for x in name_list) 94 return 0 95 96 97def save_file_path(filename: str, delete: bool = False) -> "Path": 98 """保存ファイルのフルパスを取得 99 100 Args: 101 filename (str): デフォルトファイル名 102 delete (bool, optional): 生成済みファイルを削除. Defaults to False. 103 104 Returns: 105 Path: 保存ファイルパス 106 """ 107 108 _, file_ext = os.path.splitext(filename) 109 file_name = f"{g.params["filename"]}{file_ext}" if g.params.get("filename") else f"{filename}" 110 file_path = g.cfg.setting.work_dir / file_name 111 112 if file_path.exists() and delete: 113 os.remove(file_path) 114 115 return file_path 116 117 118def split_balanced(data: list, target_size: int, tolerance: float = 0.15) -> list: 119 """リストデータを指定個数で分割 120 121 Args: 122 data (list): 対象データ 123 target_size (int): 分割サイズ 124 tolerance (float, optional): 個数誤差. Defaults to 0.15. 125 126 Returns: 127 list: 分割したリスト 128 """ 129 130 # 分割サイズに0が指定されている場合は何もしない 131 if not target_size: 132 return data 133 134 n = len(data) 135 if n == 0: 136 return [] 137 138 min_size = int(target_size * (1 - tolerance)) 139 max_size = int(target_size * (1 + tolerance)) 140 141 # 最小ブロック数の候補を計算 142 min_blocks = ceil(n / max_size) 143 max_blocks = floor(n / min_size) 144 145 # 許容範囲内でブロック数を決める(なるべく少ない) 146 for num_blocks in range(min_blocks, max_blocks + 1): 147 size = n / num_blocks 148 if min_size <= size <= max_size: 149 break 150 else: 151 # 条件を満たすブロック数がない場合は単純均等割り 152 num_blocks = ceil(n / target_size) 153 154 # 実際の分割処理 155 base_size = n // num_blocks 156 remainder = n % num_blocks 157 158 result: list = [] 159 start = 0 160 for i in range(num_blocks): 161 end = start + base_size + (1 if i < remainder else 0) 162 result.append(data[start:end]) 163 start = end 164 165 return result 166 167 168def split_text_blocks(text: str, limit: int = 2000) -> list[str]: 169 """指定文字数でテキストを行単位で分割してリストにする 170 171 Args: 172 text (str): 対象文字列 173 limit (int, optional): 分割文字数. Defaults to 2000. 174 175 Returns: 176 list[str]: 分割リスト 177 """ 178 179 blocks = [] 180 current_data = "" 181 buffer_data = "" 182 in_code = False 183 min_gap_after_code_start = 10 184 lines_count = 0 185 186 for _, line in enumerate(text.splitlines(keepends=True)): 187 stripped = line.strip() 188 buffer_data += line 189 190 # --- コードブロック開始/終了検出 --- 191 if stripped.startswith("```"): 192 in_code = not in_code 193 if not in_code: 194 current_data += buffer_data 195 buffer_data = "" 196 continue 197 198 lines_count += 1 if in_code else 0 199 200 # --- 文字数チェック --- 201 if len(current_data + buffer_data) > limit: 202 if lines_count > min_gap_after_code_start: 203 if in_code: 204 blocks.append(current_data + buffer_data + "```\n") 205 buffer_data = "```\n" 206 else: 207 blocks.append(current_data + buffer_data) 208 buffer_data = "" 209 else: 210 blocks.append(current_data) # 先頭の改行は削除されてしまう 211 current_data = "" 212 213 return blocks
def
len_count(text: str) -> int:
17def len_count(text: str) -> int: 18 """文字数をカウント(全角文字は2) 19 20 Args: 21 text (str): 判定文字列 22 23 Returns: 24 int: 文字数 25 """ 26 27 count = 0 28 for c in text: 29 if unicodedata.east_asian_width(c) in "FWA": 30 count += 2 31 else: 32 count += 1 33 34 return count
文字数をカウント(全角文字は2)
Arguments:
- text (str): 判定文字列
Returns:
int: 文字数
def
str_conv(text: str, kind: Literal['h2z', 'z2h', 'h2k', 'k2h']) -> str:
37def str_conv(text: str, kind: Literal["h2z", "z2h", "h2k", "k2h"]) -> str: 38 """文字列変換 39 40 Args: 41 text (str): 変換対象文字列 42 kind (str): 変換種類 43 - h2z: 半角文字を全角文字に変換(数字のみ) 44 - z2h: 全角文字を半角文字に変換(数字のみ) 45 - h2k: ひらがなをカタカナに変換 46 - k2h: カタカナをひらがなに変換 47 48 Returns: 49 str: 変換後の文字列 50 """ 51 52 zen = "".join(chr(0xff10 + i) for i in range(10)) 53 han = "".join(chr(0x30 + i) for i in range(10)) 54 hira = "".join(chr(0x3041 + i) for i in range(86)) 55 kana = "".join(chr(0x30a1 + i) for i in range(86)) 56 57 match kind: 58 case "h2z": # 半角文字を全角文字に変換(数字のみ) 59 trans_table = str.maketrans(han, zen) 60 case "z2h": # 全角文字を半角文字に変換(数字のみ) 61 trans_table = str.maketrans(zen, han) 62 case "h2k": # ひらがなをカタカナに変換 63 trans_table = str.maketrans(hira, kana) 64 case "k2h": # カタカナをひらがなに変換 65 trans_table = str.maketrans(kana, hira) 66 case _: 67 return text 68 69 return text.translate(trans_table)
文字列変換
Arguments:
- text (str): 変換対象文字列
- kind (str): 変換種類
- - h2z: 半角文字を全角文字に変換(数字のみ)
- - z2h: 全角文字を半角文字に変換(数字のみ)
- - h2k: ひらがなをカタカナに変換
- - k2h: カタカナをひらがなに変換
Returns:
str: 変換後の文字列
def
count_padding(data):
72def count_padding(data): 73 """プレイヤー名一覧の中の最も長い名前の文字数を返す 74 75 Args: 76 data (list, dict): 対象プレイヤー名の一覧 77 78 Returns: 79 int: 文字数 80 """ 81 82 name_list = [] 83 84 if isinstance(data, list): 85 name_list = data 86 87 if isinstance(data, dict): 88 for i in data.keys(): 89 for name in [data[i][x]["name"] for x in ("東家", "南家", "西家", "北家")]: 90 if name not in name_list: 91 name_list.append(name) 92 93 if name_list: 94 return max(len_count(x) for x in name_list) 95 return 0
プレイヤー名一覧の中の最も長い名前の文字数を返す
Arguments:
- data (list, dict): 対象プレイヤー名の一覧
Returns:
int: 文字数
def
save_file_path(filename: str, delete: bool = False) -> pathlib.Path:
98def save_file_path(filename: str, delete: bool = False) -> "Path": 99 """保存ファイルのフルパスを取得 100 101 Args: 102 filename (str): デフォルトファイル名 103 delete (bool, optional): 生成済みファイルを削除. Defaults to False. 104 105 Returns: 106 Path: 保存ファイルパス 107 """ 108 109 _, file_ext = os.path.splitext(filename) 110 file_name = f"{g.params["filename"]}{file_ext}" if g.params.get("filename") else f"{filename}" 111 file_path = g.cfg.setting.work_dir / file_name 112 113 if file_path.exists() and delete: 114 os.remove(file_path) 115 116 return file_path
保存ファイルのフルパスを取得
Arguments:
- filename (str): デフォルトファイル名
- delete (bool, optional): 生成済みファイルを削除. Defaults to False.
Returns:
Path: 保存ファイルパス
def
split_balanced(data: list, target_size: int, tolerance: float = 0.15) -> list:
119def split_balanced(data: list, target_size: int, tolerance: float = 0.15) -> list: 120 """リストデータを指定個数で分割 121 122 Args: 123 data (list): 対象データ 124 target_size (int): 分割サイズ 125 tolerance (float, optional): 個数誤差. Defaults to 0.15. 126 127 Returns: 128 list: 分割したリスト 129 """ 130 131 # 分割サイズに0が指定されている場合は何もしない 132 if not target_size: 133 return data 134 135 n = len(data) 136 if n == 0: 137 return [] 138 139 min_size = int(target_size * (1 - tolerance)) 140 max_size = int(target_size * (1 + tolerance)) 141 142 # 最小ブロック数の候補を計算 143 min_blocks = ceil(n / max_size) 144 max_blocks = floor(n / min_size) 145 146 # 許容範囲内でブロック数を決める(なるべく少ない) 147 for num_blocks in range(min_blocks, max_blocks + 1): 148 size = n / num_blocks 149 if min_size <= size <= max_size: 150 break 151 else: 152 # 条件を満たすブロック数がない場合は単純均等割り 153 num_blocks = ceil(n / target_size) 154 155 # 実際の分割処理 156 base_size = n // num_blocks 157 remainder = n % num_blocks 158 159 result: list = [] 160 start = 0 161 for i in range(num_blocks): 162 end = start + base_size + (1 if i < remainder else 0) 163 result.append(data[start:end]) 164 start = end 165 166 return result
リストデータを指定個数で分割
Arguments:
- data (list): 対象データ
- target_size (int): 分割サイズ
- tolerance (float, optional): 個数誤差. Defaults to 0.15.
Returns:
list: 分割したリスト
def
split_text_blocks(text: str, limit: int = 2000) -> list[str]:
169def split_text_blocks(text: str, limit: int = 2000) -> list[str]: 170 """指定文字数でテキストを行単位で分割してリストにする 171 172 Args: 173 text (str): 対象文字列 174 limit (int, optional): 分割文字数. Defaults to 2000. 175 176 Returns: 177 list[str]: 分割リスト 178 """ 179 180 blocks = [] 181 current_data = "" 182 buffer_data = "" 183 in_code = False 184 min_gap_after_code_start = 10 185 lines_count = 0 186 187 for _, line in enumerate(text.splitlines(keepends=True)): 188 stripped = line.strip() 189 buffer_data += line 190 191 # --- コードブロック開始/終了検出 --- 192 if stripped.startswith("```"): 193 in_code = not in_code 194 if not in_code: 195 current_data += buffer_data 196 buffer_data = "" 197 continue 198 199 lines_count += 1 if in_code else 0 200 201 # --- 文字数チェック --- 202 if len(current_data + buffer_data) > limit: 203 if lines_count > min_gap_after_code_start: 204 if in_code: 205 blocks.append(current_data + buffer_data + "```\n") 206 buffer_data = "```\n" 207 else: 208 blocks.append(current_data + buffer_data) 209 buffer_data = "" 210 else: 211 blocks.append(current_data) # 先頭の改行は削除されてしまう 212 current_data = "" 213 214 return blocks
指定文字数でテキストを行単位で分割してリストにする
Arguments:
- text (str): 対象文字列
- limit (int, optional): 分割文字数. Defaults to 2000.
Returns:
list[str]: 分割リスト