init
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
import re
|
||||
from typing import List, Dict, Tuple, Optional
|
||||
|
||||
class ManifestParser:
|
||||
def __init__(self):
|
||||
self.tasks = [] # 存储解析后的任务指令
|
||||
|
||||
def parse(self, content: str):
|
||||
"""解析 manifest.txt 或邮件正文,生成内部指令结构"""
|
||||
lines = [line.strip() for line in content.splitlines() if line.strip() and not line.strip().startswith('#')]
|
||||
self.tasks = []
|
||||
current_scope = None # 当前压缩包作用域,None表示根目录
|
||||
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
line = lines[i]
|
||||
# 处理压缩包作用域开始
|
||||
if line.startswith('in ') and line.endswith(':'):
|
||||
archive_name = line[3:-1].strip()
|
||||
current_scope = archive_name
|
||||
i += 1
|
||||
continue
|
||||
elif line == 'in .':
|
||||
current_scope = None
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# 检查行内是否包含 "in xxx" 临时作用域
|
||||
inline_archive = None
|
||||
if ' in ' in line:
|
||||
parts = line.split(' in ')
|
||||
cmd_part = parts[0].strip()
|
||||
archive_part = parts[1].strip()
|
||||
# 如果archive_part还有更多内容(如"to: xxx"),需重新组合?假设格式严格:"指令 in archive.zip"
|
||||
if ' ' in archive_part:
|
||||
# 复杂情况,暂不支持,要求用户使用单独行
|
||||
pass
|
||||
else:
|
||||
inline_archive = archive_part
|
||||
line = cmd_part
|
||||
|
||||
# 解析指令
|
||||
if line.startswith('to:'):
|
||||
# 全局默认
|
||||
targets = self._parse_targets(line[3:])
|
||||
self.tasks.append({
|
||||
'type': 'global',
|
||||
'targets': targets,
|
||||
'scope': current_scope,
|
||||
'inline_archive': inline_archive
|
||||
})
|
||||
elif line.startswith('from:'):
|
||||
# 按格式批量转换
|
||||
match = re.match(r'from:\s*(\S+)\s+to:\s*(.+)', line)
|
||||
if match:
|
||||
src_format = match.group(1).lower()
|
||||
targets = self._parse_targets(match.group(2))
|
||||
self.tasks.append({
|
||||
'type': 'by_format',
|
||||
'src_format': src_format,
|
||||
'targets': targets,
|
||||
'scope': current_scope,
|
||||
'inline_archive': inline_archive
|
||||
})
|
||||
elif line.startswith('from.name:'):
|
||||
# 精准文件名匹配
|
||||
rest = line[10:].strip()
|
||||
if ' to.name:' in rest:
|
||||
# 重命名
|
||||
parts = rest.split(' to.name:')
|
||||
src_name = parts[0].strip()
|
||||
dst_spec = parts[1].strip()
|
||||
# 解析 dst_spec: "newname.ext(size)"
|
||||
dst_name, size = self._parse_dst_with_size(dst_spec)
|
||||
self.tasks.append({
|
||||
'type': 'rename',
|
||||
'src_name': src_name,
|
||||
'dst_name': dst_name,
|
||||
'size': size,
|
||||
'scope': current_scope,
|
||||
'inline_archive': inline_archive
|
||||
})
|
||||
else:
|
||||
# to: 格式
|
||||
parts = rest.split(' to:')
|
||||
src_name = parts[0].strip()
|
||||
targets = self._parse_targets(parts[1])
|
||||
self.tasks.append({
|
||||
'type': 'by_name',
|
||||
'src_name': src_name,
|
||||
'targets': targets,
|
||||
'scope': current_scope,
|
||||
'inline_archive': inline_archive
|
||||
})
|
||||
elif line.startswith('to.name:'):
|
||||
# 多版本导出中的 to.name 行,需要结合前面的 from.name 处理
|
||||
# 我们在主流程中采用累积方式,这里简单处理,实际在main中会连续读取
|
||||
# 为简化,不在此处实现多行组合,而是在主程序中按行处理上下文
|
||||
# 让主程序负责收集连续的 to.name
|
||||
pass
|
||||
else:
|
||||
logger.warning(f"无法识别的指令: {line}")
|
||||
i += 1
|
||||
return self.tasks
|
||||
|
||||
def _parse_targets(self, target_str: str) -> List[Tuple[str, Optional[Tuple]]]:
|
||||
"""解析 "webp, png(16:9), jpg(800x600)" -> [('webp',None), ('png',('16:9')), ('jpg',('800x600'))]"""
|
||||
items = [t.strip() for t in target_str.split(',')]
|
||||
result = []
|
||||
for item in items:
|
||||
size = None
|
||||
if '(' in item and item.endswith(')'):
|
||||
fmt, size_part = item.split('(', 1)
|
||||
size_str = size_part.rstrip(')')
|
||||
if ':' in size_str:
|
||||
# 比例
|
||||
w_ratio, h_ratio = map(int, size_str.split(':'))
|
||||
size = ('ratio', w_ratio, h_ratio)
|
||||
elif 'x' in size_str:
|
||||
w, h = map(int, size_str.split('x'))
|
||||
size = ('pixel', w, h)
|
||||
result.append((fmt.lower(), size))
|
||||
else:
|
||||
result.append((item.lower(), None))
|
||||
return result
|
||||
|
||||
def _parse_dst_with_size(self, dst_spec: str) -> Tuple[str, Optional[Tuple]]:
|
||||
"""解析 "banner.webp(800x600)" -> ('banner.webp', ('pixel',800,600))"""
|
||||
size = None
|
||||
if '(' in dst_spec and dst_spec.endswith(')'):
|
||||
name, size_part = dst_spec.split('(', 1)
|
||||
size_str = size_part.rstrip(')')
|
||||
if ':' in size_str:
|
||||
w, h = map(int, size_str.split(':'))
|
||||
size = ('ratio', w, h)
|
||||
elif 'x' in size_str:
|
||||
w, h = map(int, size_str.split('x'))
|
||||
size = ('pixel', w, h)
|
||||
return name.strip(), size
|
||||
else:
|
||||
return dst_spec.strip(), None
|
||||
Reference in New Issue
Block a user