"""
INIファイル操作のためのユーティリティクラスを提供します。
このモジュールは、INIファイル形式のデータを読み書きするための `tkIniFile` クラスを定義しています。
セクションやキー/値ペアの検索、追加、更新、削除などの機能を提供し、
既存のINIファイルの操作や新しいファイルの作成をサポートします。
:doc:`tkinifile_usage`
"""
import sys
import re
from pprint import pprint
import chardet
from tklib.tkutils import pconv, pint, pfloat, conv_float
from tklib.tkfile import tkFile
import tklib.tkre as tkre
infile = 'test.ini'
outfile = 'test-out.ini'
[ドキュメント]
class tkIniFile(tkFile):
"""
INIファイルを読み書きするためのクラスです。
tkFileクラスを継承し、INIファイル特有のセクションやキーの操作機能を提供します。
ファイルパスの管理、オープン/クローズ、行単位の読み書きなどの基本機能に加えて、
INIファイルの構造(セクション、キー、値)に基づいた高度な操作が可能です。
"""
var = {}
# args: ProgramPath, UseAsIniFile, CreateIniFile
def __init__(self, path = None, mode = 'r', OpenFile = 1, IsPrint = True, **args):
"""
tkIniFileクラスの新しいインスタンスを初期化します。
親クラス `tkFile` のコンストラクタを呼び出し、INIファイル固有のプロパティを設定します。
:param path: INIファイルのパス。指定しない場合は内部で管理される。
:type path: str, optional
:param mode: ファイルオープンモード。'r' (読み込み), 'w' (書き込み) など。デフォルトは 'r'。
:type mode: str, optional
:param OpenFile: ファイルを自動的に開くかどうかのフラグ。1の場合開く。デフォルトは1。
:type OpenFile: int, optional
:param IsPrint: デバッグ情報を出力するかどうかのフラグ。デフォルトはTrue。
:type IsPrint: bool, optional
:param args: その他の引数。`update` メソッドに渡され、INIファイル関連の内部変数を更新します。
:type args: dict
"""
super().__init__(path, mode, OpenFile, IsPrint = IsPrint, **args)
# self.fp = None
# self.path = None
# self.mode = 'r'
# self.path = self.IfYes(path is not None, path, self.path)
# self.mode = self.IfYes(mode is not None, mode, self.mode)
self.KeyHead = "System/"
self.SystemKeyHead = self.KeyHead
self.ProgramPath = ''
self.CreateIniFile = 0
self.UseAsIniFile = 0
self.update(**args)
self.var[self.KeyHead + "UseAsIniFile"] = self.UseAsIniFile;
self.var[self.KeyHead + "CreateIniFile"] = self.CreateIniFile;
self.var[self.SystemKeyHead +"ProgramFile"] = self.ProgramPath;
# this called from super().__init__
# if self.path:
# self.SetPath(self.path, self.mode)
# self.Open(self.path, self.mode)
def __del__(self):
"""
tkIniFileインスタンスが削除される際にファイルを閉じます。
リソースリークを防ぐために、ファイルポインタが閉じられていることを確認します。
"""
self.close()
super(tkIniFile, self).__del__()
def __str__(self):
"""
クラスのパス表現を文字列として返します。
親クラス `tkFile` の `ClassPath` メソッドを利用して、インスタンスの識別子を生成します。
:returns: クラスパスを表す文字列。
:rtype: str
"""
return self.ClassPath()
[ドキュメント]
def read_all(self, path = None, section = None, AddSection = False, ignore_keys = [], IsPrint = False, encoding = None):
"""
INIファイルからすべてのキーと値を読み込みます。
特定のセクションのみを読み込むことも、返される辞書のキーにセクション名を含めることも可能です。
ファイルが開かれていない場合は読み込みモードでファイルを開きます。
:param path: 読み込むINIファイルのパス。省略した場合、現在のファイルパスを使用します。
:type path: str, optional
:param section: 読み込む対象のセクション名。指定した場合、そのセクション内のキーのみを返します。
:type section: str, optional
:param AddSection: 返される辞書のキーにセクション名を含めるかどうか。Trueの場合、「セクション名/キー」の形式になります。
:type AddSection: bool, optional
:param ignore_keys: 読み込み時に無視するキーのリスト。
:type ignore_keys: list[str]
:param IsPrint: デバッグ情報を出力するかどうかのフラグ。デフォルトはFalse。
:type IsPrint: bool, optional
:param encoding: ファイルのエンコーディング。デフォルトはNone(chardetで自動判定)。
:type encoding: str, optional
:returns: INIファイルから読み込まれたキーと値の辞書。ファイルが開けない場合はNone。
:rtype: dict or None
"""
if not self.fp or path is not None:
self.close()
self.open(path = path, mode = 'r', IsPrint = IsPrint, encoding = encoding)
if not self.fp:
return None
inf = {}
secname = ''
while 1:
line = self.ReadLine()
if not line:
return inf
if line == '':
continue
if tkre.Match('#', line):
continue
match = re.match(r'\s*\[(.*?)]', line)
if match:
secname = match.group(1)
continue
if section is not None and secname != section:
continue
list = tkre.Match(r'\s*(\S.*?)\s*=\s*(.*)\s*$', line)
if list is not None and len(list) >= 2:
if list[1] in ignore_keys:
continue
if AddSection:
key = secname + '/' + list[1]
else:
key = list[1]
val = list[2]
inf[key] = val
# print("{}: {}".format(key, val))
self.close()
return inf
[ドキュメント]
def ReadAll(self, path = None, AddSection = 0, ignore_keys = [], IsPrint = False, encoding = None):
"""
INIファイルからすべてのキーと値を読み込みます(read_allのエイリアス)。
このメソッドは `read_all` メソッドのラッパーです。
`AddSection` パラメータは、内部的にbool型に変換されて `read_all` に渡されます。
:param path: 読み込むINIファイルのパス。省略した場合、現在のファイルパスを使用します。
:type path: str, optional
:param AddSection: 返される辞書のキーにセクション名を含めるかどうか。1の場合、「セクション名/キー」の形式になります。
:type AddSection: int, optional
:param ignore_keys: 読み込み時に無視するキーのリスト。
:type ignore_keys: list[str]
:param IsPrint: デバッグ情報を出力するかどうかのフラグ。デフォルトはFalse。
:type IsPrint: bool, optional
:param encoding: ファイルのエンコーディング。デフォルトはNone(chardetで自動判定)。
:type encoding: str, optional
:returns: INIファイルから読み込まれたキーと値の辞書。
:rtype: dict
"""
return self.read_all(path = path, AddSection = AddSection, ignore_keys = ignore_keys,
IsPrint = IsPrint, encoding = encoding)
[ドキュメント]
def GetNextSection(self):
"""
現在のファイルポインタ位置から次のセクションを読み込みます。
ファイルの先頭から順に次のセクションヘッダを検索し、そのセクション名と
そのセクションに属する行のリストを返します。
ファイルポインタは次のセクションの開始位置、またはファイルの終端に移動します。
:returns: タプル (セクション名, セクションの行リスト)。ファイルの終端に達した場合は ('', [])。
:rtype: tuple[str, list[str]]
"""
lines = []
line = self.ReadLine()
if line == '':
return '', lines
# print("l1: ", line, end='')
match = re.match(r'\s*\[(.*?)]', line)
if match:
secname = match.group(1)
else:
secname = ''
lines.append(line)
# print("sec [{}]".format(secname))
while 1:
pos = self.Tell()
line = self.ReadLine()
# print("l2: ", line, end='')
if line == '':
break
if re.match(r'\s*\[', line):
self.Seek(pos)
break
lines.append(line)
return secname, lines
[ドキュメント]
def FindSectionFromStr(self, section, lines = None):
"""
指定された行リスト内で、特定のセクションの開始、挿入、終了インデックスを検索します。
セクションが見つかった場合、そのセクションの開始行インデックス (i0)、
新しいキーを挿入するのに適したインデックス (iins)、およびセクションの最終行インデックス (i1) を返します。
挿入ポイント (iins) は、セクション内の最後の空白ではない行の次です。
:param section: 検索するセクション名。
:type section: str
:param lines: 検索対象の行リスト。省略した場合、`self.lines` を使用します。
:type lines: list[str], optional
:returns: タプル (セクション開始インデックス, 挿入ポイントインデックス, セクション終了インデックス)。
セクションが見つからない場合は (None, None, None)。
:rtype: tuple[int or None, int or None, int or None]
"""
if lines is None:
lines = self.lines
i0 = None
# insert point: the last non-blank line in the section
iins = None
i1 = None
for i in range(len(lines)):
regsection = re.sub(r'([\[\]\(\)])', r'\\\1', section)
if tkre.Match(r'\s*\[{}\]'.format(regsection), lines[i]):
i0 = i
break
if i0 is not None:
i1 = len(lines)
for i in range(i0+1, len(lines)):
if tkre.Match(r'\s*\[(.*?)]', lines[i]):
i1 = i - 1
break
# the insertion point is the last non-blank line in the section
if tkre.Search(r'\S', lines[i]):
iins = i
#if the sction has no key, iins will be the next line to the section title
if iins is None:
iins = i1
return i0, iins, i1 - 1
return None, None, None
[ドキュメント]
def FindLastNonBlankLine(self, lines = None):
"""
指定された行リスト内で、最後の空白ではない行のインデックスを検索します。
ファイルに内容がある場合、ファイルの末尾から逆順に検索し、
空白ではない行(空白文字以外の文字を含む行)のインデックスを返します。
:param lines: 検索対象の行リスト。省略した場合、`self.lines` を使用します。
:type lines: list[str], optional
:returns: 最後の空白ではない行のインデックス。ファイルが完全に空白である場合は0。
:rtype: int
"""
if lines is None:
lines = self.lines
n = len(lines)
il = 0
for i in range(0, n):
if tkre.Search(r'\S', lines[i]):
il = i
return il
[ドキュメント]
def FindKeyFromSectionRange(self, i0, i1, key, lines = None):
"""
指定されたセクション範囲内で、特定のキーの行インデックスを検索します。
INIファイルのセクション内の特定のキーを検索し、そのキーが存在する行のインデックスを返します。
:param i0: 検索範囲の開始インデックス(セクションの開始)。
:type i0: int
:param i1: 検索範囲の終了インデックス(セクションの終了)。
:type i1: int
:param key: 検索するキー名。
:type key: str
:param lines: 検索対象の行リスト。省略した場合、`self.lines` を使用します。
:type lines: list[str], optional
:returns: キーが見つかった行のインデックス。見つからない場合はNone。
:rtype: int or None
"""
if lines is None:
lines = self.lines
n = len(lines)
if i1+1 > n:
i1 = n - 1
# print("range: ", i0, i1)
for i in range(i0, i1+1):
# print("i=", i, " key=", key)
# print(lines[i])
regkey = re.sub(r'([\[\]\(\)])', r'\\\1', key)
# print("s, reg=", key, regkey)
# exit()
if tkre.Match(r'\s*{}\s*='.format(regkey), lines[i]):
# print("hit")
return i
return None
[ドキュメント]
def WriteString(self, section, key, value, outfile = None, IsPrint = False):
"""
INIファイルにキーと値を書き込みます(write_stringのエイリアス)。
このメソッドは `write_string` メソッドのラッパーです。
`IsPrint` パラメータは、内部的に `is_print` として `write_string` に渡されます。
:param section: 値を書き込むセクション名。
:type section: str
:param key: 書き込むキー名。
:type key: str
:param value: 書き込む値。
:type value: any
:param outfile: 書き込み先のINIファイルパス。省略した場合、現在のファイルパスを使用します。
:type outfile: str, optional
:param IsPrint: デバッグ情報を出力するかどうかのフラグ。デフォルトはFalse。
:type IsPrint: bool, optional
:returns: 書き込みが成功した場合はTrue、失敗した場合はFalse。
:rtype: bool
"""
return self.write_string(section, key, value, outfile = outfile, is_print = IsPrint)
[ドキュメント]
def write_from_scratch(self, outfile, section, **kwargs):
"""
新しいINIファイルをゼロから作成し、指定されたセクションとキー/値を書き込みます。
`outfile` で指定されたファイルが存在する場合、その内容は上書きされます。
指定されたセクションと、キーワード引数で与えられたキー/値ペアが書き込まれます。
:param outfile: 書き込み先のINIファイルパス。
:type outfile: str
:param section: 書き込むセクション名。
:type section: str
:param kwargs: セクション内に書き込むキーと値のペア。値は `conv_float` で変換されます。
:type kwargs: dict
:returns: 書き込みが成功した場合はTrue、失敗した場合はFalse。
:rtype: bool
"""
fp = tkFile(outfile, mode = 'w')
if not fp: return False
fp.write(f"[{section}]\n")
for key in kwargs:
val = conv_float(kwargs[key])
fp.write(f"{key}={val}\n")
fp.close()
return True
[ドキュメント]
def write_string(self, section, key, value, outfile = None, is_print = False):
"""
INIファイル内の指定されたセクションにキーと値を書き込みます。
指定された `outfile` が存在しない場合は作成し、存在する場合は内容を読み込み、
セクションまたはキーが存在しない場合、それらは追加されます。
既存のキーは上書きされます。
:param section: 値を書き込むセクション名。
:type section: str
:param key: 書き込むキー名。
:type key: str
:param value: 書き込む値。必要に応じて `conv_float` でfloatに変換されます。
:type value: any
:param outfile: 書き込み先のINIファイルパス。省略した場合、現在のファイルパスを使用します。
:type outfile: str, optional
:param is_print: デバッグ情報を出力するかどうかのフラグ。デフォルトはFalse。
:type is_print: bool, optional
:returns: 書き込みが成功した場合はTrue、失敗した場合はFalse。
:rtype: bool
"""
debug = 0
if outfile is None: outfile = self.path
value = conv_float(value)
lines = []
if not self.open(path = outfile, IsPrint = False): #is_print):
if is_print: print("Warning in tkinifile.WriteString: Can not read [{}]".format(self.path))
else:
lines = self.ReadLineList()
self.close()
i0, inotblank, i1 = self.FindSectionFromStr(section, lines)
if debug:
print(f"190 section={section}: key={key}: i0={i0} - inotblank={inotblank} - i1={i1}")
# Section is not found
if i0 is None:
il = self.FindLastNonBlankLine(lines)
if debug:
# print(f"{lines=}")
print(f"197 i0 is None: il={il}", il)
# if file includes some data, add the new section with a blank line
if il is not None and il > 0:
if debug:
print(f"201 i0 is None: il={il}")
lines.insert(il+1, "\n")
il += 1
# add the new section
lines.insert(il+1, "[{}]\n".format(section))
lines.insert(il+2, "{}={}\n".format(key, value))
# section is found
else:
ik = self.FindKeyFromSectionRange(i0, inotblank, key, lines)
if debug:
print(f"211 key={key} ik={ik}")
# key is not found, will be added to the next line
if ik is None:
ik = inotblank
lines.insert(ik+1, "{}={}\n".format(key, value))
# the key line ik will be replaced
else:
lines[ik] = "{}={}\n".format(key, value)
# print("lines:******************")
# for l in lines:
# print(l, end='')
# exit()
out = tkIniFile(outfile, 'w')
if out.fp is None:
print("false")
return False
# out.Rewind()
ret = out.write_lines(lines)
out.close()
return ret
[ドキュメント]
def GetString(self, section = None, key = None, defstr = None, IsPrint = False):
"""
INIファイルから文字列値を取得します(get_stringのエイリアス)。
このメソッドは `get_string` メソッドのラッパーです。
`defstr` パラメータは `def_val` として、`IsPrint` パラメータは `is_print` として `get_string` に渡されます。
:param section: 値を取得するセクション名。キーのみ指定する場合はNone。
:type section: str, optional
:param key: 取得するキー名。セクション全体を取得する場合はNone。
:type key: str, optional
:param defstr: キーが見つからなかった場合に返すデフォルト値。
:type defstr: str, optional
:param IsPrint: デバッグ情報を出力するかどうかのフラグ。デフォルトはFalse。
:type IsPrint: bool, optional
:returns: 取得された文字列値、またはデフォルト値。
:rtype: str or list[str] or None
"""
return self.get_string(section = section, key = key, def_val = defstr, is_print = IsPrint)
[ドキュメント]
def get_string(self, section = None, key = None, def_val = None, is_print = False):
"""
INIファイルから指定されたセクションとキーの文字列値を取得します。
`section` と `key` の組み合わせによって、ファイル全体、特定のセクション、または特定のキーの値を読み込みます。
ファイルが開かれていない場合は、読み込みモードでファイルを開きます。
:param section: 値を取得するセクション名。キーのみ指定する場合はNone。
:type section: str, optional
:param key: 取得するキー名。セクション全体を取得する場合はNone。
:type key: str, optional
:param def_val: キーが見つからなかった場合に返すデフォルト値。
:type def_val: str, optional
:param is_print: デバッグ情報を出力するかどうかのフラグ。デフォルトはFalse。
:type is_print: bool, optional
:returns: 取得された文字列値、またはデフォルト値。
`section` のみが指定された場合、そのセクションの行リストが返されます。
`section` も `key` も指定されない場合、ファイル全体の行リストが返されます。
キーが見つからなかった場合、`def_val` が返されます。ファイルが開けない場合は `def_val`。
:rtype: str or list[str] or None
"""
re_flag = re.IGNORECASE
self.Rewind()
if section is None:
return self.ReadLines()
if key is None:
return self.GetSection(section, is_print = is_print)
self.open(path = self.path, mode = self.mode, IsPrint = is_print)
if not self.fp:
if is_print:
self.errormsg("Error in tkIniFile.GetString(): Can not read [{}]".format(self.path))
return def_val
if section != "":
key0 = f"^\\[{section}\\]"
line = self.SkipTo(key0, 0)
if line is None:
return def_val
# print("section header = ", line)
# key0 = r"^\s*{}\s*=".format(key)
key0 = r"^\s*{}\s*=\s*(.*)\s*$".format(key)
# print("key0=[{}]".format(key0))
while 1:
line = self.ReadLine()
if not line:
return def_val
# print(f"{line=}")
match = re.match(r"\s*\[", line)
if match:
return def_val
match = re.search(key0, line, re_flag)
if match:
str = match.group(1)
# print("match:[{}]".format(str))
# str = re.sub(key0, '', line)
# str = self.DelCRLF(str)
return str
return def_val
[ドキュメント]
def GetSection(self, section):
"""
INIファイルから指定されたセクションのすべての行を取得します。
指定されたセクションヘッダを検索し、次のセクションヘッダまたはファイルの終端までの行をリストで返します。
セクションのタイトル行は含まれません。
:param section: 取得するセクション名。
:type section: str
:returns: セクション内の行のリスト。セクションが見つからない場合は空の文字列を返します。
セクションが見つかったがその内容が空の場合も空のリストを返すことがあります。
:rtype: list[str] or str
"""
re_flag = re.IGNORECASE
if section is None:
return self.ReadLines()
lines = []
# self.Open(self.path, self.mode)
# if not self.fp:
# self.errormsg(sys._getframe().f_code.co_name,
# "Can not read [{}]".format(self.path))
# return lines
self.Rewind()
key0 = "^\\[{}\\]".format(section)
line = self.SkipTo(key0, 0)
if line is None:
return ''
# print("section header = ", line)
while 1:
line = self.ReadLine()
if line is None:
return defstr # NOTE: defstr はこのスコープで定義されておらず、NameErrorを発生させる可能性があります。
match = re.match("\\s*[", line)
# match = re.match("\\s*\[", line)
if match:
return lines
line = self.DelCRLF(line)
lines.append(line)
return lines
[ドキュメント]
def main():
"""
tkinifileモジュールの基本的な使用例を示すメイン関数です。
`tkIniFile` クラスのインスタンス化、ファイルの読み込み、
特定のキーの値の取得、セクション全体の取得、および値の書き込みのデモンストレーションを行います。
"""
ini = tkIniFile(infile, 'r')
s = ini.ReadLine()
print("")
print("first line=[{}]".format(s))
title = ini.GetString("Boot", "Title", "none")
print("")
print("title=[{}]".format(title))
sec = ini.GetString("Boot")
print("")
print("section [{}]=".format("Boot"), sec)
# ini.WriteString("EditFile", "fname", "a.txt", outfile)
print("")
print("WriteString test")
# ini.WriteString("EditFile", "fname2", "abc.txt", outfile)
ini.WriteString("EditFile", "fname2", "abc.txt")
ini.close()
if __name__ == "__main__":
main()