Compare commits

...

5 Commits

Author SHA1 Message Date
934ca82d9c
Bump Version: 0.8.7.2 2023-10-13 23:12:35 +08:00
b1ae70acb9
some lib-not-dr 2023-10-13 23:07:08 +08:00
0fc1fe4652
Add | lib-not-dr 2023-10-13 23:06:23 +08:00
e321db12d1
Add | more logger! 2023-10-13 23:04:32 +08:00
bfc431e8ca
logger! 2023-10-13 22:58:42 +08:00
20 changed files with 1255 additions and 31 deletions

View File

@ -10,7 +10,7 @@ from pathlib import Path
from Difficult_Rocket.api.types import Options, Version
sdk_version = Version("0.8.7.0") # SDK 版本
sdk_version = Version("0.8.7.2") # SDK 版本
build_version = Version("2.2.0.0") # 编译文件版本(与游戏本体无关)
Api_version = Version("0.1.1.0") # API 版本
__version__ = sdk_version

View File

@ -23,9 +23,9 @@
[关于版本号的说明](./docs/src/version.md)
[![release version](https://img.shields.io/badge/Release-0.8.6.0-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases)
[![pre version](https://img.shields.io/badge/Pre_Release-0.8.7.0-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases)
[![devlo version](https://img.shields.io/badge/Devloping-0.8.7-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases)
[![release version](https://img.shields.io/badge/Release-0.8.7.2-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases)
[![pre version](https://img.shields.io/badge/Pre_Release-0.8.7.2-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases)
[![devlo version](https://img.shields.io/badge/Devloping-0.9.0-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases)
[![language badge](https://stats.deeptrain.net/repo/shenjackyuanjie/Difficult-Rocket?theme=dark)](https://stats.deeptrain.net/repo/shenjackyuanjie/Difficult-Rocket?theme=dark)

View File

@ -21,9 +21,9 @@
[About Versions](src/version.md)
[![release version](https://img.shields.io/badge/Release-0.8.6.0-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases)
[![pre version](https://img.shields.io/badge/Pre_Release-0.8.7.0-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases)
[![devlo version](https://img.shields.io/badge/Devloping-0.8.8-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases)
[![release version](https://img.shields.io/badge/Release-0.8.7.2-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases)
[![pre version](https://img.shields.io/badge/Pre_Release-0.8.7.2-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases)
[![devlo version](https://img.shields.io/badge/Devloping-0.9.0-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases)
[![language badge](https://stats.deeptrain.net/repo/shenjackyuanjie/Difficult-Rocket?theme=dark)](https://stats.deeptrain.net/repo/shenjackyuanjie/Difficult-Rocket?theme=dark)

View File

@ -2,10 +2,19 @@
# DR SDK 更新日志
- 最新版本号
- DR sdk: 0.8.7.0
- DR sdk: 0.8.7.2
- DR api: 0.1.1.0
## Draft DR sdk 0.8.7.2
## Draft DR sdk 0.9.0.0
### Add
- 添加内置依赖: `lib-not-dr`
- Added built-in dependency: `lib-not-dr`
- 不再同时维护两份代码
- No longer maintain two sets of code at the same time
## DR sdk 0.8.7.2
### Add

View File

@ -0,0 +1,7 @@
# -------------------------------
# Difficult Rocket
# Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com
# All rights reserved
# -------------------------------
__version__ = '0.1.7'

View File

View File

@ -0,0 +1,40 @@
from dataclasses import dataclass, field
from typing import Set, List
class Parsed:
...
@dataclass
class Option:
name: str
shortcuts: List[str]
optional: bool
types: Set[type] = field(default_factory=lambda: {str})
@dataclass
class OptionGroup:
options: List[Option]
optional: bool = True
exclusive: bool = False
@dataclass
class Argument:
name: str
types: Set[type] = field(default_factory=lambda: {str})
@dataclass
class Flag:
name: str
shortcuts: List[str]
@dataclass
class FlagGroup:
flags: List[Flag]
exclusive: bool = False

View File

@ -0,0 +1,14 @@
class CallBackDescriptor:
def __init__(self, name):
self.callback_name = name
def __set__(self, instance, value):
assert getattr(instance, self.callback_name) is None, f"Attribute '{self.callback_name}' has been set."
instance.__dict__[self.callback_name] = value
def __get__(self, instance, owner):
return (
self
if instance is None
else instance.__dict__.get(self.callback_name)
)

View File

@ -0,0 +1,2 @@
class IllegalName(Exception):
"""名称或快捷名不合法"""

View File

@ -0,0 +1,130 @@
# -------------------------------
# Difficult Rocket
# Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com
# All rights reserved
# -------------------------------
import re
from typing import Callable, List, Optional, Union, Set
from .data import Option, Argument, Flag, Parsed
from .descriptor import CallBackDescriptor
try:
from typing import Self
except ImportError:
from typing import TypeVar
Self = TypeVar("Self") # NOQA
from .exception import IllegalName
CallBack = Union[Callable[[str], None], str] # Equals to `Callable[[str], None] | str`
# 可调用对象或字符串作为回调
# A callable or str as callback
ParseArgFunc = Callable[[str], Optional[type]]
# 解析参数的函数,返回值为 None 时表示解析失败
# function to parse argument, return None when failed
EMPTY_WORDS = re.compile(r"\s", re.I)
def check_name(name: Union[str, List[str]]) -> None:
"""
Check the name or shortcuts of argument(s) or flag(s).
The name must not be empty str, and must not contains \\t or \\n or \\f or \\r.
If that not satisfy the requirements, it will raise exception `IllegalArgumentName`.
检查 参数或标记 名称或快捷方式 是否符合要求
名称必须是非空的字符串且不能包含 \\t \\n \\f \\r
如果不符合要求将会抛出 `IllegalArgumentName` 异常
:param name: arguments
:return: None
"""
if isinstance(name, str) and EMPTY_WORDS.search(name):
raise IllegalName("The name of argument must not contains empty words.")
elif isinstance(name, list) and all((not isinstance(i, str)) and EMPTY_WORDS.search(i) for i in name):
raise IllegalName("The name of shortcut must be 'str', and must not contains empty words.")
else:
raise TypeError("The type of name must be 'str' or 'list[str]'.")
class Literal:
_tip = CallBackDescriptor("_tip")
_func = CallBackDescriptor("_func")
_err_callback = CallBackDescriptor("_err_callback")
def __init__(self, name: str):
self.name: str = name
self.sub: List[Self] = []
self._tip: Optional[CallBack] = None
self._func: Optional[CallBack] = None
self._err_callback: Optional[CallBack] = None
self._opts: List[Option] = []
self._args: List[Argument] = []
self._flags: List[Flag] = []
def __call__(self, *nodes) -> Self:
self.sub += nodes
return self
def __repr__(self):
attrs = (k for k in self.__dict__ if not (k.startswith("__") and k.endswith("__")))
return f"{self.__class__.__name__}({', '.join(f'{k}={v!r}' for k in attrs if (v := self.__dict__[k]))})"
def arg(self, name: str, types: Optional[Set[type]] = None) -> Self:
Argument(name=name, types=types)
return self
def opt(
self,
name: str,
shortcuts: Optional[List[str]] = None,
optional: bool = True,
types: Optional[Set[type]] = None
) -> Self:
check_name(name)
if shortcuts is not None and len(shortcuts) != 0:
check_name(shortcuts)
self._opts.append(
Option(name=name, shortcuts=shortcuts, optional=optional, types=types)
)
return self
def opt_group(self, opts: List[Option], exclusive: bool = False):
...
def flag(self, name: str, shortcuts: Optional[List[str]] = None) -> Self:
check_name(name)
if shortcuts is not None and len(shortcuts) != 0:
check_name(shortcuts)
Flag(name=name, shortcuts=shortcuts)
...
return self
def flag_group(self, flags: List[Flag], exclusive: bool = False) -> Self:
...
return self
def error(self, callback: CallBack) -> Self:
self._err_callback = callback
return self
def run(self, func: CallBack) -> Self:
self._func = func
return self
def tip(self, tip: CallBack) -> Self:
self._tip = tip
return self
def parse(self, cmd: Union[str, List[str]]) -> Parsed:
...
def to_doc(self) -> str:
...
def builder(node: Literal) -> Literal:
...

View File

View File

@ -0,0 +1,472 @@
# -------------------------------
# Difficult Rocket
# Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com
# All rights reserved
# -------------------------------
import platform
import warnings
from pathlib import Path
from typing import List, Tuple, Optional, Union, Any
from enum import Enum
from lib_not_dr.types import Options, Version, VersionRequirement
def ensure_cmd_readable(cmd: str) -> str:
"""
保证 参数中 不含空格
:param cmd: 要格式化的命令行参数
:return: 格式化后的命令行参数
"""
if ' ' in str(cmd):
return f'"{cmd}"'
return cmd
def format_cmd(arg_name: Optional[str] = None,
arg_value: Optional[Union[str, List[str]]] = None,
write: Optional[Any] = True) -> List[str]:
"""
用来格式化输出命令行参数
:param arg_name: 类似 --show-memory 之类的主项
:param arg_value: 类似 xxx 类的内容
:param write: 是否写入
:return: 直接拼接好的命令行参数 不带 =
"""
if not write:
return []
if arg_name is None:
return []
if arg_value is None:
return [arg_name]
if isinstance(arg_value, list):
arg_value = ','.join([ensure_cmd_readable(value) for value in arg_value])
return [f'{arg_name}{arg_value}']
arg_value = ensure_cmd_readable(arg_value)
return [f'{arg_name}{arg_value}']
class NuitkaSubConfig(Options):
"""
Nuitka 配置的子项
Nuitka configuration sub-items
"""
name = 'Nuitka Sub Configuration'
def gen_cmd(self) -> List[str]:
"""
生成命令行参数
:return:
"""
raise NotImplementedError
class NuitkaPluginConfig(NuitkaSubConfig):
"""
控制 nuitka plugin 相关参数的部分
Control part of nuitka's plugin related parameters
"""
name = 'Nuitka Plugin Configuration'
# --enable-plugin=PLUGIN_NAME
enable_plugin: List[str] = []
# --disable-plugin=PLUGIN_NAME
disable_plugin: List[str] = []
# --plugin-no-detection
plugin_no_detection: bool = False
# --user-plugin=PATH
user_plugin: List[Path] = []
# --show-source-changes
show_source_changes: bool = False
# --include-plugin-directory=MODULE/PACKAGE
include_plugin_dir: List[str] = []
# --include-plugin-files=PATTERN
include_plugin_files: List[str] = []
def gen_cmd(self) -> List[str]:
lst = []
lst += format_cmd('--enable-plugin=', self.enable_plugin, self.enable_plugin)
lst += format_cmd('--disable-plugin=', self.disable_plugin, self.disable_plugin)
lst += format_cmd('--plugin-no-detection' if self.plugin_no_detection else None)
lst += format_cmd('--user-plugin=', [str(plugin.absolute()) for plugin in self.user_plugin], self.user_plugin)
lst += format_cmd('--show-source-changes' if self.show_source_changes else None)
lst += format_cmd('--include-plugin-directory=', self.include_plugin_dir, self.include_plugin_dir)
lst += format_cmd('--include-plugin-files=', self.include_plugin_files, self.include_plugin_files)
return lst
class NuitkaIncludeConfig(NuitkaSubConfig):
"""
控制 nuitka include 数据 相关参数的部分
Control part of nuitka's include related parameters
"""
name = 'Nuitka Include Configuration'
# --include-package=PACKAGE
include_packages: List[str] = []
# --include-module=MODULE
include_modules: List[str] = []
# --prefer-source-code
# --no-prefer-source-code for --module
prefer_source_code: bool = False
# --follow-stdlib
follow_stdlib: bool = False
def gen_cmd(self) -> List[str]:
lst = []
lst += format_cmd('--include-package=', self.include_packages, self.include_packages)
lst += format_cmd('--include-module=', self.include_modules, self.include_modules)
lst += format_cmd('--prefer-source-code' if self.prefer_source_code else None)
lst += format_cmd('--no-prefer-source-code' if not self.prefer_source_code else None)
lst += format_cmd('--follow-stdlib' if self.follow_stdlib else None)
return lst
class NuitkaDataConfig(NuitkaSubConfig):
"""
控制 nuitka 数据 相关参数的部分
Control part of nuitka's data related parameters
"""
name = 'Nuitka Data Configuration'
# --include-package-data=PACKAGE=PACKAGE_PATH
include_package_data: List[Tuple[Path, Path]] = []
# --include-data-files=PATH=PATH
include_data_files: List[Tuple[Path, Path]] = []
# --include-data-dir=DIRECTORY=PATH
include_data_dir: List[Tuple[Path, Path]] = []
# --noinclude-data-files=PATH
no_include_data_files: List[Path] = []
# --list-package-data=LIST_PACKAGE_DATA
list_package_data: List[str] = []
# --list-package-dlls=LIST_PACKAGE_DLLS
list_package_dlls: List[str] = []
# --include-distribution-metadata=DISTRIBUTION
include_distribution_metadata: List[str] = []
class NuitkaBinaryInfo(Options):
"""
nuitka 构建的二进制文件的信息
nuitka build binary file information
"""
name = 'Nuitka Binary Info'
# --company-name=COMPANY_NAME
company_name: Optional[str] = None
# --product-name=PRODUCT_NAME
product_name: Optional[str] = None
# --file-version=FILE_VERSION
# --macos-app-version=MACOS_APP_VERSION
file_version: Optional[Union[str, Version]] = None
# --product-version=PRODUCT_VERSION
product_version: Optional[Union[str, Version]] = None
# --file-description=FILE_DESCRIPTION
file_description: Optional[str] = None
# --copyright=COPYRIGHT_TEXT
copyright: Optional[str] = None
# --trademarks=TRADEMARK_TEXT
trademarks: Optional[str] = None
# Icon
# --linux-icon=ICON_PATH
# --macos-app-icon=ICON_PATH
# --windows-icon-from-ico=ICON_PATH
# --windows-icon-from-exe=ICON_EXE_PATH
# 注意: 只有 Windows 下 才可以提供多个 ICO 文件
# 其他平台 和 EXE 下只会使用第一个路径
icon: Optional[List[Path]] = None
# Console
# --enable-console
# --disable-console
console: bool = True
# Windows UAC
# --windows-uac-admin
windows_uac_admin: bool = False
# --windows-uac-uiaccess
windows_uac_ui_access: bool = False
class NuitkaOutputConfig(Options):
"""
nuitka 构建的选项
nuitka build output information
"""
name = 'Nuitka Output Config'
# --output-dir=DIRECTORY
output_dir: Optional[Path] = None
# --output-filename=FILENAME
output_filename: Optional[str] = None
# --quiet
quiet: bool = False
# --no-progressbar
no_progressbar: bool = False
# --verbose
verbose: bool = False
# --verbose-output=PATH
verbose_output: Optional[Path] = None
# --show-progress
show_progress: bool = False
# --show-memory
show_memory: bool = False
# --show-scons
show_scons: bool = False
# --show-modules
show_modules: bool = False
# --show-modules-output=PATH
show_modules_output: Optional[Path] = None
# --xml=XML_FILENAME
xml: Optional[Path] = None
# --report=REPORT_FILENAME
report: Optional[Path] = None
# --report-diffable
report_diffable: bool = False
# --remove-output
remove_output: bool = False
# --no-pyo-file
no_pyo_file: bool = False
class NuitkaDebugConfig(Options):
"""
nuitka 构建的调试选项
nuikta build debug information
"""
name = 'Nuitka Debug Config'
# --debug
debug: bool = False
# --unstripped
strip: bool = True
# --profile
profile: bool = False
# --internal-graph
internal_graph: bool = False
# --trace-execution
trace_execution: bool = False
# --recompile-c-only
recompile_c_only: bool = False
# --generate-c-only
generate_c_only: bool = False
# --deployment
deployment: bool = False
# --no-deployment-flag=FLAG
deployment_flag: Optional[str] = None
# --experimental=FLAG
experimental: Optional[str] = None
class NuitkaTarget(Enum):
"""
用于指定 nuitka 构建的目标
Use to specify the target of nuitka build
exe: 不带任何参数
module: --module
standalone: --standalone
one_file: --onefile
"""
exe = ''
module = 'module'
standalone = 'standalone'
one_file = 'package'
class NuitkaScriptGenerator(Options):
"""
用于帮助生成 nuitka 构建脚本的类
Use to help generate nuitka build script
:arg main 需要编译的文件
"""
name = 'Nuitka Script Generator'
# --main=PATH
# 可以有多个 输入时需要包在列表里
main: List[Path]
# --run
run_after_build: bool = False
# --debugger
debugger: bool = False
# --execute-with-pythonpath
execute_with_python_path: bool = False
# --assume-yes-for-downloads
download_confirm: bool = True
# standalone/one_file/module/exe
target: NuitkaTarget = NuitkaTarget.exe
# --python-debug
python_debug: bool = False
# --python-flag=FLAG
python_flag: List[str] = []
# --python-for-scons=PATH
python_for_scons: Optional[Path] = None
class CompilerHelper(Options):
"""
用于帮助生成 nuitka 构建脚本的类
Use to help generate nuitka build script
"""
name = 'Nuitka Compiler Helper'
output_path: Path = Path('./build')
src_file: Path
python_cmd: str = 'python'
compat_nuitka_version: VersionRequirement = VersionRequirement("~1.8.0") # STATIC VERSION
# 以下为 nuitka 的参数
# nuitka options below
use_lto: bool = False # --lto=yes (no is faster)
use_clang: bool = True # --clang
use_msvc: bool = True # --msvc=latest
use_mingw: bool = False # --mingw64
onefile: bool = False # --onefile
onefile_tempdir: Optional[str] = '' # --onefile-tempdir-spec=
standalone: bool = True # --standalone
use_ccache: bool = True # not --disable-ccache
enable_console: bool = True # --enable-console / --disable-console
show_progress: bool = True # --show-progress
show_memory: bool = False # --show-memory
remove_output: bool = True # --remove-output
save_xml: bool = False # --xml
xml_path: Path = Path('build/compile_data.xml')
save_report: bool = False # --report
report_path: Path = Path('build/compile_report.xml')
download_confirm: bool = True # --assume-yes-for-download
run_after_build: bool = False # --run
company_name: Optional[str] = ''
product_name: Optional[str] = ''
file_version: Optional[Version] = None
product_version: Optional[Version] = None
file_description: Optional[str] = '' # --file-description
copy_right: Optional[str] = '' # --copyright
icon_path: Optional[Path] = None
follow_import: List[str] = []
no_follow_import: List[str] = []
include_data_dir: List[Tuple[str, str]] = []
include_packages: List[str] = []
enable_plugin: List[str] = [] # --enable-plugin=xxx,xxx
disable_plugin: List[str] = [] # --disable-plugin=xxx,xxx
def init(self, **kwargs) -> None:
if (compat_version := kwargs.get('compat_nuitka_version')) is not None:
if not self.compat_nuitka_version.accept(compat_version):
warnings.warn(
f"Nuitka version may not compat with {compat_version}\n"
"requirement: {self.compat_nuitka_version}"
)
# 非 windows 平台不使用 msvc
if platform.system() != 'Windows':
self.use_msvc = False
self.use_mingw = False
else:
self.use_mingw = self.use_mingw and not self.use_msvc
# Windows 平台下使用 msvc 时不使用 mingw
def __str__(self):
return self.as_markdown()
def as_markdown(self, longest: Optional[int] = None) -> str:
"""
输出编译器帮助信息
Output compiler help information
Args:
longest (Optional[int], optional):
输出信息的最大长度限制 The maximum length of output information.
Defaults to None.
Returns:
str: markdown 格式输出的编译器帮助信息
Compile helper information in markdown format
"""
front = super().as_markdown(longest)
gen_cmd = self.gen_subprocess_cmd()
return f"{front}\n\n```bash\n{' '.join(gen_cmd)}\n```"
def gen_subprocess_cmd(self) -> List[str]:
"""生成 nuitka 构建脚本
Generate nuitka build script
Returns:
List[str]:
生成的 nuitka 构建脚本
Generated nuitka build script
"""
cmd_list = [self.python_cmd, '-m', 'nuitka']
# macos 和 非 macos icon 参数不同
if platform.system() == 'Darwin':
cmd_list += format_cmd('--macos-app-version=', self.product_version, self.product_version)
cmd_list += format_cmd('--macos-app-icon=', self.icon_path.absolute(), self.icon_path)
elif platform.system() == 'Windows':
cmd_list += format_cmd('--windows-icon-from-ico=', self.icon_path.absolute(), self.icon_path)
elif platform.system() == 'Linux':
cmd_list += format_cmd('--linux-icon=', self.icon_path.absolute(), self.icon_path)
cmd_list += format_cmd('--lto=', 'yes' if self.use_lto else 'no')
cmd_list += format_cmd('--clang' if self.use_clang else None)
cmd_list += format_cmd('--msvc=latest' if self.use_msvc else None)
cmd_list += format_cmd('--mingw64' if self.use_mingw else None)
cmd_list += format_cmd('--standalone' if self.standalone else None)
cmd_list += format_cmd('--onefile' if self.onefile else None)
cmd_list += format_cmd('--onefile-tempdir-spec=', self.onefile_tempdir, self.onefile_tempdir)
cmd_list += format_cmd('--disable-ccache' if not self.use_ccache else None)
cmd_list += format_cmd('--show-progress' if self.show_progress else None)
cmd_list += format_cmd('--show-memory' if self.show_memory else None)
cmd_list += format_cmd('--remove-output' if self.remove_output else None)
cmd_list += format_cmd('--assume-yes-for-download' if self.download_confirm else None)
cmd_list += format_cmd('--run' if self.run_after_build else None)
cmd_list += format_cmd('--enable-console' if self.enable_console else '--disable-console')
cmd_list += format_cmd('--xml=', str(self.xml_path.absolute()), self.save_xml)
cmd_list += format_cmd('--report=', str(self.report_path.absolute()), self.save_report)
cmd_list += format_cmd('--output-dir=', str(self.output_path.absolute()), self.output_path)
cmd_list += format_cmd('--company-name=', self.company_name, self.company_name)
cmd_list += format_cmd('--product-name=', self.product_name, self.product_name)
cmd_list += format_cmd('--file-version=', str(self.file_version), self.file_version)
cmd_list += format_cmd('--product-version=', str(self.product_version), self.product_version)
cmd_list += format_cmd('--file-description=', self.file_description, self.file_description)
cmd_list += format_cmd('--copyright=', self.copy_right, self.copy_right)
cmd_list += format_cmd('--follow-import-to=', self.follow_import, self.follow_import)
cmd_list += format_cmd('--nofollow-import-to=', self.no_follow_import, self.no_follow_import)
cmd_list += format_cmd('--enable-plugin=', self.enable_plugin, self.enable_plugin)
cmd_list += format_cmd('--disable-plugin=', self.disable_plugin, self.disable_plugin)
if self.include_data_dir:
cmd_list += [f"--include-data-dir={src}={dst}" for src, dst in self.include_data_dir]
if self.include_packages:
cmd_list += [f"--include-package={package}" for package in self.include_packages]
cmd_list.append(f"--main={self.src_file}")
return cmd_list

View File

@ -0,0 +1,31 @@
# -------------------------------
# Difficult Rocket
# Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com
# All rights reserved
# -------------------------------
from .options import (Options,
OptionsError,
OptionNotFound,
OptionNameNotDefined,
get_type_hints_)
from .version import (Version,
VersionRequirement,
VersionParsingError,
ExtraElement)
__all__ = [
# options
'get_type_hints_',
'Options',
'OptionsError',
'OptionNotFound',
'OptionNameNotDefined',
# version
'Version',
'VersionRequirement',
'VersionParsingError',
'ExtraElement'
]

View File

@ -0,0 +1,271 @@
# -------------------------------
# Difficult Rocket
# Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com
# All rights reserved
# -------------------------------
import shutil
import traceback
from io import StringIO
from typing import get_type_hints, Type, List, Union, Dict, Any, Callable, Tuple, Optional, TYPE_CHECKING, Iterable
__all__ = [
'get_type_hints_',
'Options',
'OptionsError',
'OptionNotFound',
'OptionNameNotDefined'
]
def get_type_hints_(cls: Type):
try:
return get_type_hints(cls)
except ValueError:
return get_type_hints(cls, globalns={})
def to_str_value_(value: Any) -> Any:
"""递归的将输入值的每一个非 builtin type 转换成 str"""
if isinstance(value, (str, bytes, bytearray, int, float, bool, type(None))):
return value
elif isinstance(value, dict):
return {k: to_str_value_(v) for k, v in value.items()}
elif isinstance(value, (list, Iterable)):
return [to_str_value_(v) for v in value]
else:
return str(value)
class OptionsError(Exception):
""" option 的错误基类"""
class OptionNameNotDefined(OptionsError):
""" 向初始化的 option 里添加了一个不存在于选项里的选项 """
class OptionNotFound(OptionsError):
""" 某个选项没有找到 """
class Options:
"""
一个用于存储选项 / 提供 API 定义 的类
用法:
存储配置: 继承 Options
在类里定义 option: typing
(可选 定义 name: str = 'Option Base' 用于在打印的时候显示名字)
提供 API 接口: 继承 Options
在类里定义 option: typing
定义 一些需要的方法
子类: 继承 新的 Options
实现定义的方法
"""
name = 'Option Base'
cached_options: Dict[str, Union[str, Any]] = {}
def __init__(self, **kwargs):
"""
创建一个新的 Options 的时候的配置
如果存在 init 方法 会在设置完 kwargs 之后运行子类的 init 方法
:param kwargs: 需要设置的选项
"""
if TYPE_CHECKING:
self._options: Dict[str, Union[Callable, object]] = {}
self.flush_option()
for option, value in kwargs.items():
if option not in self.cached_options:
raise OptionNameNotDefined(f"option: {option} with value: {value} is not defined")
setattr(self, option, value)
run_load_file = True
if hasattr(self, 'init'):
run_load_file = self.init(**kwargs) # 默认 False/None
run_load_file = not run_load_file
if hasattr(self, 'load_file') and run_load_file:
try:
self.load_file()
except Exception:
traceback.print_exc()
self.flush_option()
def __str__(self):
return f"<{self.__class__.__name__} {self.name}>" if self.name else f"<{self.__class__.__name__}>"
def __repr__(self):
return self.__str__()
if TYPE_CHECKING:
_options: Dict[str, Union[Callable, object]] = {}
def init(self, **kwargs) -> bool:
""" 如果子类定义了这个函数,则会在 __init__ 之后调用这个函数
返回值为 True 则不会调用 load_file 函数
"""
def load_file(self) -> bool:
"""如果子类定义了这个函数,则会在 __init__ 和 init 之后再调用这个函数
请注意这个函数请尽量使用 try 包裹住可能出现错误的部分
否则会在控制台输出你的报错"""
return True
def option(self) -> Dict[str, Any]:
"""
获取配置类的所有配置
:return: 自己的所有配置
"""
values = {}
for ann in self.__annotations__: # 获取类型注释
values[ann] = getattr(self, ann, None)
if values[ann] is None:
values[ann] = self.__annotations__[ann]
if not hasattr(self, '_options'):
self._options: Dict[str, Union[Callable, object]] = {}
for option, a_fun in self._options.items(): # 获取额外内容
values[option] = a_fun
for option, a_fun in values.items(): # 检查是否为 property
if a_fun is bool and getattr(self, option, None) is not None:
values[option] = False
if isinstance(a_fun, property):
try:
values[option] = getattr(self, option)
except AttributeError:
raise OptionNotFound(f'Option {option} is not found in {self.name}') from None
return values
def str_option(self, shrink_to_long: Optional[int] = None) -> Dict[str, Union[str, Any]]:
"""
获取配置类的所有配置 并将所有非 BuiltIn 类型的值转换为 str
:return:
"""
raw_option = self.option()
str_option = to_str_value_(raw_option)
if shrink_to_long is None:
return str_option
if not isinstance(shrink_to_long, int) or shrink_to_long <= 0:
return str_option
for option, value in str_option.items():
if value is not None:
if len(str(value)) > shrink_to_long:
str_option[option] = str(value)[:shrink_to_long] + '...'
return str_option
def format(self, text: str) -> str:
"""
使用自己的选项给输入的字符串替换内容
:param text: 想替换的内容
:return: 替换之后的内容
"""
cache_option = self.flush_option()
for option, value in cache_option.items():
text = text.replace(f'{{{option}}}', str(value))
return text
def flush_option(self) -> Dict[str, Any]:
"""
刷新缓存 options 的内容
:return: 刷新过的 options
"""
self.cached_options = self.option()
return self.cached_options
def option_with_len(self) -> Tuple[List[Tuple[str, Any, Type]], int, int, int]:
"""
返回一个可以用于打印的 option 列表
:return:
"""
options = self.flush_option()
max_len_key = 1
max_len_value = 1
max_len_value_t = 1
option_list = []
for key, value in options.items():
value_t = type(value) if isinstance(value, type(value)) else type(value) # 判定这个类型 是不是 基本类型
max_len_key = max(max_len_key, len(key))
max_len_value = max(max_len_value, len(str(value)))
max_len_value_t = max(max_len_value_t, len(str(value_t)))
option_list.append([key, value, value_t])
return [option_list, max_len_key, max_len_value, max_len_value_t] # noqa
def as_markdown(self, longest: Optional[int] = None) -> str:
"""
返回一个 markdown 格式的 option 字符串
:param longest: 最长的输出长度
:return: markdown 格式的 option 字符串
"""
value = self.option_with_len()
cache = StringIO()
option_len = max(value[1], len('Option'))
value_len = max(value[2], len('Value'))
value_type_len = max(value[3], len('Value Type'))
# | Option | Value | Value Type |
shortest = len('Option | Value | Value Type')
if longest is not None:
console_width = max(longest, shortest)
else:
console_width = shutil.get_terminal_size(fallback=(100, 80)).columns
console_width = max(console_width, shortest)
# 为每一栏 预分配 1/3 或者 需要的宽度 (如果不需要 1/3)
option_len = min(option_len, console_width // 3)
value_len = min(value_len, console_width // 3)
value_type_len = min(value_type_len, console_width // 3)
# 先指定每一个列的输出最窄宽度, 然后去尝试增加宽度
# 循环分配新空间之前 首先检查是否已经不需要多分配 (and 后面)
while option_len + value_len + value_type_len + 16 < console_width\
and (option_len < value[1]
or value_len < value[2]
or value_type_len < value[3]):
# 每一个部分的逻辑都是
# 如果现在的输出长度小于原始长度
# 并且长度 + 1 之后的总长度依然在允许范围内
# 那么就 + 1
if option_len < value[1] and option_len + value_len + value_type_len + 16 < console_width:
option_len += 1
if value_len < value[2] and option_len + value_len + value_type_len + 16 < console_width:
value_len += 1
if value_type_len < value[3] and option_len + value_len + value_type_len + 16 < console_width:
value_type_len += 1
# 实际上 对于列表(可变对象) for 出来的这个值是一个引用
# 所以可以直接修改 string
for v in value[0]:
if len(str(v[0])) > option_len:
v[0] = f'{str(v[0])[:value_len - 3]}...'
if len(str(v[1])) > value_len:
v[1] = f'{str(v[1])[:value_len - 3]}...'
if len(str(v[2])) > value_type_len:
v[2] = f'{str(v[2])[:value_len - 3]}..'
cache.write(
f"| Option{' ' * (option_len - 3)}| Value{' ' * (value_len - 2)}| Value Type{' ' * (value_type_len - 7)}|\n")
cache.write(f'|:{"-" * (option_len + 3)}|:{"-" * (value_len + 3)}|:{"-" * (value_type_len + 3)}|\n')
for option, value, value_t in value[0]:
cache.write(f"| `{option}`{' ' * (option_len - len(option))} "
f"| `{value}`{' ' * (value_len - len(str(value)))} "
f"| `{value_t}`{' ' * (value_type_len - len(str(value_t)))} |\n")
result = cache.getvalue()
cache.close()
return result
@classmethod
def add_option(cls, name: str, value: Union[Callable, object]) -> Dict:
"""
向配置类中添加一个额外的配置
:param name: 配置的名字
:param value: 用于获取配置的函数或者类
:return: 配置类的所有配置
"""
if not hasattr(cls, '_options'):
cls._options: Dict[str, Union[Callable, object]] = {}
cls._options[name] = value
return cls._options
@staticmethod
def init_option(options_class: Type['Options'], init_value: Optional[dict] = None) -> 'Options':
return options_class(**init_value if init_value is not None else {})

View File

@ -0,0 +1,220 @@
# 本文件以 GNU Lesser General Public License v3.0GNU LGPL v3) 开源协议进行授权 (谢谢狐狸写出这么好的MCDR)
# 顺便说一句,我把所有的tab都改成了空格,因为我觉得空格比tab更好看(草,后半句是github copilot自动填充的)
"""
This part of code come from MCDReforged(https://github.com/Fallen-Breath/MCDReforged)
Thanks a lot to Fallen_Breath and MCDR contributors
GNU Lesser General Public License v3.0 (GNU LGPL v3)
"""
import re
from typing import List, Callable, Tuple, Optional, Union
"""
Plugin Version
"""
# beta.3 -> (beta, 3), random -> (random, None)
class ExtraElement:
DIVIDER = '.'
body: str
num: Optional[int]
def __init__(self, segment_str: str):
segments = segment_str.rsplit(self.DIVIDER, 1)
try:
self.body, self.num = segments[0], int(segments[1])
except (IndexError, ValueError):
self.body, self.num = segment_str, None
def __str__(self):
if self.num is None:
return self.body
return '{}{}{}'.format(self.body, self.DIVIDER, self.num)
def __lt__(self, other):
if not isinstance(other, type(self)):
raise TypeError()
if self.num is None or other.num is None:
return str(self) < str(other)
else:
return (self.body, self.num) < (other.body, other.num)
class Version:
"""
A version container that stores semver like version string
Example:
* ``"1.2.3"``
* ``"1.0.*"``
* ``"1.2.3-pre4+build.5"``
"""
EXTRA_ID_PATTERN = re.compile(r'|[-+0-9A-Za-z]+(\.[-+0-9A-Za-z]+)*')
WILDCARDS = ('*', 'x', 'X')
WILDCARD = -1
component: List[int]
has_wildcard: bool
pre: Optional[ExtraElement]
build: Optional[ExtraElement]
def __init__(self, version_str: str, *, allow_wildcard: bool = True):
"""
:param version_str: The version string to be parsed
:keyword allow_wildcard: If wildcard (``"*"``, ``"x"``, ``"X"``) is allowed. Default: ``True``
"""
if not isinstance(version_str, str):
raise VersionParsingError('Invalid input version string')
def separate_extra(text, char) -> Tuple[str, Optional[ExtraElement]]:
if char in text:
text, extra_str = text.split(char, 1)
if not self.EXTRA_ID_PATTERN.fullmatch(extra_str):
raise VersionParsingError('Invalid build string: ' + extra_str)
extra = ExtraElement(extra_str)
else:
extra = None
return text, extra
self.component = []
self.has_wildcard = False
version_str, self.build = separate_extra(version_str, '+')
version_str, self.pre = separate_extra(version_str, '-')
if len(version_str) == 0:
raise VersionParsingError('Version string is empty')
for comp in version_str.split('.'):
if comp in self.WILDCARDS:
self.component.append(self.WILDCARD)
self.has_wildcard = True
if not allow_wildcard:
raise VersionParsingError('Wildcard {} is not allowed'.format(comp))
else:
try:
num = int(comp)
except ValueError:
num = None
if num is None:
raise VersionParsingError('Invalid version number component: {}'.format(comp))
if num < 0:
raise VersionParsingError('Unsupported negatived number component: {}'.format(num))
self.component.append(num)
if len(self.component) == 0:
raise VersionParsingError('Empty version string')
def __str__(self):
version_str = '.'.join(map(lambda c: str(c) if c != self.WILDCARD else self.WILDCARDS[0], self.component))
if self.pre is not None:
version_str += '-' + str(self.pre)
if self.build is not None:
version_str += '+' + str(self.build)
return version_str
def __repr__(self):
return self.__str__()
def __getitem__(self, index: int) -> int:
if index < len(self.component):
return self.component[index]
else:
return self.WILDCARD if self.component[len(self.component) - 1] == self.WILDCARD else 0
def __lt__(self, other):
if not isinstance(other, Version):
raise TypeError('Cannot compare between instances of {} and {}'.format(Version.__name__, type(other).__name__))
for i in range(max(len(self.component), len(other.component))):
if self[i] == self.WILDCARD or other[i] == self.WILDCARD:
continue
if self[i] != other[i]:
return self[i] < other[i]
if self.pre is not None and other.pre is not None:
return self.pre < other.pre
elif self.pre is not None:
return not other.has_wildcard
elif other.pre is not None:
return False
else:
return False
def __eq__(self, other):
return not self < other and not other < self
def __le__(self, other):
return self == other or self < other
def compare_to(self, other):
if self < other:
return -1
elif self > other:
return 1
else:
return 0
DEFAULT_CRITERION_OPERATOR = '='
class Criterion:
def __init__(self, opt: str, base_version: Version, criterion: Callable[[Version, Version], bool]):
self.opt = opt
self.base_version = base_version
self.criterion = criterion
def test(self, target: Union[Version, str]):
return self.criterion(self.base_version, target)
def __str__(self):
return '{}{}'.format(self.opt if self.opt != DEFAULT_CRITERION_OPERATOR else '', self.base_version)
class VersionRequirement:
"""
A version requirement tester
It can test if a given :class:`Version` object matches its requirement
"""
CRITERIONS = {
'<=': lambda base, ver: ver <= base,
'>=': lambda base, ver: ver >= base,
'<': lambda base, ver: ver < base,
'>': lambda base, ver: ver > base,
'=': lambda base, ver: ver == base,
'^': lambda base, ver: ver >= base and ver[0] == base[0],
'~': lambda base, ver: ver >= base and ver[0] == base[0] and ver[1] == base[1],
}
def __init__(self, requirements: str):
"""
:param requirements: The requirement string, which contains several version predicates connected by space character.
e.g. ``">=1.0.x"``, ``"^2.9"``, ``">=1.2.0 <1.4.3"``
"""
if not isinstance(requirements, str):
raise VersionParsingError('Requirements should be a str, not {}'.format(type(requirements).__name__))
self.criterions = [] # type: List[Criterion]
for requirement in requirements.split(' '):
if len(requirement) > 0:
for prefix, func in self.CRITERIONS.items():
if requirement.startswith(prefix):
opt = prefix
base_version = requirement[len(prefix):]
break
else:
opt = DEFAULT_CRITERION_OPERATOR
base_version = requirement
self.criterions.append(Criterion(opt, Version(base_version), self.CRITERIONS[opt]))
def accept(self, version: Union[Version, str]):
if isinstance(version, str):
version = Version(version)
for criterion in self.criterions:
if not criterion.test(version):
return False
return True
def __str__(self):
return ' '.join(map(str, self.criterions))
class VersionParsingError(ValueError):
pass

View File

@ -1,21 +0,0 @@
# -------------------------------
# Difficult Rocket
# Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com
# All rights reserved
# -------------------------------
from typing import List
class BaseHandler:
...
class Logger:
level: int = 0
handlers: List[BaseHandler] = []
enable: bool = True
class BaseFormatter:
...

View File

@ -0,0 +1,7 @@
# -------------------------------
# Difficult Rocket
# Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com
# All rights reserved
# -------------------------------

View File

@ -0,0 +1,9 @@
# -------------------------------
# Difficult Rocket
# Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com
# All rights reserved
# -------------------------------
from lib_not_dr.types.options import Options

View File

@ -0,0 +1,33 @@
# -------------------------------
# Difficult Rocket
# Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com
# All rights reserved
# -------------------------------
import time
from types import FrameType
from typing import List, Optional
from lib_not_dr.types.options import Options
class LogMessage(Options):
name = 'LogMessage'
# 消息内容本身的属性
messages: List[str] = []
end: str = '\n'
split: str = ' '
# 消息的属性
flush: bool = True
level: int = 20
log_time: float = time.time_ns()
logger_name: str = 'root'
logger_tag: Optional[str] = None
stack_trace: Optional[FrameType] = None
# [App -> Logger -> Handler -> Formatter]-> Queue(log) -> [(File, Socket) Output] ?
# |-> Console Output

View File

@ -6,7 +6,7 @@ build-backend = "pdm.pep517.api"
[project]
name = "difficult-rocket"
version = "0.8.7.1"
version = "0.8.7.2"
description = "A rocket game"
authors = [
{name = "shenjackyuanjie", email = "3695888@qq.com"}