已合并
openEuler社区软件包加固-第6期(自动化生成用例工具开发)python部分 #66
AtomGit-Bot创建于 2023年7月12日
openEuler社区软件包加固-第6期(自动化生成用例工具开发)python部分 #66
已合并
从refs/pull/66/head合入到master
共 3 个文件变更+1913-0
| @@ -0,0 +1,98 @@ | |||
| 1 | +# This program is licensed under Mulan PSL v2. | ||
| 2 | +# You can use it according to the terms and conditions of the Mulan PSL v2. | ||
| 3 | +# http://license.coscl.org.cn/MulanPSL2 | ||
| 4 | +# THIS PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, | ||
| 5 | +# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, | ||
| 6 | +# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. | ||
| 7 | +# See the Mulan PSL v2 for more details. | ||
| 8 | +#################################### | ||
| 9 | +# @Author : hourui | ||
| 10 | +# @Contact : softgreet@qq.com | ||
| 11 | +# @Date : 2023-10-12 12:00:00 | ||
| 12 | +# @License : Mulan PSL v2 | ||
| 13 | +# @Version : 1.0 | ||
| 14 | +# @Desc : python中参数解析 | ||
| 15 | +##################################### | ||
| 16 | + | ||
| 17 | +#coding=UTF-8 | ||
| 18 | + | ||
| 19 | +from enum import Enum | ||
| 20 | +import subprocess | ||
| 21 | +import locale | ||
| 22 | +import itertools | ||
| 23 | +import ast | ||
| 24 | + | ||
| 25 | +# 比较两个抽象语法树是否相同 | ||
| 26 | +def compare_ast(node1, node2): | ||
| 27 | + if type(node1) is not type(node2): | ||
| 28 | + return False | ||
| 29 | + if isinstance(node1, ast.AST): | ||
| 30 | + if node1._fields != node2._fields: | ||
| 31 | + return False | ||
| 32 | + for k in node1._fields: | ||
| 33 | + if not compare_ast(getattr(node1, k), getattr(node2, k)): | ||
| 34 | + return False | ||
| 35 | + return True | ||
| 36 | + elif isinstance(node1, list): | ||
| 37 | + if len(node1) != len(node2): | ||
| 38 | + return False | ||
| 39 | + return all(itertools.starmap(compare_ast, zip(node1, node2))) | ||
| 40 | + else: | ||
| 41 | + return node1 == node2 | ||
| 42 | + | ||
| 43 | +class FileType(Enum): | ||
| 44 | + SRCRPM = 'src.rpm file' | ||
| 45 | + RPM = 'rpm file' | ||
| 46 | + EXE = 'other script except shell|py|perl' | ||
| 47 | + PYTHON = 'python script' | ||
| 48 | + SHELL = 'shell script' | ||
| 49 | + PERL = 'perl script' | ||
| 50 | + ELF = 'elf file' | ||
| 51 | + LINK = 'link file' | ||
| 52 | + OTHER = 'other file' | ||
| 53 | + NOTFILE = 'not file' | ||
| 54 | + | ||
| 55 | + def get_linked_file(link: str): | ||
| 56 | + ret = subprocess.run(args='file -b \'' + link + '\'', stdout=subprocess.PIPE, | ||
| 57 | + stderr=subprocess.STDOUT, shell=True, encoding=locale.getpreferredencoding()) | ||
| 58 | + if ret.returncode == 0: | ||
| 59 | + output = ret.stdout.strip().lower() | ||
| 60 | + if output.find('symbolic link') != -1: | ||
| 61 | + target = output.split(' ')[-1] | ||
| 62 | + if target[0] != '/': | ||
| 63 | + if target.startswith('./'): | ||
| 64 | + target = target.lstrip('./') | ||
| 65 | + target = link[:link.rfind('/') + 1] + target | ||
| 66 | + return target | ||
| 67 | + else: | ||
| 68 | + return link | ||
| 69 | + else: | ||
| 70 | + raise Exception(ret.stdout) | ||
| 71 | + | ||
| 72 | + def filetype(filename: str): | ||
| 73 | + ret = subprocess.run(args='file -b \'' + filename + '\'', stdout=subprocess.PIPE, | ||
| 74 | + stderr=subprocess.STDOUT, shell=True, encoding=locale.getpreferredencoding()) | ||
| 75 | + if ret.returncode == 0: | ||
| 76 | + output = ret.stdout.lower() | ||
| 77 | + if output.find('cannot open') != -1: | ||
| 78 | + return FileType.NOTFILE | ||
| 79 | + elif output.find('rpm') != -1: | ||
| 80 | + if output.find('bin') != -1: | ||
| 81 | + return FileType.RPM | ||
| 82 | + elif output.find('src') != -1: | ||
| 83 | + return FileType.SRCRPM | ||
| 84 | + elif output.find('executable') != -1: | ||
| 85 | + if output.find('python') != -1: | ||
| 86 | + return FileType.PYTHON | ||
| 87 | + elif output.find('shell') != -1: | ||
| 88 | + return FileType.SHELL | ||
| 89 | + elif output.find('perl') != -1: | ||
| 90 | + return FileType.PERL | ||
| 91 | + elif output.find('elf') != -1: | ||
| 92 | + return FileType.ELF | ||
| 93 | + else: | ||
| 94 | + return FileType.EXE | ||
| 95 | + elif output.find('symbolic link') != -1: | ||
| 96 | + return FileType.LINK | ||
| 97 | + else: | ||
| 98 | + return FileType.OTHER | ||
| @@ -0,0 +1,769 @@ | |||
| 1 | +# This program is licensed under Mulan PSL v2. | ||
| 2 | +# You can use it according to the terms and conditions of the Mulan PSL v2. | ||
| 3 | +# http://license.coscl.org.cn/MulanPSL2 | ||
| 4 | +# THIS PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, | ||
| 5 | +# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, | ||
| 6 | +# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. | ||
| 7 | +# See the Mulan PSL v2 for more details. | ||
| 8 | +#################################### | ||
| 9 | +# @Author : hourui | ||
| 10 | +# @Contact : softgreet@qq.com | ||
| 11 | +# @Date : 2023-10-12 12:00:00 | ||
| 12 | +# @License : Mulan PSL v2 | ||
| 13 | +# @Version : 1.0 | ||
| 14 | +# @Desc : python中参数解析 | ||
| 15 | +##################################### | ||
| 16 | + | ||
| 17 | +#coding=UTF-8 | ||
| 18 | + | ||
| 19 | +import argparse | ||
| 20 | +import optparse | ||
| 21 | +from typing import Any, Iterable, Sequence, Union, List, Tuple, Dict | ||
| 22 | +import copy | ||
| 23 | +import random | ||
| 24 | +from copy import deepcopy | ||
| 25 | + | ||
| 26 | +# 常量的定义 | ||
| 27 | +UNUSED = 0 | ||
| 28 | +USED = 1 | ||
| 29 | + | ||
| 30 | +# 在一次递归的搜索参数时判断是否需要继续的全局变量 | ||
| 31 | +MORE = False | ||
| 32 | + | ||
| 33 | +# 参数的action是以下这些值的时候表明该参数不需要赋值 | ||
| 34 | +NOVALARGS = ['store_true', 'store_false', 'store_const', | ||
| 35 | + 'version', 'append_const', 'count', 'callback', 'help'] | ||
| 36 | + | ||
| 37 | +# 用以简化一个参数解析对象里的属性,是默认值Ellipsis{...}时表明没有传递值过来,那么就可以从结果中去掉 | ||
| 38 | +def simplify(pair: dict): | ||
| 39 | + for key, val in list(pair.items()): | ||
| 40 | + if val == Ellipsis: | ||
| 41 | + pair.pop(key) | ||
| 42 | + return pair | ||
| 43 | + | ||
| 44 | + | ||
| 45 | +class SubParserManager(): | ||
| 46 | + def __init__(self, title=..., description=..., prog=..., parser_class=..., action=..., | ||
| 47 | + option_string=..., dest=..., required=..., help=..., metavar=...) -> None: | ||
| 48 | + self.title = title | ||
| 49 | + self.description = description | ||
| 50 | + self.prog = prog | ||
| 51 | + self.parser_class = parser_class | ||
| 52 | + self.action = action | ||
| 53 | + self.option_string = option_string | ||
| 54 | + self.dest = dest | ||
| 55 | + self.required = required | ||
| 56 | + self.help = help if help != ArgParser.SUPPRESS else ArgParser.DEFAULTHELP | ||
| 57 | + self.metavar = metavar | ||
| 58 | + | ||
| 59 | + self.subparsers: List[SubParser] = [] | ||
| 60 | + | ||
| 61 | + def add_parser(self, | ||
| 62 | + command: str, | ||
| 63 | + help: str = ..., | ||
| 64 | + aliases: Sequence[str] = ..., | ||
| 65 | + prog=..., | ||
| 66 | + usage=..., | ||
| 67 | + description=..., | ||
| 68 | + epilog=..., | ||
| 69 | + parents=..., | ||
| 70 | + formatter_class=..., | ||
| 71 | + prefix_chars=..., | ||
| 72 | + fromfile_prefix_chars=..., | ||
| 73 | + argument_default=..., | ||
| 74 | + conflict_handler=..., | ||
| 75 | + add_help=..., | ||
| 76 | + allow_abbrev=..., | ||
| 77 | + exit_on_error=...): | ||
| 78 | + subparser = SubParser(command, help=help, aliases=aliases, prog=prog, usage=usage, description=description, | ||
| 79 | + epilog=epilog, parents=parents, formatter_class=formatter_class, | ||
| 80 | + prefix_chars=prefix_chars, | ||
| 81 | + fromfile_prefix_chars=fromfile_prefix_chars, argument_default=argument_default, | ||
| 82 | + conflict_handler=conflict_handler, add_help=add_help, allow_abbrev=allow_abbrev, | ||
| 83 | + exit_on_error=exit_on_error) | ||
| 84 | + self.subparsers.append(subparser) | ||
| 85 | + return subparser | ||
| 86 | + | ||
| 87 | + def add_helpdfs(self): | ||
| 88 | + for subparser in self.subparsers: | ||
| 89 | + subparser.add_helpdfs() | ||
| 90 | + | ||
| 91 | + def display(self, prefix=''): | ||
| 92 | + s = f'{prefix}Subcommands: ' | ||
| 93 | + params = simplify({ | ||
| 94 | + 'title': self.title, | ||
| 95 | + 'description': self.description, | ||
| 96 | + 'prog': self.prog, | ||
| 97 | + 'parser_class': self.parser_class, | ||
| 98 | + 'action': self.action, | ||
| 99 | + 'option_string': self.option_string, | ||
| 100 | + 'dest': self.dest, | ||
| 101 | + 'required': self.required, | ||
| 102 | + 'help': self.help, | ||
| 103 | + 'metavar': self.metavar | ||
| 104 | + }) | ||
| 105 | + if params: | ||
| 106 | + s += str(params) | ||
| 107 | + prefix += '\t' | ||
| 108 | + for subparser in self.subparsers: | ||
| 109 | + s += f"\n{subparser.display(prefix)}" | ||
| 110 | + return s | ||
| 111 | + | ||
| 112 | + def get_num(self) -> int: | ||
| 113 | + return sum(subparser.get_num() for subparser in self.subparsers) | ||
| 114 | + | ||
| 115 | + def union(self, manager): | ||
| 116 | + if manager: | ||
| 117 | + self.subparsers.extend(manager.subparsers) | ||
| 118 | + | ||
| 119 | + def add_val_help(self, value_map, help_map): | ||
| 120 | + for subparser in self.subparsers: | ||
| 121 | + subparser.add_val_help(value_map, help_map) | ||
| 122 | + | ||
| 123 | + def yield_cmd(self, cmd): | ||
| 124 | + more = False | ||
| 125 | + n = len(cmd) | ||
| 126 | + for subparser in self.subparsers: | ||
| 127 | + more = subparser.yield_cmd(cmd) | ||
| 128 | + if more: | ||
| 129 | + break | ||
| 130 | + else: | ||
| 131 | + while len(cmd) > n: | ||
| 132 | + cmd.pop() | ||
| 133 | + return more | ||
| 134 | + | ||
| 135 | + | ||
| 136 | +class Argument(): | ||
| 137 | + def __init__(self, *name_or_flags: str, action=..., nargs=..., const=..., | ||
| 138 | + default: Any = ..., type=..., choices=..., required: bool = ..., | ||
| 139 | + help=..., metavar: Union[str, tuple[str, ...]] = ..., | ||
| 140 | + dest: str = ..., version: str = ..., **kwargs: Any) -> None: | ||
| 141 | + self.args = name_or_flags | ||
| 142 | + self.action = action | ||
| 143 | + self.nargs = nargs | ||
| 144 | + self.const = const | ||
| 145 | + self.default = default | ||
| 146 | + self.type = type | ||
| 147 | + self.choices = choices | ||
| 148 | + self.required = required | ||
| 149 | + # 默认参数的处理 | ||
| 150 | + self.help = help if help != ArgParser.SUPPRESS else ArgParser.DEFAULTHELP | ||
| 151 | + self.metavar = metavar | ||
| 152 | + self.dest = dest | ||
| 153 | + self.version = version | ||
| 154 | + self.used = UNUSED | ||
| 155 | + | ||
| 156 | + def __str__(self) -> str: | ||
| 157 | + args = list(filter(lambda x: x is not None, self.args)) | ||
| 158 | + if not args: | ||
| 159 | + return '' | ||
| 160 | + s = ','.join(self.args) | ||
| 161 | + keywords = simplify({ | ||
| 162 | + 'action': self.action, | ||
| 163 | + 'nargs': self.nargs, | ||
| 164 | + 'const': self.const, | ||
| 165 | + 'default': self.default, | ||
| 166 | + 'type': self.type, | ||
| 167 | + 'choices': self.choices, | ||
| 168 | + 'required': self.required, | ||
| 169 | + 'help': self.help, | ||
| 170 | + 'metavar': self.metavar, | ||
| 171 | + 'dest': self.dest, | ||
| 172 | + 'version': self.version | ||
| 173 | + }) | ||
| 174 | + if keywords: | ||
| 175 | + s = f"{s}\n{keywords}" | ||
| 176 | + return s | ||
| 177 | + | ||
| 178 | + def get_num(self) -> int: | ||
| 179 | + return 1 | ||
| 180 | + | ||
| 181 | + def add_val_help(self, value_map, help_map): | ||
| 182 | + args = list(filter(lambda x: x is not None, self.args)) | ||
| 183 | + if not args: | ||
| 184 | + return | ||
| 185 | + args.sort(key=lambda x: len(x)) | ||
| 186 | + label = args[-1].lstrip('-').replace('-', '_').upper() | ||
| 187 | + if self.metavar and self.metavar != Ellipsis: | ||
| 188 | + label = self.metavar.upper() | ||
| 189 | + elif self.dest and self.dest != Ellipsis: | ||
| 190 | + label = self.dest | ||
| 191 | + key = f"{UniArgParser.get_cmd_name()},{label}" | ||
| 192 | + if self.choices is not None and self.choices != Ellipsis: | ||
| 193 | + value_map[key] = self.choices | ||
| 194 | + else: | ||
| 195 | + value_map[key] = [] | ||
| 196 | + if self.default is not None and self.default != Ellipsis: | ||
| 197 | + value_map[key].append(self.default) | ||
| 198 | + if self.const is not None and self.const != Ellipsis: | ||
| 199 | + value_map[key] = self.const | ||
| 200 | + if not value_map[key]: | ||
| 201 | + value_map.pop(key) | ||
| 202 | + if self.type and self.type != Ellipsis: | ||
| 203 | + help_map[key] = self.type | ||
| 204 | + | ||
| 205 | + def yield_cmd(self, cmd): | ||
| 206 | + global MORE | ||
| 207 | + args = list(filter(lambda x: x is not None, self.args)) | ||
| 208 | + if not args: | ||
| 209 | + return False | ||
| 210 | + required = (self.required == True or (not args[0].startswith( | ||
| 211 | + '-') and self.nargs != '?')) and self.default == Ellipsis | ||
| 212 | + if required or (MORE and self.used == UNUSED): | ||
| 213 | + args.sort(key=lambda x: len(x)) | ||
| 214 | + label = args[-1].lstrip('-').replace('-', '_').upper() | ||
| 215 | + if self.metavar and self.metavar != Ellipsis: | ||
| 216 | + label = self.metavar.upper() | ||
| 217 | + elif self.dest and self.dest != Ellipsis: | ||
| 218 | + label = self.dest.upper() | ||
| 219 | + if self.action in NOVALARGS or '-h' in args or '--help' in args: | ||
| 220 | + cmd.append(args[-1]) | ||
| 221 | + elif not args[0].startswith('-'): | ||
| 222 | + cmd.append(label) | ||
| 223 | + else: | ||
| 224 | + cmd.extend([args[-1], label]) | ||
| 225 | + self.used = USED | ||
| 226 | + if not required: | ||
| 227 | + return True | ||
| 228 | + return False | ||
| 229 | + | ||
| 230 | + | ||
| 231 | +# 分组参数,和普通参数没有区别,只是提供一个整体的抽象表述,某些参数聚集在一个组中有个统一的title和description,可能表示这些参数有某些联系或者是完成一个功能的相关参数 | ||
| 232 | +class GroupArg(Argument): | ||
| 233 | + def __init__(self, title=..., description=..., *name_or_flags: str, **kwargs: Any) -> None: | ||
| 234 | + super().__init__(*name_or_flags, **kwargs) | ||
| 235 | + self.arguments = [] | ||
| 236 | + self.title = title | ||
| 237 | + self.description = description | ||
| 238 | + | ||
| 239 | + def add_argument(self, | ||
| 240 | + *name_or_flags: str, | ||
| 241 | + action=..., | ||
| 242 | + nargs=..., | ||
| 243 | + const=..., | ||
| 244 | + default: Any = ..., | ||
| 245 | + type=..., | ||
| 246 | + choices=..., | ||
| 247 | + required: bool = ..., | ||
| 248 | + help=..., | ||
| 249 | + metavar: Union[str, tuple[str, ...]] = ..., | ||
| 250 | + dest: str = ..., | ||
| 251 | + version: str = ..., | ||
| 252 | + **kwargs: Any): | ||
| 253 | + self.arguments.append(Argument(*name_or_flags, action=action, nargs=nargs, const=const, | ||
| 254 | + default=default, type=type, choices=choices, required=required, | ||
| 255 | + help=help, metavar=metavar, dest=dest, version=version, **kwargs)) | ||
| 256 | + | ||
| 257 | + def get_num(self) -> int: | ||
| 258 | + return len(self.arguments) | ||
| 259 | + | ||
| 260 | + def __str__(self) -> str: | ||
| 261 | + info = {} | ||
| 262 | + s = f'arg group: {len(self.arguments)}' | ||
| 263 | + if self.title and self.title != Ellipsis: | ||
| 264 | + info['title'] = self.title | ||
| 265 | + if self.description and self.description != Ellipsis: | ||
| 266 | + info['description'] = self.description | ||
| 267 | + if info: | ||
| 268 | + s += f" {info}" | ||
| 269 | + for arg in self.arguments: | ||
| 270 | + s += f"\n{arg}" | ||
| 271 | + return s | ||
| 272 | + | ||
| 273 | + def add_val_help(self, value_map, help_map): | ||
| 274 | + for argument in self.arguments: | ||
| 275 | + argument.add_val_help(value_map, help_map) | ||
| 276 | + | ||
| 277 | + def yield_cmd(self, cmd): | ||
| 278 | + global MORE | ||
| 279 | + more = False | ||
| 280 | + for argument in self.arguments: | ||
| 281 | + more = more or argument.yield_cmd(cmd) | ||
| 282 | + if more: | ||
| 283 | + MORE = False | ||
| 284 | + return more | ||
| 285 | + | ||
| 286 | + | ||
| 287 | +# 互斥组参数,在分组参数的基础上多了一个组内参数不能同时被使用的限制 | ||
| 288 | +class ExclusiveGroupArg(GroupArg): | ||
| 289 | + def __init__(self, required=...) -> None: | ||
| 290 | + super().__init__() | ||
| 291 | + self.required = required | ||
| 292 | + self.arguments = [] | ||
| 293 | + | ||
| 294 | + def __str__(self) -> str: | ||
| 295 | + if len(self.arguments) == 0: | ||
| 296 | + return '' | ||
| 297 | + s = f'exclusive arg group: {len(self.arguments)}' | ||
| 298 | + for arg in self.arguments: | ||
| 299 | + s += f"\n{arg}" | ||
| 300 | + return s | ||
| 301 | + | ||
| 302 | + def yield_cmd(self, cmd): | ||
| 303 | + global MORE | ||
| 304 | + more = False | ||
| 305 | + for argument in self.arguments: | ||
| 306 | + more = argument.yield_cmd(cmd) | ||
| 307 | + if more: | ||
| 308 | + return True | ||
| 309 | + if self.required == True: | ||
| 310 | + index = random.randint(0, len(self.arguments) - 1) | ||
| 311 | + self.arguments[index].used = UNUSED | ||
| 312 | + self.arguments[index].yield_cmd(cmd) | ||
| 313 | + return False | ||
| 314 | + return more | ||
| 315 | + | ||
| 316 | + | ||
| 317 | +# 和argparse的ArgumentParser对应的类 | ||
| 318 | +class ArgParser: | ||
| 319 | + SUPPRESS = argparse.SUPPRESS | ||
| 320 | + DEFAULTHELP = 'show this help message and exit' | ||
| 321 | + | ||
| 322 | + def __init__(self, | ||
| 323 | + prog=..., | ||
| 324 | + usage=..., | ||
| 325 | + description=..., | ||
| 326 | + epilog=..., | ||
| 327 | + parents=..., | ||
| 328 | + formatter_class=..., | ||
| 329 | + prefix_chars=..., | ||
| 330 | + fromfile_prefix_chars=..., | ||
| 331 | + argument_default=..., | ||
| 332 | + conflict_handler=..., | ||
| 333 | + add_help=..., | ||
| 334 | + allow_abbrev=..., | ||
| 335 | + exit_on_error=...) -> None: | ||
| 336 | + self.arguments = [] | ||
| 337 | + self.subparserManager = None | ||
| 338 | + self.ArgumentParser(prog, usage, description, epilog, parents, formatter_class, prefix_chars, | ||
| 339 | + fromfile_prefix_chars, argument_default, conflict_handler, add_help, allow_abbrev, | ||
| 340 | + exit_on_error) | ||
| 341 | + | ||
| 342 | + def __str__(self) -> str: | ||
| 343 | + params = simplify({ | ||
| 344 | + 'prog': self.prog, | ||
| 345 | + 'usage': self.usage, | ||
| 346 | + 'description': self.description, | ||
| 347 | + 'epilog': self.epilog, | ||
| 348 | + 'parents': self.parents, | ||
| 349 | + 'formatter_class': self.formatter_class, | ||
| 350 | + 'prefix_chars': self.prefix_chars, | ||
| 351 | + 'fromfile_prefix_chars': self.fromfile_prefix_chars, | ||
| 352 | + 'argument_default': self.argument_default, | ||
| 353 | + 'conflict_handler': self.conflict_handler, | ||
| 354 | + 'add_help': self.add_help, | ||
| 355 | + 'allow_abbrev': self.allow_abbrev, | ||
| 356 | + 'exit_on_error': self.exit_on_error | ||
| 357 | + }) | ||
| 358 | + s = f"{params}" if params else '' | ||
| 359 | + for arg in self.arguments: | ||
| 360 | + if s: | ||
| 361 | + s += f"\n{arg}" | ||
| 362 | + else: | ||
| 363 | + s = f"{arg}" | ||
| 364 | + if self.subparserManager: | ||
| 365 | + if s: | ||
| 366 | + s += f"\n{self.subparserManager.display()}" | ||
| 367 | + else: | ||
| 368 | + s = self.subparserManager.display() | ||
| 369 | + return s | ||
| 370 | + | ||
| 371 | + def get_num(self) -> int: | ||
| 372 | + numInArgs = sum(arg.get_num() for arg in self.arguments) | ||
| 373 | + numInSubparser = self.subparserManager.get_num() if self.subparserManager else 0 | ||
| 374 | + return numInArgs + numInSubparser | ||
| 375 | + | ||
| 376 | + def ArgumentParser(self, prog=..., usage=..., description=..., epilog=..., parents=..., | ||
| 377 | + formatter_class=..., prefix_chars=..., fromfile_prefix_chars=..., argument_default=..., | ||
| 378 | + conflict_handler=..., add_help=..., allow_abbrev=..., exit_on_error=...): | ||
| 379 | + if not prog: | ||
| 380 | + prog = UniArgParser.get_cmd_name() | ||
| 381 | + self.prog = prog | ||
| 382 | + self.usage = usage | ||
| 383 | + self.description = description | ||
| 384 | + self.epilog = epilog | ||
| 385 | + self.parents = ... | ||
| 386 | + self.formatter_class = formatter_class | ||
| 387 | + self.prefix_chars = prefix_chars | ||
| 388 | + self.fromfile_prefix_chars = fromfile_prefix_chars | ||
| 389 | + self.argument_default = argument_default | ||
| 390 | + self.conflict_handler = conflict_handler | ||
| 391 | + self.add_help = add_help | ||
| 392 | + self.allow_abbrev = allow_abbrev | ||
| 393 | + self.exit_on_error = exit_on_error | ||
| 394 | + if parents != Ellipsis and parents: | ||
| 395 | + for parent in parents: | ||
| 396 | + if isinstance(parent, (ArgParser, SubParser)): | ||
| 397 | + self.arguments.extend(deepcopy(parent.arguments)) | ||
| 398 | + if self.subparserManager: | ||
| 399 | + self.subparserManager.union( | ||
| 400 | + deepcopy(parent.subparserManager)) | ||
| 401 | + else: | ||
| 402 | + self.subparserManager = deepcopy( | ||
| 403 | + parent.subparserManager) | ||
| 404 | + elif isinstance(parent, (GroupArg, ExclusiveGroupArg)): | ||
| 405 | + self.arguments.append(deepcopy(parent)) | ||
| 406 | + else: | ||
| 407 | + raise RuntimeError("unknown parent type") | ||
| 408 | + return self | ||
| 409 | + | ||
| 410 | + def add_argument(self, *name_or_flags: str, action=..., nargs=..., const=..., default: Any = ..., type=..., | ||
| 411 | + choices=..., required: bool = ..., help=..., metavar: Union[str, tuple[str, ...]] = ..., | ||
| 412 | + dest: str = ..., version: str = ..., **kwargs: Any): | ||
| 413 | + self.arguments.append(Argument(*name_or_flags, action=action, nargs=nargs, const=const, | ||
| 414 | + default=default, type=type, choices=choices, required=required, | ||
| 415 | + help=help, metavar=metavar, dest=dest, version=version, **kwargs)) | ||
| 416 | + | ||
| 417 | + def add_subparsers(self, title=..., description=..., prog=..., parser_class=..., action=..., | ||
| 418 | + option_string=..., dest=..., required=..., help=..., metavar=...): | ||
| 419 | + self.subparserManager = SubParserManager( | ||
| 420 | + title, description, prog, parser_class, action, option_string, dest, required, help, metavar) | ||
| 421 | + return self.subparserManager | ||
| 422 | + | ||
| 423 | + def add_mutually_exclusive_group(self, required=...): | ||
| 424 | + arg = ExclusiveGroupArg(required) | ||
| 425 | + self.arguments.append(arg) | ||
| 426 | + return arg | ||
| 427 | + | ||
| 428 | + def add_argument_group(self, title=..., description=...): | ||
| 429 | + arg = GroupArg(title, description) | ||
| 430 | + self.arguments.append(arg) | ||
| 431 | + return arg | ||
| 432 | + | ||
| 433 | + def set_defaults(self, **kwargs): | ||
| 434 | + self.argument_default = kwargs | ||
| 435 | + | ||
| 436 | + # 递归的判断命令以及子命令是否有-h这样的帮助选项 | ||
| 437 | + def add_helpdfs(self): | ||
| 438 | + if self.add_help == Ellipsis or self.add_help: | ||
| 439 | + self.add_argument('-h', '--help', help=ArgParser.DEFAULTHELP) | ||
| 440 | + if self.subparserManager: | ||
| 441 | + self.subparserManager.add_helpdfs() | ||
| 442 | + | ||
| 443 | + def parse_args(self, args=..., values=...): | ||
| 444 | + self.add_helpdfs() | ||
| 445 | + UniArgParser.finalParser = copy.deepcopy(self) | ||
| 446 | + | ||
| 447 | + def parse_intermixed_args(self, args=..., values=...): | ||
| 448 | + self.parse_args(args, values) | ||
| 449 | + | ||
| 450 | + def parse_known_intermixed_args(self, args=..., values=...): | ||
| 451 | + self.parse_args(args, values) | ||
| 452 | + | ||
| 453 | + def parse_known_args(self, args=..., values=...): | ||
| 454 | + self.parse_args(args, values) | ||
| 455 | + | ||
| 456 | + def add_val_help(self, value_map: Dict, help_map: Dict): | ||
| 457 | + for argument in self.arguments: | ||
| 458 | + argument.add_val_help(value_map, help_map) | ||
| 459 | + if self.subparserManager: | ||
| 460 | + self.subparserManager.add_val_help(value_map, help_map) | ||
| 461 | + | ||
| 462 | + def yield_cmd(self, cmd): | ||
| 463 | + """ | ||
| 464 | + :return: 返回是否取到了更多的非必选且之前没有选过的参数 | ||
| 465 | + """ | ||
| 466 | + global MORE | ||
| 467 | + more = False | ||
| 468 | + for argument in self.arguments: | ||
| 469 | + more = more or argument.yield_cmd(cmd) | ||
| 470 | + if more: | ||
| 471 | + MORE = False | ||
| 472 | + if more: | ||
| 473 | + return more | ||
| 474 | + if self.subparserManager: | ||
| 475 | + more = self.subparserManager.yield_cmd(cmd) | ||
| 476 | + return more | ||
| 477 | + | ||
| 478 | + def getCmds_Vals(self) -> Tuple[List, Dict, Dict]: | ||
| 479 | + global MORE | ||
| 480 | + commands, value_map, help_map = [], {}, {} | ||
| 481 | + """ | ||
| 482 | + 每次循环生成一个可行的命令格式, 在循环开始的时候设置全局变量MORE为True,然后递归的进行处理 | ||
| 483 | + 递归的每一层对应着命令的层级,每次先处理当前层的arguments,直到所有非必选的arguments都被选过之后在进入子命令去处理 | ||
| 484 | + """ | ||
| 485 | + while True: | ||
| 486 | + MORE = True | ||
| 487 | + cmd = [UniArgParser.get_cmd_name()] | ||
| 488 | + if self.yield_cmd(cmd): | ||
| 489 | + commands.append(cmd) | ||
| 490 | + else: | ||
| 491 | + break | ||
| 492 | + # 递归的获取参数可用的值的信息以及类型信息 | ||
| 493 | + self.add_val_help(value_map, help_map) | ||
| 494 | + return commands, value_map, help_map | ||
| 495 | + | ||
| 496 | + | ||
| 497 | +class SubParser(ArgParser): | ||
| 498 | + def __init__(self, | ||
| 499 | + name: str, | ||
| 500 | + help: str = ..., | ||
| 501 | + aliases: Sequence[str] = ..., | ||
| 502 | + prog=..., | ||
| 503 | + usage=..., | ||
| 504 | + description=..., | ||
| 505 | + epilog=..., | ||
| 506 | + parents=..., | ||
| 507 | + formatter_class=..., | ||
| 508 | + prefix_chars=..., | ||
| 509 | + fromfile_prefix_chars=..., | ||
| 510 | + argument_default=..., | ||
| 511 | + conflict_handler=..., | ||
| 512 | + add_help=..., | ||
| 513 | + allow_abbrev=..., | ||
| 514 | + exit_on_error=...) -> None: | ||
| 515 | + super().__init__(prog, usage, description, epilog, parents, formatter_class, prefix_chars, | ||
| 516 | + fromfile_prefix_chars, argument_default, conflict_handler, add_help, allow_abbrev, | ||
| 517 | + exit_on_error) | ||
| 518 | + self.name = name | ||
| 519 | + self.help = help if help != ArgParser.SUPPRESS else ArgParser.DEFAULTHELP | ||
| 520 | + self.aliases = aliases | ||
| 521 | + self.cur = False | ||
| 522 | + | ||
| 523 | + def add_helpdfs(self): | ||
| 524 | + if self.add_help == Ellipsis or self.add_help: | ||
| 525 | + self.add_argument('-h', '--help', help=ArgParser.DEFAULTHELP) | ||
| 526 | + | ||
| 527 | + def display(self, prefix=''): | ||
| 528 | + s = f"{prefix}Subcommand: {self.name}\t" | ||
| 529 | + params = simplify({ | ||
| 530 | + 'help': self.help, | ||
| 531 | + 'alias': self.aliases, | ||
| 532 | + 'prog': self.prog, | ||
| 533 | + 'usage': self.usage, | ||
| 534 | + 'description': self.description, | ||
| 535 | + 'epilog': self.epilog, | ||
| 536 | + 'parents': self.parents, | ||
| 537 | + 'formatter_class': self.formatter_class, | ||
| 538 | + 'prefix_chars': self.prefix_chars, | ||
| 539 | + 'fromfile_prefix_chars': self.fromfile_prefix_chars, | ||
| 540 | + 'argument_default': self.argument_default, | ||
| 541 | + 'conflict_handler': self.conflict_handler, | ||
| 542 | + 'add_help': self.add_help, | ||
| 543 | + 'allow_abbrev': self.allow_abbrev, | ||
| 544 | + 'exit_on_error': self.exit_on_error | ||
| 545 | + }) | ||
| 546 | + if params: | ||
| 547 | + s += f"{params}" | ||
| 548 | + for arg in self.arguments: | ||
| 549 | + s += '\n' | ||
| 550 | + s += "\n".join(f"{prefix}{line}" for line in str(arg).split('\n') if line) | ||
| 551 | + if self.subparserManager: | ||
| 552 | + s += f"\n{self.subparserManager.display(prefix)}" | ||
| 553 | + return s | ||
| 554 | + | ||
| 555 | + def yield_cmd(self, cmd): | ||
| 556 | + global MORE | ||
| 557 | + more = False | ||
| 558 | + name = f"^{self.name}" | ||
| 559 | + n = len(cmd) | ||
| 560 | + for argument in self.arguments: | ||
| 561 | + more = argument.yield_cmd(cmd) or more | ||
| 562 | + if more: | ||
| 563 | + if n != -1: | ||
| 564 | + cmd.insert(n, name) | ||
| 565 | + n = -1 | ||
| 566 | + MORE = False | ||
| 567 | + if more: | ||
| 568 | + return more | ||
| 569 | + if isinstance(self.argument_default, dict) and 'func' in self.argument_default and not self.cur and len( | ||
| 570 | + cmd) == n: | ||
| 571 | + self.cur = True | ||
| 572 | + cmd.insert(n, name) | ||
| 573 | + MORE = False | ||
| 574 | + return True | ||
| 575 | + if self.subparserManager: | ||
| 576 | + cmd.insert(n, name) | ||
| 577 | + more = self.subparserManager.yield_cmd(cmd) | ||
| 578 | + return more | ||
| 579 | + | ||
| 580 | + | ||
| 581 | +# 和optparse的OptionParse对象对应,这里采用了继承的方式,在此基础上添加自定义的对参数的处理逻辑 | ||
| 582 | +class Option(optparse.Option): | ||
| 583 | + ATTRS = ['action', | ||
| 584 | + 'type', | ||
| 585 | + 'dest', | ||
| 586 | + 'default', | ||
| 587 | + 'nargs', | ||
| 588 | + 'const', | ||
| 589 | + 'choices', | ||
| 590 | + 'callback', | ||
| 591 | + 'callback_args', | ||
| 592 | + 'callback_kwargs', | ||
| 593 | + 'help', | ||
| 594 | + 'metavar'] | ||
| 595 | + | ||
| 596 | + def __init__(self, *opts, **attrs: Any) -> None: | ||
| 597 | + self.attrs = {key: val for key, | ||
| 598 | + val in attrs.items() if key in self.ATTRS} | ||
| 599 | + super().__init__(*opts, **attrs) | ||
| 600 | + | ||
| 601 | + def __str__(self) -> str: | ||
| 602 | + opts = [] | ||
| 603 | + opts.extend(self._short_opts) | ||
| 604 | + opts.extend(self._long_opts) | ||
| 605 | + s = ','.join(opts) | ||
| 606 | + return f"{s}\n{self.attrs}" if self.attrs else s | ||
| 607 | + | ||
| 608 | + | ||
| 609 | +class OptParser(optparse.OptionParser): | ||
| 610 | + SUPPRESS_HELP = optparse.SUPPRESS_HELP | ||
| 611 | + SUPPRESS_USAGE = optparse.SUPPRESS_USAGE | ||
| 612 | + | ||
| 613 | + # optionparser中有帮助的信息的标签 | ||
| 614 | + ATTRS = [ | ||
| 615 | + 'usage', | ||
| 616 | + 'version', | ||
| 617 | + 'description', | ||
| 618 | + 'prog', | ||
| 619 | + 'epilog'] | ||
| 620 | + | ||
| 621 | + def __init__(self, | ||
| 622 | + usage: str = None, | ||
| 623 | + option_list: Iterable[optparse.Option] = None, | ||
| 624 | + option_class: type[optparse.Option] = Option, | ||
| 625 | + version: str = None, | ||
| 626 | + conflict_handler: str = "error", | ||
| 627 | + description: str = None, | ||
| 628 | + formatter: optparse.HelpFormatter = None, | ||
| 629 | + add_help_option: bool = True, | ||
| 630 | + prog: str = None, | ||
| 631 | + epilog: str = None) -> None: | ||
| 632 | + if not prog: | ||
| 633 | + prog = UniArgParser.get_cmd_name() | ||
| 634 | + super().__init__(usage=usage, option_list=option_list, option_class=option_class, version=version, | ||
| 635 | + conflict_handler=conflict_handler, description=description, | ||
| 636 | + formatter=formatter, add_help_option=add_help_option, prog=prog, epilog=epilog) | ||
| 637 | + | ||
| 638 | + def OptionParser(self, *args, **kwargs): | ||
| 639 | + if args and len(args) == 1 and ('usage' not in kwargs or not kwargs['usage']): | ||
| 640 | + kwargs['usage'] = args[0] | ||
| 641 | + super().__init__(**kwargs) | ||
| 642 | + return self | ||
| 643 | + | ||
| 644 | + def add_option(self, *args, **kwargs): | ||
| 645 | + opt = Option(*args, **kwargs) | ||
| 646 | + super().add_option(opt) | ||
| 647 | + | ||
| 648 | + def OptionGroup(self, parser, title, description=None): | ||
| 649 | + return OptGroup(parser, title, description) | ||
| 650 | + | ||
| 651 | + def parse_args(self, args=None, vals=None): | ||
| 652 | + if self.usage: | ||
| 653 | + self.usage = self.usage.replace( | ||
| 654 | + '%prog', UniArgParser.get_cmd_name()) | ||
| 655 | + UniArgParser.finalParser = copy.deepcopy(self) | ||
| 656 | + super().parse_args() | ||
| 657 | + | ||
| 658 | + def get_num(self) -> int: | ||
| 659 | + num = len(self.option_list) | ||
| 660 | + for group in self.option_groups: | ||
| 661 | + if isinstance(group, OptGroup): | ||
| 662 | + num += group.get_num() | ||
| 663 | + else: | ||
| 664 | + num += len(group.option_list) | ||
| 665 | + return num | ||
| 666 | + | ||
| 667 | + def __str__(self) -> str: | ||
| 668 | + attrs = {attr: getattr(self, attr) | ||
| 669 | + for attr in self.ATTRS if getattr(self, attr)} | ||
| 670 | + s = f"{attrs}" if attrs else '' | ||
| 671 | + for option in self.option_list: | ||
| 672 | + if s: | ||
| 673 | + s += f"\n{option}" | ||
| 674 | + else: | ||
| 675 | + s = str(option) | ||
| 676 | + for group in self.option_groups: | ||
| 677 | + if s: | ||
| 678 | + s += f"\n\n{group}" | ||
| 679 | + else: | ||
| 680 | + s = str(group) | ||
| 681 | + return s | ||
| 682 | + | ||
| 683 | + # optparse包的option都是可选项,直接遍历 | ||
| 684 | + def getCmds_Vals(self) -> Tuple[List, Dict, Dict]: | ||
| 685 | + if not UniArgParser.CurCommand: | ||
| 686 | + return None | ||
| 687 | + name = UniArgParser.get_cmd_name() | ||
| 688 | + cmds, vals, typeVals, options = [], {}, {}, [] | ||
| 689 | + options.extend(self.option_list) | ||
| 690 | + for groups in self.option_groups: | ||
| 691 | + options.extend(groups.option_list) | ||
| 692 | + for option in options: | ||
| 693 | + opts = [] | ||
| 694 | + opts.extend(option._short_opts) | ||
| 695 | + opts.extend(option._long_opts) | ||
| 696 | + opts.sort(key=lambda x: len(x)) | ||
| 697 | + label = opts[-1].lstrip('-').replace('-', '_').upper() | ||
| 698 | + if 'metavar' in option.attrs: | ||
| 699 | + label = option.attrs['metavar'].upper() | ||
| 700 | + elif 'dest' in option.attrs: | ||
| 701 | + label = option.attrs['dest'].upper() | ||
| 702 | + if ('action' in option.attrs and option.attrs['action'] in NOVALARGS) or '-h' in opts or '--help' in opts: | ||
| 703 | + cmds.append([f"{name}", opts[0]]) | ||
| 704 | + else: | ||
| 705 | + cmds.append([f"{name}", opts[0], label]) | ||
| 706 | + key = f"{name},{label}" | ||
| 707 | + if 'choices' in option.attrs: | ||
| 708 | + vals[key] = option.attrs['choices'] if option.attrs['choices'] else [] | ||
| 709 | + else: | ||
| 710 | + vals[key] = [] | ||
| 711 | + if 'default' in option.attrs and option.attrs['default'] is not None: | ||
| 712 | + vals[key].append(option.attrs['default']) | ||
| 713 | + # if 'const' in option.attrs and option.attrs['const'] is not None: | ||
| 714 | + # vals[key] = option.attrs['const'] | ||
| 715 | + if not vals[key]: | ||
| 716 | + vals.pop(key) | ||
| 717 | + if 'type' in option.attrs: | ||
| 718 | + typeVals[key] = option.attrs['type'] | ||
| 719 | + return (cmds, vals, typeVals) | ||
| 720 | + | ||
| 721 | + | ||
| 722 | +class OptGroup(optparse.OptionGroup): | ||
| 723 | + def __init__(self, parser, title, description=None) -> None: | ||
| 724 | + super().__init__(parser, title, description=description) | ||
| 725 | + | ||
| 726 | + def add_option(self, *args, **kwargs): | ||
| 727 | + opt = Option(*args, **kwargs) | ||
| 728 | + super().add_option(opt) | ||
| 729 | + | ||
| 730 | + def get_num(self) -> int: | ||
| 731 | + return len(self.option_list) | ||
| 732 | + | ||
| 733 | + def __str__(self) -> str: | ||
| 734 | + params = { | ||
| 735 | + 'title': self.title | ||
| 736 | + } | ||
| 737 | + if self.description: | ||
| 738 | + params['description'] = self.description | ||
| 739 | + s = f"Group: {params}" | ||
| 740 | + for option in self.option_list: | ||
| 741 | + s += f"\n{option}" | ||
| 742 | + return s | ||
| 743 | + | ||
| 744 | + | ||
| 745 | +class UniArgParser: | ||
| 746 | + PARSERS = ('argparse.ArgumentParser', | ||
| 747 | + 'optparse.OptionParser', 'argparse', 'optparse') | ||
| 748 | + | ||
| 749 | + finalParser = None | ||
| 750 | + CurCommand = None | ||
| 751 | + | ||
| 752 | + | ||
| 753 | + def build(cls, parser: str, args=[], keywords={}): | ||
| 754 | + """ | ||
| 755 | + 工厂方法,根据传入的parser的不同创建不同的参数处理对象 | ||
| 756 | + """ | ||
| 757 | + if parser in cls.PARSERS: | ||
| 758 | + return ArgParser(**keywords) if parser.startswith('argparse') else OptParser(**keywords) | ||
| 759 | + return None | ||
| 760 | + | ||
| 761 | + | ||
| 762 | + def get_cmd_name(cls): | ||
| 763 | + """ | ||
| 764 | + 获取当前处理的命令的名字 | ||
| 765 | + """ | ||
| 766 | + if cls.CurCommand: | ||
| 767 | + curCmd = cls.CurCommand | ||
| 768 | + return curCmd.path[curCmd.path.rfind('/') + 1:] | ||
| 769 | + return "%prog" | ||
| @@ -0,0 +1,1046 @@ | |||
| 1 | +# This program is licensed under Mulan PSL v2. | ||
| 2 | +# You can use it according to the terms and conditions of the Mulan PSL v2. | ||
| 3 | +# http://license.coscl.org.cn/MulanPSL2 | ||
| 4 | +# THIS PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, | ||
| 5 | +# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, | ||
| 6 | +# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. | ||
| 7 | +# See the Mulan PSL v2 for more details. | ||
| 8 | +#################################### | ||
| 9 | +# @Author : hourui | ||
| 10 | +# @Contact : softgreet@qq.com | ||
| 11 | +# @Date : 2023-10-12 12:00:00 | ||
| 12 | +# @License : Mulan PSL v2 | ||
| 13 | +# @Version : 1.0 | ||
| 14 | +# @Desc : python中参数解析 | ||
| 15 | +##################################### | ||
| 16 | + | ||
| 17 | +#coding=UTF-8 | ||
| 18 | + | ||
| 19 | +import ast | ||
| 20 | +import os | ||
| 21 | +import subprocess | ||
| 22 | +import sys | ||
| 23 | +from abc import ABCMeta | ||
| 24 | +from copy import deepcopy | ||
| 25 | +from typing import Union, Iterable | ||
| 26 | +import timeout_decorator | ||
| 27 | +from pebble import ProcessPool | ||
| 28 | +from python_parse.parser import UniArgParser, ArgParser, OptParser | ||
| 29 | +from python_parse.commonlib import FileType, compare_ast | ||
| 30 | +from logger import log | ||
| 31 | + | ||
| 32 | +# 检测到这些函数调用时不予执行 | ||
| 33 | +no_exe_label = { | ||
| 34 | + 'input', | ||
| 35 | + 'print', | ||
| 36 | + 'os.system', | ||
| 37 | + 'time.sleep', | ||
| 38 | + 'subprocess' | ||
| 39 | +} | ||
| 40 | + | ||
| 41 | +pool = ProcessPool() | ||
| 42 | +log_file = '' | ||
| 43 | + | ||
| 44 | + | ||
| 45 | +def subprocess_task(code='', global_env={}): | ||
| 46 | + """ | ||
| 47 | + Args: | ||
| 48 | + code: 放在子进程中尝试执行的代码,防止因执行所需系统环境和参数的缺失导致的崩溃 | ||
| 49 | + global_env: python代码的执行时的环境 | ||
| 50 | + """ | ||
| 51 | + stdin_bac, stdout_bac, stderr_bac = sys.stdin, sys.stdout, sys.stderr | ||
| 52 | + sys.stdin, sys.stdout, sys.stderr = None, None, None | ||
| 53 | + # 对于因执行所需系统环境和参数的缺失导致的异常不作处理 | ||
| 54 | + try: | ||
| 55 | + ret_sink = '__RET_SINK' | ||
| 56 | + exec(f"{ret_sink}={code}", global_env) | ||
| 57 | + return global_env[ret_sink] | ||
| 58 | + except: | ||
| 59 | + log.warning("因模拟执行python代码时的输入参数未知以及所需系统环境的缺失导致的异常", log_file) | ||
| 60 | + finally: | ||
| 61 | + sys.stdin, sys.stdout, sys.stderr = stdin_bac, stdout_bac, stderr_bac | ||
| 62 | + | ||
| 63 | + | ||
| 64 | + | ||
| 65 | +def timing_wrap_func(func, args, keywords): | ||
| 66 | + """ | ||
| 67 | + 封装需要执行的函数,超时退出 | ||
| 68 | + :param: 执行的回调函数func及其参数args, keywords | ||
| 69 | + """ | ||
| 70 | + stdin_bac, stdout_bac, stderr_bac = sys.stdin, sys.stdout, sys.stderr | ||
| 71 | + sys.stdin, sys.stdout, sys.stderr = None, None, None | ||
| 72 | + try: | ||
| 73 | + return func(*args, **keywords) | ||
| 74 | + except: | ||
| 75 | + log.warning("因模拟执行python代码时的输入参数未知以及所需系统环境的缺失导致的异常", log_file) | ||
| 76 | + finally: | ||
| 77 | + sys.stdin, sys.stdout, sys.stderr = stdin_bac, stdout_bac, stderr_bac | ||
| 78 | + | ||
| 79 | + | ||
| 80 | +class ImportObj: | ||
| 81 | + """ | ||
| 82 | + 引用对象, 封装了引用的路径,如果不封装直接用string表示的话将无法区分是一个引用还是一个值为字符串的变量。 | ||
| 83 | + """ | ||
| 84 | + | ||
| 85 | + def __init__(self, val=None) -> None: | ||
| 86 | + self.val: str = val | ||
| 87 | + | ||
| 88 | + # 引用路径的合并 | ||
| 89 | + def __add__(self, obj): | ||
| 90 | + if isinstance(obj, str): | ||
| 91 | + return ImportObj(f"{self.val}.{obj}") | ||
| 92 | + elif isinstance(obj, ImportObj): | ||
| 93 | + return ImportObj(f"{self.val}.{obj.val}") | ||
| 94 | + else: | ||
| 95 | + raise RuntimeError("Wrong parameter type") | ||
| 96 | + | ||
| 97 | + def __str__(self) -> str: | ||
| 98 | + return self.val | ||
| 99 | + | ||
| 100 | + | ||
| 101 | +class Environment(metaclass=ABCMeta): | ||
| 102 | + """python执行环境的一个不完全简单模拟 | ||
| 103 | + 作为抽象基类存储了当前环境下定义和引用的变量,函数以及类;同时定义了对各种ast节点的通用处理方法,即命名为exec_{ast节点类型}的方法 | ||
| 104 | + Attributes: | ||
| 105 | + driver: ast节点的处理方法的驱动,通过表驱动的方式用来判断该如何处理特定类型的ast节点,具体通过类方法exec_driver来使用和维护 | ||
| 106 | + UNSCAN/SCANNING/SCANNED: 当前环境的扫描状态 | ||
| 107 | + """ | ||
| 108 | + UNSCAN = 0 | ||
| 109 | + SCANNING = 1 | ||
| 110 | + SCANNED = 2 | ||
| 111 | + | ||
| 112 | + driver = None | ||
| 113 | + | ||
| 114 | + | ||
| 115 | + def exec_driver(cls, env, node: ast.AST): | ||
| 116 | + """ | ||
| 117 | + Args: | ||
| 118 | + env:通过类方法来调用的,所以需要再传入一个Environment对象,调用的时候通常是传递self | ||
| 119 | + node: 抽象语法树节点 | ||
| 120 | + """ | ||
| 121 | + if not cls.driver: | ||
| 122 | + # 为不需要进行处理的节点定义的默认不作任何处理的函数 | ||
| 123 | + def default(m, n): return None | ||
| 124 | + | ||
| 125 | + cls.collection = { | ||
| 126 | + ast.List: list, | ||
| 127 | + ast.Tuple: tuple, | ||
| 128 | + ast.Set: set, | ||
| 129 | + } | ||
| 130 | + cls.bin_op = { | ||
| 131 | + ast.Add: lambda x, y: x + y, | ||
| 132 | + ast.Sub: lambda x, y: x - y, | ||
| 133 | + ast.Mult: lambda x, y: x * y, | ||
| 134 | + ast.Div: lambda x, y: x / y, | ||
| 135 | + ast.FloorDiv: lambda x, y: x // y, | ||
| 136 | + ast.Mod: lambda x, y: x % y, | ||
| 137 | + ast.Pow: lambda x, y: x ** y, | ||
| 138 | + ast.LShift: lambda x, y: x << y, | ||
| 139 | + ast.RShift: lambda x, y: x >> y, | ||
| 140 | + ast.BitOr: lambda x, y: x | y, | ||
| 141 | + ast.BitAnd: lambda x, y: x & y, | ||
| 142 | + ast.MatMult: lambda x, y: x @ y | ||
| 143 | + } | ||
| 144 | + cls.driver = { | ||
| 145 | + ast.Import: cls.exec_import_statement, | ||
| 146 | + ast.ImportFrom: cls.exec_import_from, | ||
| 147 | + ast.ClassDef: cls.exec_cls_or_fun_def, | ||
| 148 | + ast.FunctionDef: cls.exec_cls_or_fun_def, | ||
| 149 | + ast.Expr: cls.exec_expr, | ||
| 150 | + ast.Call: cls.exec_call, | ||
| 151 | + ast.Return: cls.exec_return, | ||
| 152 | + ast.If: cls.exec_if_statement, | ||
| 153 | + ast.With: cls.exec_block, | ||
| 154 | + ast.Try: cls.exec_block, | ||
| 155 | + ast.While: cls.exec_block, | ||
| 156 | + ast.For: cls.exec_block, | ||
| 157 | + ast.Constant: cls.exec_constant, | ||
| 158 | + ast.Attribute: cls.exec_attribute, | ||
| 159 | + ast.Name: cls.exec_name, | ||
| 160 | + ast.Assign: cls.exec_assign, | ||
| 161 | + ast.AugAssign: cls.exec_aug_assign, | ||
| 162 | + ast.List: cls.exec_collection, | ||
| 163 | + ast.Tuple: cls.exec_collection, | ||
| 164 | + ast.Set: cls.exec_collection, | ||
| 165 | + ast.Dict: cls.exec_dict, | ||
| 166 | + ast.ListComp: cls.exec_collection_comp, | ||
| 167 | + ast.SetComp: cls.exec_collection_comp, | ||
| 168 | + ast.GeneratorExp: cls.exec_collection_comp, | ||
| 169 | + ast.DictComp: cls.exec_collection_comp, | ||
| 170 | + ast.Delete: default, | ||
| 171 | + ast.Break: default, | ||
| 172 | + ast.Continue: default, | ||
| 173 | + ast.Raise: default, | ||
| 174 | + ast.arguments: default, | ||
| 175 | + ast.Compare: default, | ||
| 176 | + ast.Global: default, | ||
| 177 | + ast.Lambda: default, | ||
| 178 | + ast.BinOp: cls.exec_bin_op, | ||
| 179 | + ast.UnaryOp: cls.exec_unary_op, | ||
| 180 | + ast.BoolOp: default, | ||
| 181 | + ast.IfExp: default, | ||
| 182 | + ast.Pass: default, | ||
| 183 | + ast.Slice: default, | ||
| 184 | + ast.Subscript: default, | ||
| 185 | + ast.Assert: default, | ||
| 186 | + ast.Yield: default, | ||
| 187 | + ast.YieldFrom: default, | ||
| 188 | + ast.Starred: default, | ||
| 189 | + ast.JoinedStr: default | ||
| 190 | + } | ||
| 191 | + return cls.driver[type(node)](env, node) | ||
| 192 | + | ||
| 193 | + def __init__(self, node: Union[ast.Module, ast.FunctionDef, ast.ClassDef], parent_env) -> None: | ||
| 194 | + """ | ||
| 195 | + Args: | ||
| 196 | + node: 和当前环境相对应的ast节点,节点类型只能是ast.Module, ast.FunctionDef, ast.ClassDef, | ||
| 197 | + 分别对应了python执行时的模块级别的全局环境(ModEnv),函数和方法内部的局部环境(FuncEnv)以及类环境(ClsEnv) | ||
| 198 | + parent_env: 当前环境的父环境。ModEnv没有父环境。而三种环境下面都可能会有子FuncEnv,子ClsEnv。 | ||
| 199 | + Attributes: | ||
| 200 | + self.unknown_import_star: 未知的from xxx import * | ||
| 201 | + self.imports: 对python内置变量、方法和对象,第三方包的变量、方法和对象以及无法求出值的包内其它模块的变量的引用 | ||
| 202 | + self.variables: 存储了当前环境下定义的变量对象和方法/类的定义以及从包内其它模块导入的可以求出值的变量和方法/类定义 | ||
| 203 | + self.import_codes: 存储了检测到的import语句,用于在执行一些未知方法时模拟Python的命名空间 | ||
| 204 | + self.scanned: 当前Environment是否被扫描过,扫描过的将不再重复检测方法/类定义 | ||
| 205 | + """ | ||
| 206 | + self.node = node | ||
| 207 | + self.parent_env: Environment = parent_env | ||
| 208 | + self.unknown_import_star = set() | ||
| 209 | + self.imports = {} | ||
| 210 | + self.variables = {} | ||
| 211 | + self.import_codes = [] | ||
| 212 | + self.scanned = Environment.UNSCAN | ||
| 213 | + | ||
| 214 | + def exec(self, args: list = None, keywords: dict = None): | ||
| 215 | + if self.scanned == Environment.UNSCAN: | ||
| 216 | + self.scanned = Environment.SCANNING | ||
| 217 | + ret = self.exec_codeblock(list(ast.iter_child_nodes(self.node))) | ||
| 218 | + self.scanned = Environment.SCANNED | ||
| 219 | + return ret | ||
| 220 | + | ||
| 221 | + def exec_codeblock(self, nodes: list[ast.AST]): | ||
| 222 | + ret = None | ||
| 223 | + for node in nodes: | ||
| 224 | + val = self.exec_driver(self, node) | ||
| 225 | + # 检测的目的是检测到所有语句,所以遇到return语句并不真的返回,并且选择最后一个作为返回值 | ||
| 226 | + if val and isinstance(node, ast.Return): | ||
| 227 | + ret = val | ||
| 228 | + return ret | ||
| 229 | + | ||
| 230 | + def exec_import_statement(self, node: ast.Import) -> None: | ||
| 231 | + """处理import语句 | ||
| 232 | + 能成功求解出值的放入Variable中,否则放入Import中 | ||
| 233 | + """ | ||
| 234 | + if self.scanned != Environment.SCANNING: | ||
| 235 | + return | ||
| 236 | + for alias in node.names: | ||
| 237 | + name = alias.asname if alias.asname else alias.name | ||
| 238 | + target = self.locate(alias.name) | ||
| 239 | + if target: | ||
| 240 | + self.variables[name] = target | ||
| 241 | + else: | ||
| 242 | + self.imports[name] = ImportObj(alias.name) | ||
| 243 | + self.import_codes.append(ast.unparse(node)) | ||
| 244 | + | ||
| 245 | + def exec_import_from(self, node: ast.ImportFrom) -> None: | ||
| 246 | + if self.scanned != Environment.SCANNING: | ||
| 247 | + return None | ||
| 248 | + if node.level != 0: | ||
| 249 | + # 处理从相对路径.和..导入的情况 | ||
| 250 | + path = self.get_root_env().belongs.get_path() | ||
| 251 | + if path.endswith('__init__'): | ||
| 252 | + path = path[:-9] | ||
| 253 | + node.level -= 1 | ||
| 254 | + while node.level != 0: | ||
| 255 | + path = path[:path.rfind('.')] | ||
| 256 | + node.level -= 1 | ||
| 257 | + node.module = f"{path}.{node.module}" if node.module else path | ||
| 258 | + for alias in node.names: | ||
| 259 | + name = alias.asname if alias.asname else alias.name | ||
| 260 | + if name != '*': | ||
| 261 | + # 处理from xxx import * | ||
| 262 | + module_path = f"{node.module}.{alias.name}" | ||
| 263 | + val = self.locate(module_path) | ||
| 264 | + if val: | ||
| 265 | + self.variables[name] = val | ||
| 266 | + else: | ||
| 267 | + self.imports[name] = ImportObj(module_path) | ||
| 268 | + self.import_codes.append(ast.unparse(node)) | ||
| 269 | + else: | ||
| 270 | + module = self.locate(node.module) | ||
| 271 | + if module: | ||
| 272 | + # 定位到了Package/Module这样的Python包内文件组织结构上的抽象类时需要转化为对应的初始模块(__init__.py)/ModEnv | ||
| 273 | + if isinstance(module, Package): | ||
| 274 | + module = module.init | ||
| 275 | + if isinstance(module, Module): | ||
| 276 | + source_env = module.env | ||
| 277 | + else: | ||
| 278 | + source_env = module | ||
| 279 | + if not source_env.scanned: | ||
| 280 | + source_env.exec() | ||
| 281 | + self.imports.update(source_env.imports) | ||
| 282 | + self.variables.update(source_env.variables) | ||
| 283 | + else: | ||
| 284 | + self.unknown_import_star.add(ImportObj(node.module)) | ||
| 285 | + self.import_codes.append(ast.unparse(node)) | ||
| 286 | + | ||
| 287 | + def exec_cls_or_fun_def(self, node: Union[ast.ClassDef, ast.FunctionDef]) -> None: | ||
| 288 | + """ | ||
| 289 | + 存储函数和类定义到Variable中 | ||
| 290 | + """ | ||
| 291 | + if self.scanned != Environment.SCANNING: | ||
| 292 | + return | ||
| 293 | + if isinstance(node, ast.ClassDef): | ||
| 294 | + self.variables[node.name] = ClsEnv(node, self) | ||
| 295 | + elif isinstance(node, ast.FunctionDef): | ||
| 296 | + self.variables[node.name] = FuncEnv(node, self) | ||
| 297 | + else: | ||
| 298 | + raise RuntimeError("Wrong args in exec_ClsOrFunDef!") | ||
| 299 | + | ||
| 300 | + def dfs_assign(self, targets: Union[list, tuple], vals: Iterable): | ||
| 301 | + n = len(targets) | ||
| 302 | + if len(vals) != n: | ||
| 303 | + vals = [None for i in range(n)] | ||
| 304 | + for i in range(n): | ||
| 305 | + target = str(targets[i]) | ||
| 306 | + if (vals[i] is not None) or (target not in self.variables): | ||
| 307 | + self.variables[target] = vals[i] | ||
| 308 | + | ||
| 309 | + def exec_assign(self, node: ast.Assign) -> None: | ||
| 310 | + """ | ||
| 311 | + 处理赋值语句, 因为Python支持a,(b,[c,d],e),f这样的递归赋值,需要递归的进行处理(调用dfs_Assign方法) | ||
| 312 | + """ | ||
| 313 | + val = self.exec_driver(self, node.value) | ||
| 314 | + targets = [self.exec_driver(self, target) for target in node.targets] | ||
| 315 | + if isinstance(targets[0], (tuple, list)): | ||
| 316 | + if not isinstance(val, Iterable): | ||
| 317 | + val = [val] | ||
| 318 | + self.dfs_assign(targets[0], val) | ||
| 319 | + else: | ||
| 320 | + for target in targets: | ||
| 321 | + if not target: | ||
| 322 | + continue | ||
| 323 | + if isinstance(target, ImportObj): | ||
| 324 | + target = target.val | ||
| 325 | + if target.startswith('self.'): | ||
| 326 | + cls_env = self.get_cls_env() | ||
| 327 | + cls_env.variables[target[5:]] = val | ||
| 328 | + else: | ||
| 329 | + self.variables[target] = val | ||
| 330 | + else: | ||
| 331 | + raise RuntimeError("unknown ret type") | ||
| 332 | + | ||
| 333 | + def exec_aug_assign(self, node: ast.AugAssign) -> None: | ||
| 334 | + trans_bin_op_node = ast.BinOp() | ||
| 335 | + trans_bin_op_node.left = deepcopy(node.target) | ||
| 336 | + trans_bin_op_node.left.ctx = ast.Load() | ||
| 337 | + trans_bin_op_node.op = node.op | ||
| 338 | + trans_bin_op_node.right = node.value | ||
| 339 | + trans_assign_node = ast.Assign() | ||
| 340 | + trans_assign_node.targets = [node.target] | ||
| 341 | + trans_assign_node.value = trans_bin_op_node | ||
| 342 | + # 防止在ast.unparse查看对应代码时出错,所以给了个虚假的临时行号1,但它不影响unparse的结果 | ||
| 343 | + trans_assign_node.lineno = 1 | ||
| 344 | + self.exec_driver(self, trans_assign_node) | ||
| 345 | + | ||
| 346 | + def exec_if_statement(self, node: ast.If): | ||
| 347 | + # 当测试条件是__name__ == '__main__'时只有启动模块才往下执行内部的代码块 | ||
| 348 | + target = ast.parse( | ||
| 349 | + "if __name__ == '__main__':\n\tpass", mode='exec').body[0].test | ||
| 350 | + if (not compare_ast(node.test, target)) or self.booted(): | ||
| 351 | + return self.exec_codeblock(node.body) | ||
| 352 | + | ||
| 353 | + def exec_block(self, node: Union[ast.With, ast.Try, ast.While, ast.For]): | ||
| 354 | + return self.exec_codeblock(node.body) | ||
| 355 | + | ||
| 356 | + def exec_expr(self, node: ast.Expr) -> None: | ||
| 357 | + self.exec_driver(self, node.value) | ||
| 358 | + | ||
| 359 | + def exec_call(self, node: ast.Call): | ||
| 360 | + func = self.exec_driver(self, node.func) | ||
| 361 | + node_args, node_keywords = node.args, node.keywords | ||
| 362 | + args, keywords = [], {} | ||
| 363 | + if node_args: | ||
| 364 | + if isinstance(node_args[-1], ast.Starred): | ||
| 365 | + star_val = self.exec_driver(self, node_args[-1].value) | ||
| 366 | + if isinstance(star_val, Iterable): | ||
| 367 | + args.extend(star_val) | ||
| 368 | + node_args = node_args[:-1] | ||
| 369 | + for arg in node_args: | ||
| 370 | + val = self.exec_driver(self, arg) | ||
| 371 | + args.append(val.val if isinstance(val, ImportObj) else val) | ||
| 372 | + if node_keywords: | ||
| 373 | + if not node_keywords[-1].arg: | ||
| 374 | + star_val = self.exec_driver(self, node_keywords[-1].value) | ||
| 375 | + if isinstance(star_val, dict): | ||
| 376 | + keywords.update(star_val) | ||
| 377 | + node_keywords = node_keywords[:-1] | ||
| 378 | + for keyword in node_keywords: | ||
| 379 | + val = self.exec_driver(self, keyword.value) | ||
| 380 | + if isinstance(val, Environment): | ||
| 381 | + val = str(val) | ||
| 382 | + keywords[keyword.arg] = val.val if isinstance( | ||
| 383 | + val, ImportObj) else val | ||
| 384 | + if func: | ||
| 385 | + # 对类名的调用视为对类的__init__方法的调用,在该类之前未被扫描过的时候先执行一遍 | ||
| 386 | + if isinstance(func, ClsEnv): | ||
| 387 | + if func.scanned == Environment.UNSCAN: | ||
| 388 | + func.exec() | ||
| 389 | + if '__init__' in func.variables: | ||
| 390 | + func.variables['__init__'].exec(args, keywords) | ||
| 391 | + return func | ||
| 392 | + # 对自身的递归调用不予执行 | ||
| 393 | + if isinstance(func, FuncEnv): | ||
| 394 | + if func != self: | ||
| 395 | + return func.exec(args, keywords) | ||
| 396 | + return None | ||
| 397 | + elif isinstance(func, ImportObj): | ||
| 398 | + func = func.val | ||
| 399 | + # 程序国际化时对字符串的封装,_("xxx")可以将"xxx"转化为对应国家地区的文字 | ||
| 400 | + if func == '_': | ||
| 401 | + return args[0] if args else None | ||
| 402 | + # 通过load_entry_point根据group和entry_name动态加载软件包真正的执行入口函数时的处理 | ||
| 403 | + if func == 'load_entry_point': | ||
| 404 | + _, group, entry_name = args | ||
| 405 | + name = self.get_entry(group, entry_name) | ||
| 406 | + target = self.locate(name) | ||
| 407 | + if isinstance(target, FuncEnv): | ||
| 408 | + return target.exec(args, keywords) | ||
| 409 | + return None | ||
| 410 | + # 对super调用的处理,仅处理了和参数相关的部分,其它调用不予处理 | ||
| 411 | + if func == "super": | ||
| 412 | + if not args or args[0] == self.get_cls_env(): | ||
| 413 | + for base in self.get_cls_env().bases: | ||
| 414 | + if isinstance(base, ArgParser) or isinstance(base, OptParser): | ||
| 415 | + return base | ||
| 416 | + return None | ||
| 417 | + # 处理self调用 | ||
| 418 | + if func.startswith('self.'): | ||
| 419 | + cls_env = self.get_cls_env() | ||
| 420 | + ok, ret = cls_env.find(func[5:]) | ||
| 421 | + if ok: | ||
| 422 | + return ret | ||
| 423 | + if func in UniArgParser.PARSERS: | ||
| 424 | + # 创建参数处理库optparse/argparse的参数parser对象时转为对Parser.py中自定义参数parser的对象的创建 | ||
| 425 | + return UniArgParser.build(func, args, keywords) | ||
| 426 | + else: | ||
| 427 | + # 未知的引用方法的执行 | ||
| 428 | + try: | ||
| 429 | + keywords = ','.join( | ||
| 430 | + f"{key}={val}" for key, val in keywords.items()) | ||
| 431 | + args = ','.join(f"'{arg}'" if isinstance( | ||
| 432 | + arg, str) else str(arg) for arg in args) | ||
| 433 | + if keywords and args: | ||
| 434 | + code = "{}({},{})".format(func, args, keywords) | ||
| 435 | + elif keywords: | ||
| 436 | + code = "{}({})".format(func, keywords) | ||
| 437 | + elif args: | ||
| 438 | + code = "{}({})".format(func, args) | ||
| 439 | + else: | ||
| 440 | + code = "{}()".format(func) | ||
| 441 | + if func not in no_exe_label: | ||
| 442 | + return self.exec_code(code) | ||
| 443 | + except: | ||
| 444 | + # 屏蔽处理未知引用方法导致的异常 | ||
| 445 | + log.warning("因模拟执行python代码时的输入参数未知以及所需系统环境的缺失导致的异常", log_file) | ||
| 446 | + elif callable(func): | ||
| 447 | + # 可调用对象的处理,包括内置方法(str.split,list.append等)的执行和自定义参数parser对象的方法的执行 | ||
| 448 | + return timing_wrap_func(func, args=args, keywords=keywords) | ||
| 449 | + return None | ||
| 450 | + | ||
| 451 | + def exec_return(self, node: ast.Return): | ||
| 452 | + if node.value: | ||
| 453 | + return self.exec_driver(self, node.value) | ||
| 454 | + return None | ||
| 455 | + | ||
| 456 | + def exec_constant(self, node: ast.Constant): | ||
| 457 | + return node.value | ||
| 458 | + | ||
| 459 | + def exec_attribute(self, node: ast.Attribute): | ||
| 460 | + """ 处理属性Attribute | ||
| 461 | + 属性在ast节点中被表示为:{node.value}.{node.attr},且使用属性的方式有ast.Load,ast.Store,ast.Del | ||
| 462 | + ast.Load需要去加载属性的值 | ||
| 463 | + ast.Store只需要返回对应的ImportObj对象,方便后续赋值 | ||
| 464 | + ast.Del不予处理,目标是检测代码而不是真的执行,不过仍然和ast.Store一样返回了对应的ImportObj对象 | ||
| 465 | + """ | ||
| 466 | + val = self.exec_driver(self, node.value) | ||
| 467 | + if val: | ||
| 468 | + # node.value是Package对象时递归的去寻找node.attr,对应于从包名开始书写的Python表达式,eg: os.path.xxx | ||
| 469 | + if isinstance(val, Package): | ||
| 470 | + return val.dfs_locate([node.attr]) | ||
| 471 | + # node.value是Module对象时需要转化为Module对应的ModEnv,然后在后续在ModEnv中进行寻找 | ||
| 472 | + if isinstance(val, Module): | ||
| 473 | + val = val.env | ||
| 474 | + if isinstance(node.ctx, ast.Load): | ||
| 475 | + if isinstance(val, (ModEnv, ClsEnv)): | ||
| 476 | + return val.find(node.attr)[1] | ||
| 477 | + elif isinstance(val, ImportObj): | ||
| 478 | + if val.val == 'self': | ||
| 479 | + ok, ret = self.get_cls_env().find(node.attr) | ||
| 480 | + if ok: | ||
| 481 | + return ret | ||
| 482 | + val = val + node.attr | ||
| 483 | + if val.val in no_exe_label: | ||
| 484 | + return None | ||
| 485 | + ok, ret = self.find(val.val) | ||
| 486 | + if ok: | ||
| 487 | + return ret | ||
| 488 | + if val.val not in UniArgParser.PARSERS: | ||
| 489 | + return val | ||
| 490 | + else: | ||
| 491 | + return UniArgParser.build(val.val) | ||
| 492 | + # 可能在之前已经得到了一个对象,即node.value是一个对象,此时通过内置函数直接返回对应的属性对象 | ||
| 493 | + elif hasattr(val, node.attr): | ||
| 494 | + return getattr(val, node.attr) | ||
| 495 | + else: | ||
| 496 | + return None | ||
| 497 | + else: | ||
| 498 | + if isinstance(val, (ModEnv, ClsEnv)): | ||
| 499 | + val = ImportObj(val.get_path()) | ||
| 500 | + if isinstance(val, ImportObj): | ||
| 501 | + return val + node.attr | ||
| 502 | + return None | ||
| 503 | + | ||
| 504 | + def exec_name(self, node: ast.Name): | ||
| 505 | + # python 中__file__表示当前文件名,这里没有选择去获取当前环境的根环境的所属模块的名字,直接取了命令名字。在检测参数的目标导向下其它文件的文件名并不重要 | ||
| 506 | + if node.id == '__file__': | ||
| 507 | + return self.get_cur_command() | ||
| 508 | + if isinstance(node.ctx, ast.Load): | ||
| 509 | + # self直接返回了一个属性对象,放到了上一级去处理,也可以返回当前所属的ClsEnv | ||
| 510 | + if node.id == 'self': | ||
| 511 | + return ImportObj('self') | ||
| 512 | + ok, ret = self.find(node.id) | ||
| 513 | + if ok: | ||
| 514 | + if isinstance(ret, ImportObj): | ||
| 515 | + val = ret.val | ||
| 516 | + if val in no_exe_label: | ||
| 517 | + return None | ||
| 518 | + for i in range(len(val) + 1): | ||
| 519 | + if (i == len(val) or val[i] == '.') and val[:i] in UniArgParser.PARSERS: | ||
| 520 | + ret = UniArgParser.build(val[:i]) | ||
| 521 | + if val[i + 1:]: | ||
| 522 | + attrs = val[i + 1:].split('.') | ||
| 523 | + for attr in attrs: | ||
| 524 | + if hasattr(ret, attr): | ||
| 525 | + ret = getattr(ret, attr) | ||
| 526 | + break | ||
| 527 | + return ret | ||
| 528 | + return ImportObj(node.id) | ||
| 529 | + | ||
| 530 | + def exec_collection(self, node: Union[ast.List, ast.Tuple, ast.Set]): | ||
| 531 | + # 处理集合对象list,tuple,set。该return语句等价于list/tuple/set(...), ...指代集合对象内部的值 | ||
| 532 | + return self.__class__.collection[type(node)](self.exec_driver(self, elt) for elt in node.elts) | ||
| 533 | + | ||
| 534 | + def exec_collection_comp(self, node: Union[ast.ListComp, ast.SetComp, ast.GeneratorExp]): | ||
| 535 | + # 处理Python中的推导式,类似于[i for i in ...] | ||
| 536 | + val = self.exec_code(ast.unparse(node)) | ||
| 537 | + if val and isinstance(node, ast.GeneratorExp): | ||
| 538 | + try: | ||
| 539 | + return list(val) | ||
| 540 | + except: | ||
| 541 | + # 并非所有情况都能转化成功 | ||
| 542 | + log.warning("因模拟执行python代码时的输入参数未知以及所需系统环境的缺失导致的异常", log_file) | ||
| 543 | + return val | ||
| 544 | + | ||
| 545 | + def exec_code(self, code: str): | ||
| 546 | + """在Python中通过exec的方式真正的执行一段代码code | ||
| 547 | + 在执行之前先获取到当前环境以及父环境的所有可能的import语句并执行,得到一个global_env | ||
| 548 | + 同时将当前环境以及父环境的variables中的内容更新到global_env中 | ||
| 549 | + 最后将code,globalEnv封装成一个子进程任务放到子进程中去执行 | ||
| 550 | + """ | ||
| 551 | + try: | ||
| 552 | + global_env = {} | ||
| 553 | + for importCode in self.get_import_codes(): | ||
| 554 | + try: | ||
| 555 | + exec(importCode, global_env) | ||
| 556 | + except ImportError: | ||
| 557 | + continue | ||
| 558 | + global_env.update(self.variables) | ||
| 559 | + node = self.parent_env | ||
| 560 | + while node: | ||
| 561 | + for key, val in node.variables.items(): | ||
| 562 | + if key not in global_env: | ||
| 563 | + global_env[key] = val | ||
| 564 | + node = node.parent_env | ||
| 565 | + future = pool.schedule(subprocess_task, args=[ | ||
| 566 | + code, global_env], timeout=1) | ||
| 567 | + result = future.result(1) | ||
| 568 | + return result | ||
| 569 | + except: | ||
| 570 | + return None | ||
| 571 | + | ||
| 572 | + def exec_dict(self, node: ast.Dict): | ||
| 573 | + """ | ||
| 574 | + 和list/set/tuple类似,不过是键值对的形式 | ||
| 575 | + """ | ||
| 576 | + keys, vals = node.keys, node.values | ||
| 577 | + ret = {} | ||
| 578 | + if keys: | ||
| 579 | + if keys[-1] is None: | ||
| 580 | + dblstar_val = self.exec_driver(self, vals[-1]) | ||
| 581 | + if isinstance(dblstar_val, dict): | ||
| 582 | + ret.update(dblstar_val) | ||
| 583 | + keys = keys[:-1] | ||
| 584 | + vals = vals[:-1] | ||
| 585 | + for i in range(len(keys)): | ||
| 586 | + key = self.exec_driver(self, keys[i]) | ||
| 587 | + val = self.exec_driver(self, vals[i]) | ||
| 588 | + ret[key] = val | ||
| 589 | + return ret | ||
| 590 | + | ||
| 591 | + def exec_bin_op(self, node: ast.BinOp): | ||
| 592 | + """对二元运算符的处理 | ||
| 593 | + 具体的二元运算符的类型以及对应的执行方式定义在cls.bin_op中 | ||
| 594 | + """ | ||
| 595 | + left = self.exec_driver(self, node.left) | ||
| 596 | + right = self.exec_driver(self, node.right) | ||
| 597 | + try: | ||
| 598 | + return self.bin_op[type(node.op)](left, right) | ||
| 599 | + except: | ||
| 600 | + return None | ||
| 601 | + | ||
| 602 | + def exec_unary_op(self, node: ast.UnaryOp): | ||
| 603 | + val = self.exec_driver(self, node.operand) | ||
| 604 | + try: | ||
| 605 | + op = node.op | ||
| 606 | + if isinstance(op, ast.UAdd): | ||
| 607 | + return val | ||
| 608 | + elif isinstance(op, ast.USub): | ||
| 609 | + return -val | ||
| 610 | + elif isinstance(op, ast.Not): | ||
| 611 | + return not val | ||
| 612 | + elif isinstance(op, ast.Invert): | ||
| 613 | + return ~val | ||
| 614 | + except: | ||
| 615 | + return None | ||
| 616 | + | ||
| 617 | + # 判断当前环境所属模块是否是启动模块 | ||
| 618 | + def booted(self) -> bool: | ||
| 619 | + return self.get_root_env().belongs.booted() | ||
| 620 | + | ||
| 621 | + # 调用dfs_locate的入口 | ||
| 622 | + def locate(self, name: str): | ||
| 623 | + if name: | ||
| 624 | + name = name.split('.') | ||
| 625 | + root_env = self.get_root_env() | ||
| 626 | + site_package = root_env.belongs.get_root_pyunit() | ||
| 627 | + return site_package.dfs_locate(name) | ||
| 628 | + return None | ||
| 629 | + | ||
| 630 | + def dfs_locate(self, names: list[str]): | ||
| 631 | + """递归的一层一层的去定位一个变量/ImportObj对象/函数/类所在的位置""" | ||
| 632 | + if self.scanned == Environment.UNSCAN: | ||
| 633 | + self.exec() | ||
| 634 | + name = names[0] | ||
| 635 | + if name in self.imports: | ||
| 636 | + if len(names) != 1: | ||
| 637 | + return None | ||
| 638 | + else: | ||
| 639 | + return self.imports[name] | ||
| 640 | + elif name in self.variables: | ||
| 641 | + target = self.variables[name] | ||
| 642 | + if isinstance(target, (ClsEnv, FuncEnv)): | ||
| 643 | + if len(names) > 1: | ||
| 644 | + return target.dfs_locate(names[1:]) | ||
| 645 | + else: | ||
| 646 | + return target | ||
| 647 | + else: | ||
| 648 | + if len(names) != 1: | ||
| 649 | + return None | ||
| 650 | + else: | ||
| 651 | + return target | ||
| 652 | + else: | ||
| 653 | + return None | ||
| 654 | + | ||
| 655 | + def get_cur_command(self) -> str: | ||
| 656 | + return self.parent_env.get_cur_command() | ||
| 657 | + | ||
| 658 | + # 获取当前环境在Python包中的完整路径 | ||
| 659 | + def get_path(self) -> str: | ||
| 660 | + return f"{self.parent_env.get_path()}.{self.node.name}" | ||
| 661 | + | ||
| 662 | + def get_root_env(self): | ||
| 663 | + return self.parent_env.get_root_env() | ||
| 664 | + | ||
| 665 | + # 根据group,name获取真正的执行入口entry | ||
| 666 | + def get_entry(self, group, name): | ||
| 667 | + root_env = self.get_root_env() | ||
| 668 | + site_package = root_env.belongs.get_root_pyunit() | ||
| 669 | + return site_package.get_entry(group, name) | ||
| 670 | + | ||
| 671 | + def get_import_codes(self): | ||
| 672 | + import_codes = [] | ||
| 673 | + import_codes.extend(self.parent_env.get_import_codes()) | ||
| 674 | + import_codes.extend(self.import_codes) | ||
| 675 | + return import_codes | ||
| 676 | + | ||
| 677 | + def get_cls_env(self): | ||
| 678 | + return None | ||
| 679 | + | ||
| 680 | + # 在环境中查找具体的对象和变量,如果找不到,就在父环境中去找 | ||
| 681 | + def find(self, name: str): | ||
| 682 | + if self.scanned == Environment.UNSCAN: | ||
| 683 | + self.exec() | ||
| 684 | + ok = False | ||
| 685 | + if name in self.variables: | ||
| 686 | + name = self.variables[name] | ||
| 687 | + ok = True | ||
| 688 | + elif name in self.imports: | ||
| 689 | + name = self.imports[name] | ||
| 690 | + ok = True | ||
| 691 | + elif self.parent_env: | ||
| 692 | + ok, name = self.parent_env.find(name) | ||
| 693 | + return ok, name | ||
| 694 | + | ||
| 695 | + | ||
| 696 | +class PyUnit(metaclass=ABCMeta): | ||
| 697 | + def __init__(self, path: str, name: str, parent_node) -> None: | ||
| 698 | + self.name = name | ||
| 699 | + self.path = path | ||
| 700 | + self.parent_node: PyUnit = parent_node | ||
| 701 | + | ||
| 702 | + def get_path(self) -> str: | ||
| 703 | + if self.parent_node: | ||
| 704 | + path = self.parent_node.get_path() | ||
| 705 | + if path: | ||
| 706 | + return f"{path}.{self.name}" | ||
| 707 | + return self.name | ||
| 708 | + | ||
| 709 | + def get_root_pyunit(self): | ||
| 710 | + if self.parent_node: | ||
| 711 | + return self.parent_node.get_root_pyunit() | ||
| 712 | + return self | ||
| 713 | + | ||
| 714 | + def get_cur_command(self) -> str: | ||
| 715 | + return self.parent_node.get_cur_command() | ||
| 716 | + | ||
| 717 | + | ||
| 718 | +class Package(PyUnit): | ||
| 719 | + """ | ||
| 720 | + 扫描python代码包的组织结构并以树的形式存储 | ||
| 721 | + """ | ||
| 722 | + | ||
| 723 | + def __init__(self, path: str, name: str, parent_node) -> None: | ||
| 724 | + super().__init__(path, name, parent_node) | ||
| 725 | + self.subunits = {} | ||
| 726 | + self.init: Module = None | ||
| 727 | + for f in os.listdir(path): | ||
| 728 | + if f == '__pycache__': | ||
| 729 | + continue | ||
| 730 | + filepath = os.path.join(path, f) | ||
| 731 | + if os.path.isfile(filepath) and ( | ||
| 732 | + FileType.filetype(filepath) == FileType.PYTHON or filepath.endswith('.py')): | ||
| 733 | + if f.endswith('.py'): | ||
| 734 | + f = f[:-3] | ||
| 735 | + if f == '__init__': | ||
| 736 | + self.init = Module(filepath, f, self) | ||
| 737 | + else: | ||
| 738 | + self.subunits[f] = Module(filepath, f, self) | ||
| 739 | + elif os.path.isdir(filepath): | ||
| 740 | + self.subunits[f] = Package(filepath, f, self) | ||
| 741 | + | ||
| 742 | + def dfs_locate(self, names: list[str]): | ||
| 743 | + if self.init: | ||
| 744 | + ans = self.init.dfs_locate(names) | ||
| 745 | + if ans: | ||
| 746 | + return ans | ||
| 747 | + name = names[0] | ||
| 748 | + for subunit_name, subunit in self.subunits.items(): | ||
| 749 | + if subunit_name == name: | ||
| 750 | + if len(names) > 1: | ||
| 751 | + return subunit.dfs_locate(names[1:]) | ||
| 752 | + else: | ||
| 753 | + return subunit | ||
| 754 | + return None | ||
| 755 | + | ||
| 756 | + def get_boot_mod(self): | ||
| 757 | + for subunit in self.subunits.values(): | ||
| 758 | + val = subunit.get_boot_mod() | ||
| 759 | + if val: | ||
| 760 | + return val | ||
| 761 | + return None | ||
| 762 | + | ||
| 763 | + | ||
| 764 | +class SitePackage(PyUnit): | ||
| 765 | + """是源码中的python一级包的父目录的抽象,还包括了可执行的python命令在内""" | ||
| 766 | + | ||
| 767 | + def __init__(self, path: str) -> None: | ||
| 768 | + self.entry_points = {} | ||
| 769 | + self.packages = {} | ||
| 770 | + self.commands = [] | ||
| 771 | + super().__init__(path, '', None) | ||
| 772 | + for f in os.listdir(path): | ||
| 773 | + filepath = os.path.join(path, f) | ||
| 774 | + if os.path.isdir(filepath): | ||
| 775 | + if filepath.endswith('egg-info'): | ||
| 776 | + entry_path = os.path.join(filepath, 'entry_points.txt') | ||
| 777 | + if not os.path.isfile(entry_path): | ||
| 778 | + continue | ||
| 779 | + group = None | ||
| 780 | + with open(entry_path, mode='r') as entry_points: | ||
| 781 | + for line in entry_points.readlines(): | ||
| 782 | + line = line.strip() | ||
| 783 | + if line.startswith('[') and line.endswith(']'): | ||
| 784 | + group = line[1:-1] | ||
| 785 | + entry = {} | ||
| 786 | + self.entry_points[group] = entry | ||
| 787 | + elif line.find('=') != -1: | ||
| 788 | + name, val = line.split('=') | ||
| 789 | + entry[name.strip()] = val.strip().replace(':', '.') | ||
| 790 | + else: | ||
| 791 | + self.packages[f] = Package(filepath, f, self) | ||
| 792 | + | ||
| 793 | + def dfs_locate(self, names: list[str]): | ||
| 794 | + name = names[0] | ||
| 795 | + for package_name, package in self.packages.items(): | ||
| 796 | + if package_name == name: | ||
| 797 | + if len(names) > 1: | ||
| 798 | + return package.dfs_locate(names[1:]) | ||
| 799 | + else: | ||
| 800 | + return package | ||
| 801 | + return None | ||
| 802 | + | ||
| 803 | + def get_entry(self, group, name): | ||
| 804 | + if self.entry_points and group in self.entry_points.keys(): | ||
| 805 | + entry_points = self.entry_points[group] | ||
| 806 | + if name in entry_points.keys(): | ||
| 807 | + return entry_points[name] | ||
| 808 | + return None | ||
| 809 | + | ||
| 810 | + def get_cur_command(self): | ||
| 811 | + return self.get_boot_mod() | ||
| 812 | + | ||
| 813 | + def get_boot_mod(self): | ||
| 814 | + """ | ||
| 815 | + 确定启动模块 | ||
| 816 | + """ | ||
| 817 | + for package in self.packages.values(): | ||
| 818 | + val = package.get_boot_mod() | ||
| 819 | + if val: | ||
| 820 | + return val | ||
| 821 | + for commands in self.commands: | ||
| 822 | + val = commands.get_boot_mod() | ||
| 823 | + if val: | ||
| 824 | + return val | ||
| 825 | + return '__file__' | ||
| 826 | + | ||
| 827 | + def extend_commands(self, commands): | ||
| 828 | + self.commands.extend(commands) | ||
| 829 | + | ||
| 830 | + | ||
| 831 | +class Module(PyUnit): | ||
| 832 | + def __init__(self, path: str, name: str, parent_node) -> None: | ||
| 833 | + super().__init__(path, name, parent_node) | ||
| 834 | + srcfile = open(self.path, mode='r') | ||
| 835 | + source = ''.join(srcfile.readlines()) | ||
| 836 | + node = ast.parse(source, mode='exec') | ||
| 837 | + self.env = ModEnv(node, self) | ||
| 838 | + self.boot = False | ||
| 839 | + | ||
| 840 | + def exec(self): | ||
| 841 | + self.env.exec() | ||
| 842 | + | ||
| 843 | + def dfs_locate(self, names: list[str]): | ||
| 844 | + return self.env.dfs_locate(names) | ||
| 845 | + | ||
| 846 | + def booted(self) -> bool: | ||
| 847 | + return self.boot | ||
| 848 | + | ||
| 849 | + def get_cur_command(self) -> str: | ||
| 850 | + if self.boot: | ||
| 851 | + return self.path[self.path.rfind('/') + 1:] | ||
| 852 | + if self.parent_node: | ||
| 853 | + return self.parent_node.get_cur_command() | ||
| 854 | + return None | ||
| 855 | + | ||
| 856 | + def get_boot_mod(self) -> str: | ||
| 857 | + if self.boot: | ||
| 858 | + return self.path[self.path.rfind('/') + 1:] | ||
| 859 | + return None | ||
| 860 | + | ||
| 861 | + | ||
| 862 | +class ModEnv(Environment): | ||
| 863 | + def __init__(self, node: ast.Module, mod: Module) -> None: | ||
| 864 | + super().__init__(node, None) | ||
| 865 | + self.belongs: Module = mod | ||
| 866 | + | ||
| 867 | + def get_path(self) -> str: | ||
| 868 | + return self.belongs.get_path() | ||
| 869 | + | ||
| 870 | + def get_root_env(self): | ||
| 871 | + return self | ||
| 872 | + | ||
| 873 | + def get_cur_command(self): | ||
| 874 | + return self.belongs.get_cur_command() | ||
| 875 | + | ||
| 876 | + def get_import_codes(self): | ||
| 877 | + return self.import_codes | ||
| 878 | + | ||
| 879 | + def exec(self, args: list = None, keywords: dict = None): | ||
| 880 | + super().exec(args, keywords) | ||
| 881 | + | ||
| 882 | + | ||
| 883 | +class ClsEnv(Environment): | ||
| 884 | + def __init__(self, node: ast.ClassDef, parent_env) -> None: | ||
| 885 | + super().__init__(node, parent_env) | ||
| 886 | + | ||
| 887 | + def find(self, name: str): | ||
| 888 | + """类环境下的find需要在父环境中寻找之前先在基类中寻找 | ||
| 889 | + 对于基类中存在argparse,optparse的参数解析类时也要判断之前已经处理过的自定义的解析对象是否满足条件 | ||
| 890 | + """ | ||
| 891 | + if self.scanned == Environment.UNSCAN: | ||
| 892 | + self.exec() | ||
| 893 | + ok, ret = False, None | ||
| 894 | + if name in self.variables: | ||
| 895 | + ret = self.variables[name] | ||
| 896 | + ok = True | ||
| 897 | + elif name in self.imports: | ||
| 898 | + ret = self.imports[name] | ||
| 899 | + ok = True | ||
| 900 | + else: | ||
| 901 | + self.get_cls_env() | ||
| 902 | + if self.bases != Ellipsis and self.bases: | ||
| 903 | + for base in self.bases: | ||
| 904 | + if isinstance(base, ClsEnv): | ||
| 905 | + ok, ret = base.find(name) | ||
| 906 | + if ok: | ||
| 907 | + break | ||
| 908 | + elif isinstance(base, ArgParser) or isinstance(base, OptParser): | ||
| 909 | + if hasattr(base, name): | ||
| 910 | + ret = getattr(base, name) | ||
| 911 | + ok = True | ||
| 912 | + break | ||
| 913 | + if not ok and self.parent_env: | ||
| 914 | + ok, ret = self.parent_env.find(name) | ||
| 915 | + return (ok, ret) | ||
| 916 | + | ||
| 917 | + # 获取类环境时处理一下基类bases | ||
| 918 | + def get_cls_env(self) -> Environment: | ||
| 919 | + if not hasattr(self, 'bases'): | ||
| 920 | + setattr(self, 'bases', ...) | ||
| 921 | + self.bases = [self.exec_driver(self, base) | ||
| 922 | + for base in self.node.bases] | ||
| 923 | + for i in range(len(self.bases)): | ||
| 924 | + try: | ||
| 925 | + if callable(self.bases[i]): | ||
| 926 | + self.bases[i] = self.bases[i]() | ||
| 927 | + except: | ||
| 928 | + log.warning("因模拟执行python代码时的输入参数未知以及所需系统环境的缺失导致的异常", log_file) | ||
| 929 | + return self | ||
| 930 | + | ||
| 931 | + def get_parent_cls(self): | ||
| 932 | + if not self.bases and self.node.bases: | ||
| 933 | + self.bases = [self.exec_driver(self, base) for base in self.node.bases] | ||
| 934 | + return self.bases | ||
| 935 | + | ||
| 936 | + def __str__(self) -> str: | ||
| 937 | + return f"Customized Class: {self.get_path()}" | ||
| 938 | + | ||
| 939 | + | ||
| 940 | +class FuncEnv(Environment): | ||
| 941 | + """ 函数/方法的局部环境 | ||
| 942 | + Attributes | ||
| 943 | + stack: 模拟了一个函数调用堆栈,对于多个函数之间的循环调用将直接退出,防止死循环 | ||
| 944 | + """ | ||
| 945 | + stack = [] | ||
| 946 | + | ||
| 947 | + def __init__(self, node: ast.FunctionDef, parent_env) -> None: | ||
| 948 | + super().__init__(node, parent_env) | ||
| 949 | + | ||
| 950 | + def exec(self, args: list = [], keywords: dict = {}): | ||
| 951 | + """ | ||
| 952 | + 需要将函数调用时传递的参数和函数定义时可接收的参数进行对应,将参数放到variables中,然后调用父类Environment中的方法进行执行 | ||
| 953 | + """ | ||
| 954 | + if self.scanned == Environment.UNSCAN: | ||
| 955 | + self.scanned = Environment.SCANNING | ||
| 956 | + if self in FuncEnv.stack: | ||
| 957 | + return | ||
| 958 | + FuncEnv.stack.append(self) | ||
| 959 | + arguments = self.node.args | ||
| 960 | + start = 0 | ||
| 961 | + if len(arguments.args) > 0 and arguments.args[0].arg in ('self', 'cls'): | ||
| 962 | + start = 1 | ||
| 963 | + j = 0 | ||
| 964 | + for i in range(start, len(arguments.args)): | ||
| 965 | + arg = arguments.args[i].arg | ||
| 966 | + if arg in keywords: | ||
| 967 | + self.variables[arg] = keywords[arg] | ||
| 968 | + keywords.pop(arg) | ||
| 969 | + elif j < len(args): | ||
| 970 | + self.variables[arg] = args[j] | ||
| 971 | + j += 1 | ||
| 972 | + else: | ||
| 973 | + k = i - len(arguments.args) + len(arguments.defaults) | ||
| 974 | + if k >= 0: | ||
| 975 | + self.variables[arg] = self.exec_driver(self, arguments.defaults[k]) | ||
| 976 | + else: | ||
| 977 | + self.variables[arg] = None | ||
| 978 | + if arguments.vararg: | ||
| 979 | + arg = arguments.vararg.arg | ||
| 980 | + self.variables[arg] = args[j:] if j < len(args) else None | ||
| 981 | + for i in range(len(arguments.kwonlyargs)): | ||
| 982 | + arg = arguments.kwonlyargs[i].arg | ||
| 983 | + if arg in keywords: | ||
| 984 | + self.variables[arg] = keywords[arg] | ||
| 985 | + keywords.pop(arg) | ||
| 986 | + elif arguments.kw_defaults[i]: | ||
| 987 | + self.variables[arg] = self.exec_driver( | ||
| 988 | + self, arguments.kw_defaults[i]) | ||
| 989 | + else: | ||
| 990 | + self.variables[arg] = None | ||
| 991 | + if arguments.kwarg: | ||
| 992 | + arg = arguments.kwarg.arg | ||
| 993 | + self.variables[arg] = keywords | ||
| 994 | + ret = super().exec(args, keywords) | ||
| 995 | + FuncEnv.stack.pop() | ||
| 996 | + return ret | ||
| 997 | + | ||
| 998 | + def get_cls_env(self) -> Environment: | ||
| 999 | + return self.parent_env.get_cls_env() | ||
| 1000 | + | ||
| 1001 | + def __str__(self) -> str: | ||
| 1002 | + return f"Customized Function: {self.get_path()}" | ||
| 1003 | + | ||
| 1004 | + | ||
| 1005 | +class PyArgExtractor: | ||
| 1006 | + def __init__(self, commands: list[str], basedir: str, logfile: str): | ||
| 1007 | + """ | ||
| 1008 | + 定位Python源码位置,初始化sitepackage对象,并将命令command对象传入到sitepackage中。但并非所有源码包都有存放Python包的sitepackage | ||
| 1009 | + """ | ||
| 1010 | + global log_file | ||
| 1011 | + log_file = logfile | ||
| 1012 | + self.site_package = None | ||
| 1013 | + status, output = subprocess.getstatusoutput(f"find {basedir} -type d -name site-packages") | ||
| 1014 | + if status == 0 and output: | ||
| 1015 | + for line in output.split('\n'): | ||
| 1016 | + self.site_package = SitePackage(line) | ||
| 1017 | + break | ||
| 1018 | + self.commands = [Module(command, command[command.rfind('/') + 1:], self.site_package) for command in commands] | ||
| 1019 | + if self.site_package: | ||
| 1020 | + self.site_package.extend_commands(self.commands) | ||
| 1021 | + self.basedir = basedir | ||
| 1022 | + self.args = {command.name: [] for command in self.commands} | ||
| 1023 | + self.usages = {command.name: '' for command in self.commands} | ||
| 1024 | + self.nums = 0 | ||
| 1025 | + | ||
| 1026 | + def get_cmds_args(self): | ||
| 1027 | + """获取命令的参数信息 | ||
| 1028 | + Returns: | ||
| 1029 | + content: usage帮助信息 | ||
| 1030 | + commands: 格式化的参数 | ||
| 1031 | + value_map: 可用的值的map,格式:'命令,需要赋值的参数':值的列表 | ||
| 1032 | + help_map: 类型信息的map,格式:'命令,需要赋值的参数':类型 | ||
| 1033 | + """ | ||
| 1034 | + content, commands, value_map, help_map = '', [], {}, {} | ||
| 1035 | + for command in self.commands: | ||
| 1036 | + UniArgParser.CurCommand = command | ||
| 1037 | + command.boot = True | ||
| 1038 | + command.exec() | ||
| 1039 | + command.boot = False | ||
| 1040 | + if UniArgParser.finalParser: | ||
| 1041 | + num = UniArgParser.finalParser.get_num() | ||
| 1042 | + content = f"COMMAND: {command.name}\n{num}\n{UniArgParser.finalParser}" | ||
| 1043 | + self.nums += num | ||
| 1044 | + commands, value_map, help_map = UniArgParser.finalParser.getCmds_Vals() | ||
| 1045 | + UniArgParser.finalParser = None | ||
| 1046 | + return content, commands, value_map, help_map | ||