623 lines
23 KiB
Python
623 lines
23 KiB
Python
|
from abc import ABC, abstractmethod
|
|||
|
from enum import Enum
|
|||
|
from pathlib import Path
|
|||
|
import re
|
|||
|
import os, sys
|
|||
|
from functools import wraps
|
|||
|
from typing import Any, Dict, List, Iterable
|
|||
|
import json
|
|||
|
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
|||
|
import core.logger as logger
|
|||
|
log = logger.get_logger()
|
|||
|
|
|||
|
# 自定义 JSON 编码器,处理不可序列化的类型
|
|||
|
class CustomJSONEncoder(json.JSONEncoder):
|
|||
|
def default(self, obj):
|
|||
|
if isinstance(obj, Enum):
|
|||
|
# 对于枚举类型,返回其值
|
|||
|
return obj.value
|
|||
|
elif hasattr(obj, '__dict__'):
|
|||
|
# 对于具有 __dict__ 属性的对象,返回其字典表示
|
|||
|
return obj.__dict__
|
|||
|
try:
|
|||
|
# 尝试将对象转换为列表
|
|||
|
iterable = iter(obj)
|
|||
|
return list(iterable)
|
|||
|
except TypeError:
|
|||
|
pass
|
|||
|
# 让基类处理其他类型,或抛出 TypeError
|
|||
|
return super().default(obj)
|
|||
|
|
|||
|
LOAD_INDEX = 0
|
|||
|
def loading_animation(func):
|
|||
|
"""加载动画装饰器:在每次函数被call执行时显示一个循环加载动画
|
|||
|
"""
|
|||
|
@wraps(func)
|
|||
|
def wrapper(*args, **kwargs):
|
|||
|
global LOAD_INDEX
|
|||
|
spinner = ['|', '/', '-', '\\']
|
|||
|
if os.isatty(sys.stdout.fileno()):
|
|||
|
out_str = '\r' + spinner[LOAD_INDEX]
|
|||
|
else:
|
|||
|
out_str = spinner[LOAD_INDEX]
|
|||
|
sys.stdout.write(out_str)
|
|||
|
sys.stdout.flush()
|
|||
|
LOAD_INDEX = (LOAD_INDEX + 1) % len(spinner)
|
|||
|
|
|||
|
return func(*args, **kwargs)
|
|||
|
return wrapper
|
|||
|
|
|||
|
|
|||
|
def obj2s(obj:any, indent:int=0) -> str:
|
|||
|
"""将对象转换为字符串
|
|||
|
"""
|
|||
|
if isinstance(obj, dict):
|
|||
|
ret = " "*indent + "{"
|
|||
|
for k,v in obj.items():
|
|||
|
ret += " "*indent + "\"{}\" : {}\n".format(k, obj2s(v, indent+2))
|
|||
|
ret = " "*indent + "}"
|
|||
|
return ret
|
|||
|
elif isinstance(obj, (list, set)):
|
|||
|
ret = "["
|
|||
|
for idx, item in enumerate(obj):
|
|||
|
if idx != 0:
|
|||
|
ret += ","
|
|||
|
ret += " "*indent + obj2s(item, indent+2)
|
|||
|
ret = "]"
|
|||
|
return ret
|
|||
|
elif isinstance(obj, str):
|
|||
|
return "\"{}\"".format(obj)
|
|||
|
elif isinstance(obj, bool):
|
|||
|
return str(obj).lower()
|
|||
|
else:
|
|||
|
return str(obj)
|
|||
|
|
|||
|
def get_alkaid_root(base_dir=os.path.curdir):
|
|||
|
"""获取alkaid的根目录
|
|||
|
1. 从base_dir目录开始查找,base_dir默认为当前环境目录
|
|||
|
2. 如果找不到,则尝试从文件路径开始向上
|
|||
|
3. 如果还是找不到,则抛出错误
|
|||
|
"""
|
|||
|
cwd = os.path.realpath(base_dir)
|
|||
|
try_dirs = [Path(cwd).absolute(), Path(__file__).absolute()]
|
|||
|
def check_subdir(cdir,dirs):
|
|||
|
for dir_ in dirs:
|
|||
|
# print(f"check {cdir}/{dir_} exists")
|
|||
|
if not cdir.joinpath(dir_).is_dir():
|
|||
|
return False
|
|||
|
log.info(f"Alkaid found in {cdir}")
|
|||
|
return True
|
|||
|
for item in try_dirs:
|
|||
|
try_dir = item
|
|||
|
while try_dir != Path.home():
|
|||
|
if check_subdir(try_dir, [".repo", "project", "sdk", "kernel"]):
|
|||
|
return str(try_dir)
|
|||
|
try_dir = try_dir.parent
|
|||
|
log.trace(f"Alkaid cant found in {item} path tree")
|
|||
|
raise FileNotFoundError("Alkaid not found in this directory or any parent directory")
|
|||
|
|
|||
|
|
|||
|
def get_files(directory, filter=lambda root, file: True, recursive=True) -> List[str]:
|
|||
|
"""获取指定目录下的所有文件
|
|||
|
Args:
|
|||
|
directory: 目录路径
|
|||
|
filter: 过滤函数,用于过滤文件。默认是保留所有文件
|
|||
|
filter(root, file)
|
|||
|
1.1 root: 当前目录, file: 当前文件
|
|||
|
1.2 返回True表示保留,返回False表示过滤
|
|||
|
recursive: 是否递归获取目录下的所有文件。默认是True,如果为False,则只获取当前目录下的文件
|
|||
|
Returns:
|
|||
|
List[str]: 目录下的所有文件列表,包含完整路径
|
|||
|
"""
|
|||
|
file_list = []
|
|||
|
#如果传入的是一个字符串的pattern,则转化为正则表达式
|
|||
|
if isinstance(filter, str):
|
|||
|
filter = lambda root, file: bool(re.match(filter, file))
|
|||
|
if recursive:
|
|||
|
for root, dirs, files in os.walk(directory):
|
|||
|
files = [f for f in files if filter(root, f)]
|
|||
|
file_list += [os.path.join(root, file) for file in files]
|
|||
|
else:
|
|||
|
files = os.listdir(directory)
|
|||
|
files = [f for f in files if filter(directory, f)]
|
|||
|
file_list += [os.path.join(directory, file) for file in files]
|
|||
|
return file_list
|
|||
|
|
|||
|
def is_valid_path(path):
|
|||
|
try:
|
|||
|
# 尝试创建一个 Path 对象
|
|||
|
Path(path)
|
|||
|
return True
|
|||
|
except Exception:
|
|||
|
return False
|
|||
|
|
|||
|
class ContentType(Enum):
|
|||
|
"""文件内容类型标签
|
|||
|
"""
|
|||
|
KV = "kv" #key-value格式
|
|||
|
RAW = "raw" #原始格式
|
|||
|
def __str__(self):
|
|||
|
return self.value
|
|||
|
def __repr__(self):
|
|||
|
return self.value
|
|||
|
|
|||
|
TYPE = 'type'
|
|||
|
CONTENT = 'content'
|
|||
|
class ContentStruct(list):
|
|||
|
"""用于结构化的存储文件内容
|
|||
|
1. 文件内容结构化存储,便于后续的修改和同步
|
|||
|
2. 使用列表从上到下存储,遍历列表即可获得文件内容
|
|||
|
3. 使用字典区分内容类型,便于后续的修改和同步
|
|||
|
"""
|
|||
|
def append(self, type:ContentType, content:Any):
|
|||
|
"""在列表末尾添加一个内容
|
|||
|
"""
|
|||
|
_item = {
|
|||
|
TYPE: type,
|
|||
|
CONTENT: content
|
|||
|
}
|
|||
|
super().append(_item)
|
|||
|
def insert(self, idx:int, type:ContentType, content:Any):
|
|||
|
"""在指定位置插入一个内容
|
|||
|
"""
|
|||
|
_item = {
|
|||
|
TYPE: type,
|
|||
|
CONTENT: content
|
|||
|
}
|
|||
|
super().insert(idx, _item)
|
|||
|
|
|||
|
def __str__(self):
|
|||
|
return json.dumps(self, indent=4, cls=CustomJSONEncoder)
|
|||
|
|
|||
|
def __repr__(self):
|
|||
|
return self.__str__()
|
|||
|
|
|||
|
class FileParser(ABC):
|
|||
|
def __init__(self, file_path:str):
|
|||
|
"""
|
|||
|
初始化文件解析器
|
|||
|
"""
|
|||
|
self.file_path = file_path
|
|||
|
self._raws = []
|
|||
|
self._content = ContentStruct()
|
|||
|
self.reload()
|
|||
|
|
|||
|
def reload(self):
|
|||
|
"""重新加载文件内容"""
|
|||
|
self._content.clear()
|
|||
|
with open(self.file_path, 'r') as f:
|
|||
|
self._raws = f.read().split('\n') # 使用这种方式可以保留行末的换行符
|
|||
|
|
|||
|
@abstractmethod
|
|||
|
def _parse(self) -> Dict[str,str]:
|
|||
|
pass
|
|||
|
|
|||
|
@abstractmethod
|
|||
|
def __getitem__(self, key:str) -> Any:
|
|||
|
pass
|
|||
|
|
|||
|
@abstractmethod
|
|||
|
def __setitem__(self, key:str, value:Any):
|
|||
|
pass
|
|||
|
|
|||
|
@abstractmethod
|
|||
|
def __delitem__(self, key:str):
|
|||
|
pass
|
|||
|
|
|||
|
def __iter__(self):
|
|||
|
"""迭代器"""
|
|||
|
for item in self._content:
|
|||
|
if item[TYPE] == ContentType.KV:
|
|||
|
yield item[CONTENT][0], item[CONTENT][2]
|
|||
|
|
|||
|
def __len__(self):
|
|||
|
"""获取长度"""
|
|||
|
return len([_ for _ in self._content if _[TYPE] == ContentType.KV])
|
|||
|
|
|||
|
class DefconfigParser(FileParser):
|
|||
|
# 过滤器,用于过滤掉不符合条件的行
|
|||
|
def filter(self, type:ContentType, key:str, item:Dict) -> bool:
|
|||
|
return item[TYPE] != type or key != item[CONTENT][0].strip()
|
|||
|
# 分隔符,用于分隔key和value,
|
|||
|
DELIMITER = ['=']
|
|||
|
def __init__(self, *args, **kwargs):
|
|||
|
super().__init__(*args, **kwargs)
|
|||
|
self._parse()
|
|||
|
|
|||
|
def _parse(self):
|
|||
|
"""解析defconfig文件
|
|||
|
"""
|
|||
|
for line in self._raws:
|
|||
|
#构建key-value对 value中可能也有=号所以要拼接一下
|
|||
|
key, delimiter, value, tail = self._parse_line(line)
|
|||
|
if key and delimiter:
|
|||
|
self._content.append(ContentType.KV, [key, delimiter, value, tail])
|
|||
|
else:
|
|||
|
self._content.append(ContentType.RAW, value)
|
|||
|
|
|||
|
def _parse_line(self, raw_line):
|
|||
|
"""解析defconfig文件的每一行
|
|||
|
"""
|
|||
|
line = raw_line.strip()
|
|||
|
if line.startswith('#') or not line:
|
|||
|
return None, None, raw_line, None
|
|||
|
for delimiter in self.DELIMITER:
|
|||
|
parts = line.split(delimiter, 1)
|
|||
|
key = parts[0].strip()
|
|||
|
if len(parts) < 2:
|
|||
|
delimiter = delimiter.strip()
|
|||
|
if key.endswith(delimiter): # 例如 var_name = 这样的情况
|
|||
|
return key[:-len(delimiter)], delimiter, '', ''
|
|||
|
continue
|
|||
|
value = parts[1]
|
|||
|
parts = value.split('#', 1) # 如果value后面存在注释,则将注释分离出来
|
|||
|
value = parts[0]
|
|||
|
tail = '#' + parts[1] if len(parts) > 1 else ''
|
|||
|
return key, delimiter, value, tail
|
|||
|
return None, None, raw_line, ''
|
|||
|
|
|||
|
def get(self, key:str, default:Any=None) -> str:
|
|||
|
#从后往前遍历,因为defconfig文件中可能存在多个重复的key,所以需要从后往前遍历
|
|||
|
for item in reversed(self._content):
|
|||
|
if self.filter(ContentType.KV, key, item):
|
|||
|
continue
|
|||
|
return item[CONTENT][2].strip() #item[CONTENT] = [key, delimiter, value, tail]
|
|||
|
return default
|
|||
|
|
|||
|
def index(self, key:str) -> int:
|
|||
|
"""获取指定key的索引
|
|||
|
"""
|
|||
|
for idx, item in enumerate(self._content):
|
|||
|
if self.filter(ContentType.KV, key, item):
|
|||
|
continue
|
|||
|
return idx
|
|||
|
return -1
|
|||
|
|
|||
|
def insert(self, idx, key:str, delimiter:str='=', value:Any='', tail:str=''):
|
|||
|
"""在指定位置插入指定key的值
|
|||
|
"""
|
|||
|
self._content.insert(idx, ContentType.KV, [key, delimiter, value, tail])
|
|||
|
|
|||
|
def __getitem__(self, key:str) -> Any:
|
|||
|
"""获取指定key的值,如果key不存在,则返回None
|
|||
|
"""
|
|||
|
ret = self.get(key, None)
|
|||
|
if ret is None:
|
|||
|
raise KeyError(f"Key {key} not found in {self.file_path}")
|
|||
|
return ret
|
|||
|
def __setitem__(self, key:str, value:Any):
|
|||
|
"""设置指定key的值
|
|||
|
"""
|
|||
|
is_found = False
|
|||
|
for idx in range(len(self._content) -1, -1, -1):
|
|||
|
if self.filter(ContentType.KV, key, self._content[idx]):
|
|||
|
continue
|
|||
|
if is_found: #一个文件中可能存在多个重复的key,所以需要从后往前遍历,修改最后一个并且将其他的删除掉
|
|||
|
del self._content[idx]
|
|||
|
continue
|
|||
|
self._content[idx][CONTENT][2] = value #item[CONTENT] = [key, delimiter, value]
|
|||
|
is_found = True
|
|||
|
if not is_found:
|
|||
|
self._content.append(ContentType.KV, [key, '=', value])
|
|||
|
|
|||
|
def __delitem__(self, key:str):
|
|||
|
"""删除指定key的配置项
|
|||
|
"""
|
|||
|
del_idx = []
|
|||
|
for idx, item in enumerate(self._content):
|
|||
|
if self.filter(ContentType.KV, key, item):
|
|||
|
continue
|
|||
|
del_idx.append(idx)
|
|||
|
#return 有可能存在多个重复的key,所以不能直接返回,要全部删掉
|
|||
|
if not del_idx:
|
|||
|
raise KeyError(f"Key {key} not found in {self.file_path}")
|
|||
|
else:
|
|||
|
for idx in reversed(del_idx):
|
|||
|
del self._content[idx]
|
|||
|
|
|||
|
def __contains__(self, key:str) -> bool:
|
|||
|
"""判断指定key是否存在
|
|||
|
"""
|
|||
|
return self.index(key) != -1
|
|||
|
|
|||
|
def sync(self):
|
|||
|
"""将_content中的修改同步到_raws中
|
|||
|
同步策略:
|
|||
|
1. 如果item[TYPE] == ContentType.RAW,则将item[CONTENT]添加到_raws中
|
|||
|
2. 如果item[TYPE] == ContentType.KV,则将item[CONTENT]转换为字符串并添加到_raws中
|
|||
|
3. 如果value为None,则删除该行
|
|||
|
"""
|
|||
|
_content = []
|
|||
|
for item in self._content:
|
|||
|
if item[TYPE] == ContentType.RAW: #原始行
|
|||
|
_content.append(item[CONTENT])
|
|||
|
elif item[TYPE] == ContentType.KV: #key-value行
|
|||
|
_content.append(''.join(item[CONTENT]))
|
|||
|
self._raws = _content
|
|||
|
|
|||
|
def flush(self, file_path=None):
|
|||
|
"""将_content中的修改同步到文件中
|
|||
|
Args:
|
|||
|
file_path: 要同步到的文件路径,默认为当前文件路径
|
|||
|
"""
|
|||
|
if file_path is None:
|
|||
|
file_path = self.file_path
|
|||
|
self.sync()
|
|||
|
with open(file_path, 'w') as f:
|
|||
|
f.write('\n'.join(self._raws))
|
|||
|
|
|||
|
def __del__(self):
|
|||
|
"""析构函数,确保对象销毁时自动同步到文件"""
|
|||
|
# try:
|
|||
|
# self.flush()
|
|||
|
# except Exception as e:
|
|||
|
# # 防止析构时出现异常导致程序崩溃
|
|||
|
# log.error(f"Error during auto-sync in __del__: {e}")
|
|||
|
pass
|
|||
|
def __str__(self):
|
|||
|
return self._content.__str__()
|
|||
|
def __repr__(self):
|
|||
|
return self.__str__()
|
|||
|
|
|||
|
class MakefileParser(FileParser):
|
|||
|
"""解析 Makefile 文件,提供类似字典的接口进行访问和修改
|
|||
|
|
|||
|
支持的 Makefile 语法:
|
|||
|
- 变量赋值: 使用 '=', ':=' 等
|
|||
|
- 多行续写: 使用 '\'
|
|||
|
|
|||
|
提供以下接口:
|
|||
|
- get(key, default): 获取指定 key 的值,如果不存在则返回 default
|
|||
|
- __getitem__(key): 获取指定 key 的值,等同于使用 parser[key]
|
|||
|
- __setitem__(key, value): 设置指定 key 的值,等同于使用 parser[key] = value
|
|||
|
- __delitem__(key): 删除指定 key,等同于使用 del parser[key]
|
|||
|
- flush(): 将内存中的修改写回文件
|
|||
|
"""
|
|||
|
# Makefile的分隔符列表,按优先级排序
|
|||
|
DELIMITERS = [':=', '?=', ' =', '=', ":"]
|
|||
|
|
|||
|
def __init__(self, *args, **kwargs):
|
|||
|
self.content_modifed = False
|
|||
|
super().__init__(*args, **kwargs)
|
|||
|
self._parse()
|
|||
|
|
|||
|
def _parse(self):
|
|||
|
self._content.clear()
|
|||
|
"""解析 Makefile 文件内容,构建结构化存储"""
|
|||
|
cur_key, cur_delimiter, cur_value, tail, is_continued = None, None, [], None, False
|
|||
|
for line in self._raws:
|
|||
|
key, delimiter, value, tail = self._parse_line(line, is_continued)
|
|||
|
# 如果这一行是一个全新的key-value对, 则先将其先暂存起来
|
|||
|
if key and delimiter:
|
|||
|
if cur_key:
|
|||
|
raise ValueError(f"Last line not ended, but new line started: {line}")
|
|||
|
cur_key, cur_delimiter = key, delimiter
|
|||
|
if value:
|
|||
|
cur_value.append(value)
|
|||
|
# 如果这一行是上一行的续行, 这只需要将这一行value添加到current_value中
|
|||
|
elif is_continued and value and not key and not delimiter:
|
|||
|
cur_value.append(value)
|
|||
|
is_continued = True if tail == '\\' else False
|
|||
|
# 如果当前存在key和delimiter,并且不是续行(已经结束) 则将当前的key-value对保存到内容结构中
|
|||
|
if cur_key and not is_continued:
|
|||
|
self._content.append(ContentType.KV, [cur_key, cur_delimiter, cur_value, tail])
|
|||
|
cur_key, cur_delimiter, cur_value, tail, is_continued = None, None, [], None, False
|
|||
|
elif not cur_key:
|
|||
|
self._content.append(ContentType.RAW, line)
|
|||
|
# 处理最后一个键值对
|
|||
|
if cur_key:
|
|||
|
self._content.append(ContentType.KV, [cur_key, cur_delimiter, cur_value, tail])
|
|||
|
|
|||
|
def _parse_line(self, raw_line, is_continued):
|
|||
|
"""解析行,识别分隔符、键和值
|
|||
|
Args:
|
|||
|
raw_line: 原始行
|
|||
|
is_continued: 是否为续行
|
|||
|
Returns:
|
|||
|
(key, delimiter, value, tail)
|
|||
|
key: 键
|
|||
|
delimiter: 分隔符
|
|||
|
value: 值
|
|||
|
tail: 行尾部的数据,比如注释
|
|||
|
"""
|
|||
|
line = raw_line.strip()
|
|||
|
if line.startswith('#') or not line: # 单独的一个注释行 或者空行
|
|||
|
return None, None, raw_line, None
|
|||
|
if is_continued: # 说明这一行的内容就是上一行的接续,所有内容都是value
|
|||
|
# line = line[:-1].strip()
|
|||
|
if raw_line.endswith('\\'): # 如果存在续行符,则将续行符分离出来
|
|||
|
return None, None, raw_line[:-1], raw_line[-1]
|
|||
|
return None, None, raw_line, None
|
|||
|
# 尝试切割划出key和value
|
|||
|
for delimiter in self.DELIMITERS:
|
|||
|
parts = line.split(delimiter, 1)
|
|||
|
if len(parts) < 2:
|
|||
|
if line.endswith(delimiter): # 例如 var_name := 这样的情况
|
|||
|
return parts[0].strip(), delimiter, None, None
|
|||
|
continue
|
|||
|
key, value = parts[0].strip(), parts[1]
|
|||
|
if not key.isidentifier(): # 如果key不是一个合法的标识符,说明这个分隔符是误会
|
|||
|
continue
|
|||
|
if value.endswith('\\'): # 如果value后面存在续行符,则将续行符分离出来
|
|||
|
value, tail = value[:-1], value[-1]
|
|||
|
else:
|
|||
|
parts = value.split('#', 1) # 如果value后面存在注释,则将注释分离出来
|
|||
|
value, tail = parts[0], '#' + parts[1] if len(parts) > 1 else None
|
|||
|
return key, delimiter, value, tail
|
|||
|
# 如果没有任何分隔符,则将整行作为 value 返回
|
|||
|
return None, None, raw_line, None
|
|||
|
|
|||
|
def get(self, key, default=None):
|
|||
|
"""获取指定键的值,如果不存在则返回默认值"""
|
|||
|
idx = self.index(key)
|
|||
|
if idx == -1:
|
|||
|
return default
|
|||
|
item = self._content[idx]
|
|||
|
value = item[CONTENT][2]
|
|||
|
return self._value_process(value)
|
|||
|
|
|||
|
def _value_process(self, value):
|
|||
|
"""处理value 将value中存在的空格去掉
|
|||
|
"""
|
|||
|
ret = []
|
|||
|
for _ in value:
|
|||
|
ret += _.split()
|
|||
|
value = ret
|
|||
|
return [_.strip() for _ in value]
|
|||
|
|
|||
|
def index(self, key):
|
|||
|
"""获取指定键的索引"""
|
|||
|
for idx, item in enumerate(self._content):
|
|||
|
if item[TYPE] == ContentType.KV and item[CONTENT][0].strip() == key:
|
|||
|
return idx
|
|||
|
return -1
|
|||
|
|
|||
|
def insert(self, idx:Any, key:str, delimiter:str=':=', values:Any=None, tail:str=None):
|
|||
|
"""插入指定位置插入值,如果idx是key,则将key插入到key的第一个匹配项的位置
|
|||
|
Args:
|
|||
|
idx: 插入位置,可以是索引,也可以是key
|
|||
|
key: 键
|
|||
|
delimiter: 分隔符
|
|||
|
values: 值
|
|||
|
tail: 行尾部的数据,比如注释
|
|||
|
"""
|
|||
|
if isinstance(idx, str):
|
|||
|
idx = self.index(idx)
|
|||
|
if idx == -1:
|
|||
|
raise KeyError(f"Key '{idx}' not found in {self.file_path}")
|
|||
|
if not isinstance(idx, int):
|
|||
|
raise ValueError(f"Invalid index type: {type(idx)}")
|
|||
|
if not isinstance(values, list):
|
|||
|
values = [str(values)]
|
|||
|
self._content.insert(idx, ContentType.KV, [key, delimiter, values, tail])
|
|||
|
|
|||
|
def replace(self, old_pattern, new:str, global_replace=True):
|
|||
|
"""替换指定键的值
|
|||
|
Args:
|
|||
|
old_pattern: 旧的pattern
|
|||
|
new: 新的pattern
|
|||
|
global_replace: 是否全局替换
|
|||
|
"""
|
|||
|
if self.content_modifed:
|
|||
|
self.sync()
|
|||
|
for idx, item in enumerate(self._raws):
|
|||
|
if not re.match(old_pattern, item):
|
|||
|
continue
|
|||
|
self._raws[idx] = re.sub(old_pattern, new, item)
|
|||
|
if not global_replace:
|
|||
|
break
|
|||
|
log.info(f"{self._raws}")
|
|||
|
self._parse()
|
|||
|
|
|||
|
def delete(self, old_pattern, global_delete=True):
|
|||
|
"""删除指定键的值
|
|||
|
Args:
|
|||
|
old_pattern: 旧的pattern
|
|||
|
global_delete: 是否全局删除
|
|||
|
"""
|
|||
|
del_idx = []
|
|||
|
if self.content_modifed:
|
|||
|
self.sync()
|
|||
|
for idx, item in enumerate(self._raws):
|
|||
|
if re.match(old_pattern, item):
|
|||
|
del_idx.append(idx)
|
|||
|
if not global_delete:
|
|||
|
break
|
|||
|
for idx in reversed(del_idx):
|
|||
|
del self._raws[idx]
|
|||
|
self._parse()
|
|||
|
|
|||
|
def __getitem__(self, key):
|
|||
|
"""获取指定键的值"""
|
|||
|
if isinstance(key, str):
|
|||
|
value = self.get(key, None)
|
|||
|
if value is None:
|
|||
|
raise KeyError(f"Key '{key}' not found in {self.file_path}")
|
|||
|
elif isinstance(key, int):
|
|||
|
return self._value_process(self._content[key][CONTENT][2])
|
|||
|
else:
|
|||
|
raise KeyError(f"Invalid key type: {type(key)}")
|
|||
|
|
|||
|
def __setitem__(self, key, value):
|
|||
|
"""设置指定键的值
|
|||
|
|
|||
|
如果键已存在,则更新最后一个匹配项并删除其他匹配项
|
|||
|
如果键不存在,则在文件末尾添加
|
|||
|
"""
|
|||
|
# 将单个字符串转换为列表
|
|||
|
if not isinstance(value, list):
|
|||
|
value = [str(value)]
|
|||
|
|
|||
|
found = False
|
|||
|
log.info(f"set [{key}] = {value}")
|
|||
|
# 从后往前遍历以找到并更新最后一个匹配项
|
|||
|
for i in range(len(self._content) - 1, -1, -1):
|
|||
|
item = self._content[i]
|
|||
|
if item[TYPE] == ContentType.KV and item[CONTENT][0].strip() == key.strip():
|
|||
|
if not found:
|
|||
|
# 更新最后一个匹配项
|
|||
|
log.info(f"update [{key}] = {self._content[i][CONTENT][2]} to {value}")
|
|||
|
self._content[i][CONTENT][2] = value
|
|||
|
found = True
|
|||
|
else:
|
|||
|
# 删除其他匹配项
|
|||
|
del self._content[i]
|
|||
|
self.content_modifed = True
|
|||
|
# 如果没有找到匹配项,添加新项
|
|||
|
if not found:
|
|||
|
if not key.isidentifier(): # 如果key中是非标识符字符,则认为这是一个正则pattern
|
|||
|
self.replace(key, value[0])
|
|||
|
else:
|
|||
|
# 新增一个kv结构
|
|||
|
self._content.append(ContentType.KV, [key, ' := ', value, None])
|
|||
|
self.content_modifed = True
|
|||
|
|
|||
|
def __delitem__(self, key):
|
|||
|
"""删除指定键的所有实例"""
|
|||
|
found = False
|
|||
|
for i in range(len(self._content) - 1, -1, -1):
|
|||
|
item = self._content[i]
|
|||
|
if item[TYPE] == ContentType.KV and item[CONTENT][0].strip() == key.strip():
|
|||
|
del self._content[i]
|
|||
|
found = True
|
|||
|
|
|||
|
if not found:
|
|||
|
raise KeyError(f"Key '{key}' not found in {self.file_path}")
|
|||
|
else:
|
|||
|
self.delete(key)
|
|||
|
|
|||
|
def sync(self):
|
|||
|
"""同步文件内容"""
|
|||
|
|
|||
|
if not self.content_modifed:
|
|||
|
return
|
|||
|
self.content_modifed = False
|
|||
|
_content = []
|
|||
|
for item in self._content:
|
|||
|
if item[TYPE] == ContentType.RAW:
|
|||
|
line = item[CONTENT]
|
|||
|
elif item[TYPE] == ContentType.KV:
|
|||
|
key, delimiter, values, tail = item[CONTENT]
|
|||
|
line = f"{key}{delimiter}"
|
|||
|
line += '\\\n'.join(values)
|
|||
|
line += tail if tail else ''
|
|||
|
else:
|
|||
|
raise ValueError(f"Invalid item type: {item[TYPE]}")
|
|||
|
_content.append(line)
|
|||
|
_content = '\n'.join(_content)
|
|||
|
self._raws = _content.split('\n')
|
|||
|
|
|||
|
|
|||
|
def flush(self, file_path=None):
|
|||
|
"""将内存中的修改写回文件"""
|
|||
|
if file_path is None:
|
|||
|
file_path = self.file_path
|
|||
|
self.sync()
|
|||
|
with open(file_path, 'w') as f:
|
|||
|
f.write('\n'.join(self._raws))
|
|||
|
|
|||
|
def __str__(self):
|
|||
|
return json.dumps(self._content, indent=4, cls=CustomJSONEncoder)
|
|||
|
|
|||
|
def __repr__(self):
|
|||
|
return self.__str__()
|