Compare commits
39 Commits
enhance/lo
...
main
Author | SHA1 | Date | |
---|---|---|---|
a1c3fb4aab | |||
0e642f8611 | |||
59497b596f | |||
367209cbe5 | |||
84e16ef9af | |||
3537cc9168 | |||
3957cf5a29 | |||
a318ca62e8 | |||
7e1b704543 | |||
48773547c9 | |||
56e5c0af80 | |||
d7b7e05a76 | |||
8baf2d4d70 | |||
716f735fe7 | |||
e8b040ab97 | |||
364624a996 | |||
4463dbfa6b | |||
67a5c14ec7 | |||
7f09f336ef | |||
390bdae762 | |||
a9366ac476 | |||
a22749deb8 | |||
8b82b049e6 | |||
eb153ae4bc | |||
f0eb5480b5 | |||
e1b20e71a9 | |||
91d70dd332 | |||
5ff3468293 | |||
40462cf663 | |||
a8f326dca9 | |||
4fa5089819 | |||
e404486ccb | |||
a4e63ef73f | |||
987631c0f3 | |||
d544090691 | |||
be8e009901 | |||
544c180e72 | |||
ea2c084fbe | |||
fc73ba2367 |
92
README.md
92
README.md
@ -6,101 +6,17 @@ A python lib came from [Difficult Rocket](https://github.com/shenjackyuanjie/Dif
|
|||||||
|
|
||||||
## Information/信息
|
## Information/信息
|
||||||
|
|
||||||
- Version / 版本: 0.3.0
|
- Version / 版本: 0.4.0
|
||||||
- Author / 作者: shenjackyuanjie <3695888@qq.com>
|
- Author / 作者: shenjackyuanjie <3695888@qq.com>
|
||||||
|
|
||||||
[shenjackyuanjie](https://github.com/shenjackyuanjie)
|
[shenjackyuanjie](https://github.com/shenjackyuanjie)
|
||||||
|
|
||||||
> [更新日志|Change Log](docs/change_log.md)
|
> [更新日志|Change Log](docs/change_logs)
|
||||||
|
|
||||||
### License/许可证
|
### License/许可证
|
||||||
|
|
||||||
[MPL-2.0](https://www.mozilla.org/en-US/MPL/2.0/)
|
[MPL-2.0](https://www.mozilla.org/en-US/MPL/2.0/)
|
||||||
|
|
||||||
## 安装/Install
|
## 剩余文档已经迁移到 [dr.shenjack.top](https://dr.shenjack.top/main/lib-not-dr/)
|
||||||
|
|
||||||
```bash title="install.sh"
|
## The rest of the documentation have been moved to [dr.shenjack.top](https://dr.shenjack.top/main/lib-not-dr/)
|
||||||
pip install lib-not-dr
|
|
||||||
pip install lib-not-dr[nuitka]
|
|
||||||
# install with nuitka support
|
|
||||||
```
|
|
||||||
|
|
||||||
## 发布计划 / Release Plan
|
|
||||||
|
|
||||||
> [文档/Docs](/docs/release-plan/summary.md)
|
|
||||||
|
|
||||||
## 使用/Usage
|
|
||||||
|
|
||||||
### Logger
|
|
||||||
|
|
||||||
> WIP
|
|
||||||
> 等待 0.4.0
|
|
||||||
|
|
||||||
```python title="logger.py"
|
|
||||||
from lib_not_dr import loggers
|
|
||||||
|
|
||||||
logger = loggers.get_logger("test")
|
|
||||||
|
|
||||||
logger.fine('Hello World!')
|
|
||||||
logger.debug('Hello World!')
|
|
||||||
logger.trace('Hello tracing!')
|
|
||||||
logger.info('Hello World!') # info!
|
|
||||||
logger.warn('warnnnnnnn')
|
|
||||||
logger.error('Hello World!')
|
|
||||||
logger.fatal('good bye world')
|
|
||||||
|
|
||||||
# tag
|
|
||||||
logger.info('this message if from tag', tag='test')
|
|
||||||
logger.debug('this debug log if from admin', tag='admin')
|
|
||||||
|
|
||||||
# end
|
|
||||||
logger.debug('and this message ends with none', end=' ')
|
|
||||||
logger.trace('so this message will be in the same line', tag='same line!')
|
|
||||||
```
|
|
||||||
|
|
||||||
### Nuitka pyproject paser
|
|
||||||
|
|
||||||
> `pyproject.toml` 内的配置
|
|
||||||
>
|
|
||||||
> Config in `pyproject.toml`
|
|
||||||
|
|
||||||
前往 [example/nuitka](/example/nuitka) 查看更多例子
|
|
||||||
|
|
||||||
```toml title="pyproject.toml"
|
|
||||||
[tool.lndl.nuitka.cli]
|
|
||||||
main = "main.py"
|
|
||||||
# --main=main.py
|
|
||||||
standalone = true
|
|
||||||
onefile = false
|
|
||||||
[tool.lndl.nuitka]
|
|
||||||
script = "xxx.py"
|
|
||||||
```
|
|
||||||
|
|
||||||
> 通过 `lndl_nuitka` 命令行工具使用
|
|
||||||
>
|
|
||||||
> Use with `lndl_nuitka` command line tool
|
|
||||||
|
|
||||||
```bash
|
|
||||||
lndl_nuitka .
|
|
||||||
lndl_nuitka . -- --onefile
|
|
||||||
# add --onefile to nuitka
|
|
||||||
lndl_nuitka . -y
|
|
||||||
# run without confirmation
|
|
||||||
lndl_nuitka . -n
|
|
||||||
# do not run
|
|
||||||
```
|
|
||||||
|
|
||||||
> 通过 `lib_not_dr.nuitka.reader` 模块使用
|
|
||||||
>
|
|
||||||
> Use with `lib_not_dr.nuitka.reader`
|
|
||||||
|
|
||||||
```python
|
|
||||||
from tomli import loads
|
|
||||||
from lib_not_dr.nuitka.reader import main, run_nuitka
|
|
||||||
|
|
||||||
pyproject_toml = loads(open("pyproject.toml", "r").read())
|
|
||||||
nuitka_config = pyproject_toml["tool"]["lndl"]["nuitka"]
|
|
||||||
nuitka_config["product_version"] = "0.1.0"
|
|
||||||
command = main(nuitka_config)
|
|
||||||
run_nuitka(command)
|
|
||||||
```
|
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
# lndl 0.3.0 (draft)
|
|
||||||
|
|
||||||
## 0.3.0 (draft)
|
|
||||||
|
|
||||||
> logger 继续后延
|
|
||||||
|
|
||||||
### lndl-nuitka
|
|
||||||
|
|
||||||
- [x] 修改配置路径
|
|
||||||
- 从 `[tool.lndl.nuitka]` 变为 `[tool.lndl.nuitka.cli]`
|
|
||||||
- [x] 添加新配置
|
|
||||||
- 添加 `[tool.lndl.nuitka.script]`
|
|
||||||
- 用于项目动态修改 nuitka 脚本中的信息
|
|
||||||
- 例如 `file-version` `product-version` 等
|
|
||||||
|
|
||||||
- 重构了一部分解析逻辑
|
|
||||||
- 现在配置路径位于 `[tool.lndl.nuitka.cli]`
|
|
||||||
- 可以添加脚本用于动态解析依赖
|
|
||||||
- `[tool.lndl.nuitka]`
|
|
||||||
- `script = "script.py"`
|
|
||||||
|
|
||||||
### Logger
|
|
||||||
|
|
||||||
> 预计 `0.4` 发布?
|
|
||||||
|
|
||||||
- [x] 完成配置解析
|
|
||||||
- [x] 支持读取配置之后自动应用
|
|
||||||
|
|
||||||
- 将 `logger` 模块重命名为 `loggers`
|
|
||||||
|
|
||||||
- 添加了 `get_config` 函数
|
|
||||||
- 用于获取全局配置
|
|
||||||
- 我也不确定有啥用捏
|
|
||||||
- 添加了 `read_config` 函数
|
|
||||||
- 用于向指定 `ConfigStorage`/全局 `ConfigStorage` 实例中添加配置
|
|
||||||
- 添加了 `get_logger` 函数
|
|
||||||
- 用于从指定 `ConfigStorage`/全局 `ConfigStorage` 实例中获取指定名称的 `Logger` 实例
|
|
||||||
- `Logger`
|
|
||||||
- 添加了 `clone_logger` 函数
|
|
||||||
- 用于克隆一个新的配置相同的 `Logger` 实例
|
|
@ -1,5 +0,0 @@
|
|||||||
# lndl 0.4.0 (draft)
|
|
||||||
|
|
||||||
## Logger
|
|
||||||
|
|
||||||
- [ ] 达到可用级别
|
|
@ -1,184 +0,0 @@
|
|||||||
# lndl 老版本的更新日志
|
|
||||||
|
|
||||||
## 0.2.3
|
|
||||||
|
|
||||||
### lndl-nuitka
|
|
||||||
|
|
||||||
- 现在如果没有找到 toml 解析器
|
|
||||||
- 提示安装库 的信息里不再带有 `tomllib` 了
|
|
||||||
- 标准库也想装 ? 🤣
|
|
||||||
|
|
||||||
## 0.2.2
|
|
||||||
|
|
||||||
### Logger
|
|
||||||
|
|
||||||
- `MainFormatter`
|
|
||||||
- `_trace_format`
|
|
||||||
- 通过不需要获取绝对路径时直接使用获取到的路径减少用时
|
|
||||||
- `LogMessage`
|
|
||||||
- 不再继承 `Options`
|
|
||||||
- `Options` 的初始化太慢了
|
|
||||||
- 终于是开始扣时间了
|
|
||||||
- `ConfigStorage`
|
|
||||||
- 完善了功能 (虽然还是 WIP)
|
|
||||||
|
|
||||||
## 0.2.1
|
|
||||||
|
|
||||||
> logger 继续后延
|
|
||||||
|
|
||||||
### Fix
|
|
||||||
|
|
||||||
- `Logger`
|
|
||||||
- `MainFormatter`
|
|
||||||
- 修复了会导致 `ColorFormatter` 崩溃的问题
|
|
||||||
|
|
||||||
## 0.2.0
|
|
||||||
|
|
||||||
> 内容和 0.2.0-rc.10 相同
|
|
||||||
|
|
||||||
## 0.2.0-rc.10
|
|
||||||
|
|
||||||
### lndl-nuitka
|
|
||||||
|
|
||||||
- 将 `lndl-nuitka` 中的 `subprocess.run` 方法修改为
|
|
||||||
- Linux / MacOS: `subprocess.run(shell=False)`
|
|
||||||
- Windows: `subprocess.run(shell=True)`
|
|
||||||
|
|
||||||
## 0.2.0-rc.9
|
|
||||||
|
|
||||||
### lndl-nuitka
|
|
||||||
|
|
||||||
- 将运行方法修改为 `subprocess.run(shell=False)`
|
|
||||||
|
|
||||||
## 0.2.0-rc.4
|
|
||||||
|
|
||||||
### lndl-nuitka
|
|
||||||
|
|
||||||
- 在 `lndl-nuitka` 中使用新添加的 `subprocess_to_bash` 函数展示命令
|
|
||||||
- 保证展示的命令可以直接运行
|
|
||||||
|
|
||||||
> 真的就是在刷版本号啊
|
|
||||||
|
|
||||||
## 0.2.0-rc.3
|
|
||||||
|
|
||||||
### lndl-nuitka
|
|
||||||
|
|
||||||
- 在 `arg_parser` 中添加了函数 `subprocess_to_bash`
|
|
||||||
- 用于将 `arg_parser` 中的参数转换为 `bash` 命令
|
|
||||||
- 理论上可以直接运行
|
|
||||||
|
|
||||||
## 0.2.0-rc.1/2
|
|
||||||
|
|
||||||
### lndl-nuitka
|
|
||||||
|
|
||||||
- 修复了一些细节问题 (反正我懒得统计具体内容了)
|
|
||||||
- 现在不会在没有给定附加参数的时候报 `invalid args:` 了
|
|
||||||
- 似乎没有别的细节了?
|
|
||||||
|
|
||||||
## 0.2.0-beta.2
|
|
||||||
|
|
||||||
### lndl-nuitka
|
|
||||||
|
|
||||||
- 可以使用 `--` 单独添加参数了
|
|
||||||
- 例如 `lndl-nuitka -- --onefile`
|
|
||||||
- 会将 `--onefile` 添加到 `nuitka` 的参数中
|
|
||||||
|
|
||||||
## 0.2.0-beta.0/1
|
|
||||||
|
|
||||||
### 重构
|
|
||||||
|
|
||||||
- 重构了文件目录结构
|
|
||||||
- 保证 `setuptools` 可以正常工作
|
|
||||||
|
|
||||||
### lndl-nuitka
|
|
||||||
|
|
||||||
- 添加了脚本 `lndl-nuitka`
|
|
||||||
- 用于解析 `pyproject.toml` 中的 `tool.lndl.nuitka` 部分
|
|
||||||
|
|
||||||
## 0.2.0-alpha0
|
|
||||||
|
|
||||||
### Logger
|
|
||||||
|
|
||||||
- 添加了 Logger (虽说 0.1.8 就有了)
|
|
||||||
- 目前入口点位于 `lib_not_dr.logger.logger.Logger`
|
|
||||||
|
|
||||||
## 0.1.8
|
|
||||||
|
|
||||||
- 为 `types.Options` 添加了 `_check_option` 选项
|
|
||||||
- 为 `True` 时 会检查参数是否合法 (在类属性中已经定义了)
|
|
||||||
|
|
||||||
### Logger
|
|
||||||
|
|
||||||
- WIP (wait for 0.2.0)
|
|
||||||
|
|
||||||
## 0.1.7
|
|
||||||
|
|
||||||
- 修复了 `CompilerHelper` 中的问题
|
|
||||||
- 重复添加 `--output-dir` 参数
|
|
||||||
- 参数喜加 2
|
|
||||||
- `--onefile`
|
|
||||||
- `--onefile-tempdir-spec`
|
|
||||||
|
|
||||||
## 0.1.6
|
|
||||||
|
|
||||||
- 优化了 `CompilerHelper` 的一些周围实现
|
|
||||||
- 参数喜加一
|
|
||||||
- `--report`
|
|
||||||
|
|
||||||
## 0.1.5
|
|
||||||
|
|
||||||
- 修复了 `types.Options` 中的 `as_markdown` 实现细节
|
|
||||||
- 现在不会长度溢出了
|
|
||||||
|
|
||||||
## 0.1.4
|
|
||||||
|
|
||||||
- 更改了 `types.Options` 中的 `as_markdown` 实现方式
|
|
||||||
- 原来的实现会导致 如果终端长度 > 所需长度 时, 循环一直进行
|
|
||||||
- 同时现在分配长度是均分
|
|
||||||
|
|
||||||
## 0.1.3
|
|
||||||
|
|
||||||
- 修改了 `README.md` 内的版本号
|
|
||||||
|
|
||||||
## 0.1.2
|
|
||||||
|
|
||||||
- 改进了 `types.Options`
|
|
||||||
- 现在 `as_markdown` 如果没有指定最长长度 会自动适配输出所用终端的宽度
|
|
||||||
- 同时如果指定最大长度, 也会更灵活的适配
|
|
||||||
- 补全了一些文档
|
|
||||||
|
|
||||||
## 0.1.1
|
|
||||||
|
|
||||||
- 为 `CompilerHelper` 添加了 `remove_output` 的选项
|
|
||||||
- 用于删除编译后的过程文件
|
|
||||||
|
|
||||||
## 0.1.0
|
|
||||||
|
|
||||||
- 添加了一些文档
|
|
||||||
- `CompilerHelper`
|
|
||||||
- 添加了 `run_after_build` 参数
|
|
||||||
- 用于在编译完成后执行程序
|
|
||||||
- 添加了 `compat_nuitka_version` 参数
|
|
||||||
- 用于验证 nuitka 版本是否兼容
|
|
||||||
|
|
||||||
## 0.0.4
|
|
||||||
|
|
||||||
添加了项目的 url
|
|
||||||
|
|
||||||
## 0.0.3
|
|
||||||
|
|
||||||
继续添加了一些文档
|
|
||||||
|
|
||||||
## 0.0.2
|
|
||||||
|
|
||||||
添加了一些文档
|
|
||||||
|
|
||||||
## 0.0.1
|
|
||||||
|
|
||||||
- 添加了
|
|
||||||
- `nuitka.compile`
|
|
||||||
- `CompilerHelper`
|
|
||||||
- `types.options`
|
|
||||||
- `Options`
|
|
||||||
- `types.version`
|
|
||||||
- `Version`
|
|
@ -1,7 +0,0 @@
|
|||||||
# lndl 更新日志
|
|
||||||
|
|
||||||
## [0.4.0](/docs/change_logs/0-4.md)
|
|
||||||
|
|
||||||
## [0.3.0](/docs/change_logs/0-3.md)
|
|
||||||
|
|
||||||
## [old](/docs/change_logs/old.md)
|
|
@ -1,88 +0,0 @@
|
|||||||
# Command parser
|
|
||||||
|
|
||||||
> By shenjackyuanjie And MSDNicrosoft and Harvey Huskey
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```python
|
|
||||||
from typing import Callable, Self, Optional, List
|
|
||||||
|
|
||||||
class Literal:
|
|
||||||
def __init__(self, name: str):
|
|
||||||
self.name = name
|
|
||||||
self.sub = []
|
|
||||||
self._tip = ''
|
|
||||||
|
|
||||||
def __call__(self, *nodes) -> Self:
|
|
||||||
self.sub += nodes
|
|
||||||
return self
|
|
||||||
|
|
||||||
def run(self, func: Callable[[List[str]], None]) -> Self:
|
|
||||||
return self
|
|
||||||
|
|
||||||
def tip(self, tip: str) -> Self:
|
|
||||||
return self
|
|
||||||
|
|
||||||
def arg(self, parse_func: Callable[[str], Optional[type]]) -> Self:
|
|
||||||
return self
|
|
||||||
|
|
||||||
def error(self, callback: Callable[[str], None]) -> Self:
|
|
||||||
return self
|
|
||||||
|
|
||||||
|
|
||||||
builder = Literal('test1')(
|
|
||||||
Literal('a')
|
|
||||||
.run(lambda x: print(x)),
|
|
||||||
Literal('b')
|
|
||||||
.tip('this is b')
|
|
||||||
.run(lambda x: print(x))(
|
|
||||||
Literal('c')
|
|
||||||
.run(lambda x: print(x)),
|
|
||||||
Literal('d')
|
|
||||||
.run(lambda x: print(x)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
build
|
|
||||||
|
|
||||||
- test
|
|
||||||
- main
|
|
||||||
- arg:text
|
|
||||||
- go
|
|
||||||
- arg:int
|
|
||||||
- run
|
|
||||||
|
|
||||||
- command: 主节点
|
|
||||||
- literal: 字面量节点
|
|
||||||
|
|
||||||
## 设计思路
|
|
||||||
|
|
||||||
```rust
|
|
||||||
pub enum ArgumentType {
|
|
||||||
String(String),
|
|
||||||
Int(i128),
|
|
||||||
Bool(bool),
|
|
||||||
Float(f64),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type CallBackFunc = Fn(Vec<(String, ArgumentType)>) -> bool;
|
|
||||||
|
|
||||||
pub enum CallBack {
|
|
||||||
Fn(CallBackFunc),
|
|
||||||
Message(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Command {
|
|
||||||
fn new(nodes: Vec<Command>) -> Self;
|
|
||||||
// fn parse(&self, input: String) -> Result<Command, Error>;
|
|
||||||
fn literal(&self, name: String, then: Vec<Command>) -> &self;
|
|
||||||
fn argument(&self, name: String, shortcut: List<String>, optional: Option<bool>) -> &self;
|
|
||||||
fn flag(&self, name: String, shortcut: List<String>, ) -> &self;
|
|
||||||
fn error(&self, ret: CallBack) -> &self;
|
|
||||||
fn run(&self, ret: CallBack) -> &self;
|
|
||||||
fn tip(&self, tip: String) -> &self;
|
|
||||||
fn to_doc(&self) -> String;
|
|
||||||
fn exec(&self) -> Option;
|
|
||||||
}
|
|
||||||
```
|
|
@ -55,4 +55,4 @@ def main():
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
lndl_logger()
|
lndl_logger()
|
||||||
logging_logger()
|
# logging_logger()
|
||||||
|
9
pdm.lock
generated
9
pdm.lock
generated
@ -1,9 +0,0 @@
|
|||||||
# This file is @generated by PDM.
|
|
||||||
# It is not intended for manual editing.
|
|
||||||
|
|
||||||
[metadata]
|
|
||||||
groups = ["default"]
|
|
||||||
cross_platform = true
|
|
||||||
static_urls = false
|
|
||||||
lock_version = "4.3"
|
|
||||||
content_hash = "sha256:cb30ff0b06924f6f0d5f726b84c255686a2e277a4180b00b7b6e427c05ca202b"
|
|
@ -21,7 +21,7 @@ classifiers = [
|
|||||||
# 系统支持.
|
# 系统支持.
|
||||||
"Operating System :: OS Independent",
|
"Operating System :: OS Independent",
|
||||||
]
|
]
|
||||||
license = { file = "LICENSE" }
|
license = { text = "MPL-2.0" }
|
||||||
dynamic = ["version"]
|
dynamic = ["version"]
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
@ -48,11 +48,18 @@ nuitka = [
|
|||||||
"tomli >= 2.0.1",
|
"tomli >= 2.0.1",
|
||||||
"nuitka", # any version
|
"nuitka", # any version
|
||||||
]
|
]
|
||||||
|
dev = ["twine"]
|
||||||
|
|
||||||
|
[tool.pdm.scripts]
|
||||||
|
pub = { call = "scripts.pub:main" }
|
||||||
|
pre_build = { call = "scripts.pub:clean_build" }
|
||||||
|
post_publish = { call = "scripts.pub:upload_gitea" }
|
||||||
|
|
||||||
[tool.setuptools.dynamic]
|
[tool.setuptools.dynamic]
|
||||||
version = { attr = "lib_not_dr._version_"}
|
version = { attr = "lib_not_dr._version_"}
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
|
Issues = "https://github.com/shenjackyuanjie/lib-not-dr/issues"
|
||||||
Homepage = "https://github.com/shenjackyuanjie/lib-not-dr"
|
Homepage = "https://github.com/shenjackyuanjie/lib-not-dr"
|
||||||
Repository = "https://github.com/shenjackyuanjie/lib-not-dr"
|
Repository = "https://github.com/shenjackyuanjie/lib-not-dr"
|
||||||
Changelog = "https://github.com/shenjackyuanjie/lib-not-dr/blob/main/docs/change_logs"
|
Changelog = "https://github.com/shenjackyuanjie/lib-not-dr/blob/main/docs/change_logs"
|
||||||
|
38
scripts/pub.py
Normal file
38
scripts/pub.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from typing import Union
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def clean(dir_path: Union[str, Path]):
|
||||||
|
"""
|
||||||
|
clean everything in dir
|
||||||
|
will not remove dir
|
||||||
|
"""
|
||||||
|
print(f"Cleaning {dir_path}")
|
||||||
|
path_dir = Path(dir_path) if isinstance(dir_path, str) else dir_path
|
||||||
|
for path in path_dir.rglob("*"):
|
||||||
|
if path.is_file():
|
||||||
|
path.unlink()
|
||||||
|
elif path.is_dir():
|
||||||
|
clean(path)
|
||||||
|
path.rmdir()
|
||||||
|
|
||||||
|
|
||||||
|
def clean_build():
|
||||||
|
clean("./dist")
|
||||||
|
clean("./build")
|
||||||
|
|
||||||
|
|
||||||
|
def upload_gitea():
|
||||||
|
twine_cmd = [
|
||||||
|
sys.executable,
|
||||||
|
'-m', 'twine',
|
||||||
|
'upload',
|
||||||
|
'--repository',
|
||||||
|
'gitea',
|
||||||
|
'./dist/*',
|
||||||
|
'--verbose'
|
||||||
|
]
|
||||||
|
subprocess.run(twine_cmd, shell=False, check=True)
|
@ -4,12 +4,15 @@
|
|||||||
# All rights reserved
|
# All rights reserved
|
||||||
# -------------------------------
|
# -------------------------------
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
# 兼容 3.8
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from lib_not_dr import loggers, nuitka, types, command
|
from lib_not_dr import loggers, nuitka, types, command
|
||||||
|
|
||||||
_version_ = "0.3.0"
|
_version_ = "0.4.0"
|
||||||
|
|
||||||
# fmt: off
|
# fmt: off
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
@ -99,13 +99,13 @@ class LogLevel(Options):
|
|||||||
|
|
||||||
# fmt: off
|
# fmt: off
|
||||||
from lib_not_dr.loggers.config import get_logger, get_config, read_config # noqa: E402
|
from lib_not_dr.loggers.config import get_logger, get_config, read_config # noqa: E402
|
||||||
|
from lib_not_dr.loggers import config # noqa: E402
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from lib_not_dr.loggers import logger
|
from lib_not_dr.loggers import logger
|
||||||
from lib_not_dr.loggers import formatter
|
from lib_not_dr.loggers import formatter
|
||||||
from lib_not_dr.loggers import outstream
|
from lib_not_dr.loggers import outstream
|
||||||
from lib_not_dr.loggers import structure
|
from lib_not_dr.loggers import structure
|
||||||
from lib_not_dr.loggers import config
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
# modules
|
# modules
|
||||||
|
@ -86,7 +86,11 @@ class ConfigStorage(Options):
|
|||||||
return sorted(cycles_set) # 返回排序后的循环列表
|
return sorted(cycles_set) # 返回排序后的循环列表
|
||||||
|
|
||||||
def parse_level(self, level_config: dict) -> Optional[int]:
|
def parse_level(self, level_config: dict) -> Optional[int]:
|
||||||
""" """
|
"""
|
||||||
|
Parse level config
|
||||||
|
:param level_config:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
level_found: Tuple[Optional[int], Optional[str]] = (
|
level_found: Tuple[Optional[int], Optional[str]] = (
|
||||||
level_config.get("level"),
|
level_config.get("level"),
|
||||||
level_config.get("level_name"),
|
level_config.get("level_name"),
|
||||||
@ -272,21 +276,22 @@ class ConfigStorage(Options):
|
|||||||
env = ConfigStorage()
|
env = ConfigStorage()
|
||||||
for logger_name, config in logger_config.items():
|
for logger_name, config in logger_config.items():
|
||||||
# get output for logger
|
# get output for logger
|
||||||
if "output" in config:
|
if "outputs" in config:
|
||||||
if self.outputs.get(config["output"]) is None:
|
for i, output_name in enumerate(config["outputs"]):
|
||||||
if self.fail_outputs.get(config["output"]) is None:
|
if self.outputs.get(output_name) is None:
|
||||||
|
if self.fail_outputs.get(output_name) is None:
|
||||||
self.log.error(
|
self.log.error(
|
||||||
f'Logger {logger_name} output {config["output"]} not found, ignored'
|
f'Logger {logger_name} output {output_name} not found, ignored'
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.log.error(
|
self.log.error(
|
||||||
f'Logger {logger_name} require a fail output {config["output"]}, ignored'
|
f'Logger {logger_name} require a fail output {output_name}, ignored'
|
||||||
)
|
)
|
||||||
env.fail_loggers[logger_name] = config
|
env.fail_loggers[logger_name] = config
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
config["output"] = self.outputs[config["output"]]
|
config["outputs"][i] = self.outputs[output_name]
|
||||||
if level := self.parse_level(config) is not None:
|
if (level := self.parse_level(config)) is not None:
|
||||||
config["level"] = level
|
config["level"] = level
|
||||||
if "level_name" in config:
|
if "level_name" in config:
|
||||||
config.pop("level_name")
|
config.pop("level_name")
|
||||||
@ -319,11 +324,13 @@ _storage = ConfigStorage(loggers={'root': Logger(logger_name='root')})
|
|||||||
|
|
||||||
|
|
||||||
def get_config() -> ConfigStorage:
|
def get_config() -> ConfigStorage:
|
||||||
|
global _storage
|
||||||
return _storage
|
return _storage
|
||||||
|
|
||||||
|
|
||||||
def get_logger(name: str = 'root', storage: Optional[ConfigStorage] = None) -> Logger:
|
def get_logger(name: str = 'root', storage: Optional[ConfigStorage] = None) -> Logger:
|
||||||
if storage is None:
|
if storage is None:
|
||||||
|
global _storage
|
||||||
storage = _storage
|
storage = _storage
|
||||||
|
|
||||||
if name not in storage.loggers:
|
if name not in storage.loggers:
|
||||||
@ -333,9 +340,42 @@ def get_logger(name: str = 'root', storage: Optional[ConfigStorage] = None) -> L
|
|||||||
return storage.loggers[name]
|
return storage.loggers[name]
|
||||||
|
|
||||||
|
|
||||||
|
def get_logger_from_old(name: str, old_name: str = 'root', storage: Optional[ConfigStorage] = None) -> Logger:
|
||||||
|
if storage is None:
|
||||||
|
global _storage
|
||||||
|
storage = _storage
|
||||||
|
|
||||||
|
if name not in storage.loggers:
|
||||||
|
root_log = get_logger(old_name, storage).clone_logger()
|
||||||
|
root_log.logger_name = name
|
||||||
|
storage.loggers[name] = root_log
|
||||||
|
return storage.loggers[name]
|
||||||
|
|
||||||
|
|
||||||
|
def add_logger(name: str, logger: Logger, storage: Optional[ConfigStorage] = None) -> None:
|
||||||
|
if storage is None:
|
||||||
|
global _storage
|
||||||
|
storage = _storage
|
||||||
|
|
||||||
|
storage.loggers[name] = logger
|
||||||
|
|
||||||
|
|
||||||
def read_config(log_config: Dict, storage: Optional[ConfigStorage] = None) -> ConfigStorage:
|
def read_config(log_config: Dict, storage: Optional[ConfigStorage] = None) -> ConfigStorage:
|
||||||
if storage is None:
|
if storage is None:
|
||||||
|
global _storage
|
||||||
storage = _storage
|
storage = _storage
|
||||||
|
|
||||||
storage.read_dict_config(log_config)
|
storage.read_dict_config(log_config)
|
||||||
return storage
|
return storage
|
||||||
|
|
||||||
|
|
||||||
|
# fmt: off
|
||||||
|
__all__ = [
|
||||||
|
"get_config",
|
||||||
|
"get_logger",
|
||||||
|
"_storage",
|
||||||
|
"read_config",
|
||||||
|
"ConfigStorage",
|
||||||
|
]
|
||||||
|
# 整的跟 export 一样
|
||||||
|
# fmt: on
|
||||||
|
@ -76,9 +76,9 @@ class BaseFormatter(Options):
|
|||||||
template = Template(template)
|
template = Template(template)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return template.substitute(**info)
|
return template.substitute(info)
|
||||||
except (KeyError, ValueError):
|
except (KeyError, ValueError):
|
||||||
return template.safe_substitute(**info)
|
return template.safe_substitute(info)
|
||||||
|
|
||||||
def _format(self, message: FormattingMessage) -> FormattingMessage:
|
def _format(self, message: FormattingMessage) -> FormattingMessage:
|
||||||
"""
|
"""
|
||||||
|
@ -34,7 +34,7 @@ class BaseColorFormatter(BaseFormatter):
|
|||||||
# Debug: cyan
|
# Debug: cyan
|
||||||
LogLevel.debug: "\033[0;36m",
|
LogLevel.debug: "\033[0;36m",
|
||||||
# Info: white
|
# Info: white
|
||||||
LogLevel.info: "\033[0;37m",
|
LogLevel.info: "\033[0;38;2;255;255;255m",
|
||||||
# Warn: yellow
|
# Warn: yellow
|
||||||
LogLevel.warn: "\033[0;33m",
|
LogLevel.warn: "\033[0;33m",
|
||||||
# Error: red
|
# Error: red
|
||||||
@ -44,6 +44,8 @@ class BaseColorFormatter(BaseFormatter):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def get_color(self, message: FormattingMessage) -> str:
|
def get_color(self, message: FormattingMessage) -> str:
|
||||||
|
if message[0].level in self.color:
|
||||||
|
return self.color[message[0].level]
|
||||||
for level in self.color:
|
for level in self.color:
|
||||||
if message[0].level <= level:
|
if message[0].level <= level:
|
||||||
break
|
break
|
||||||
@ -65,7 +67,7 @@ class LevelColorFormatter(BaseColorFormatter):
|
|||||||
# Debug: cyan
|
# Debug: cyan
|
||||||
LogLevel.debug: "\033[38;2;133;138;149m",
|
LogLevel.debug: "\033[38;2;133;138;149m",
|
||||||
# Info: white
|
# Info: white
|
||||||
LogLevel.info: "\033[0;37m",
|
LogLevel.info: "\033[0;38;2;255;255;255m",
|
||||||
# Warn: yellow
|
# Warn: yellow
|
||||||
LogLevel.warn: "\033[0;33m",
|
LogLevel.warn: "\033[0;33m",
|
||||||
# Error: red
|
# Error: red
|
||||||
@ -104,7 +106,7 @@ class LoggerColorFormatter(BaseColorFormatter):
|
|||||||
# Debug: cyan
|
# Debug: cyan
|
||||||
LogLevel.debug: "\033[0;36m",
|
LogLevel.debug: "\033[0;36m",
|
||||||
# Info: white
|
# Info: white
|
||||||
LogLevel.info: "\033[0;37m",
|
LogLevel.info: "\033[0;38;2;255;255;255m",
|
||||||
# Warn: yellow
|
# Warn: yellow
|
||||||
LogLevel.warn: "\033[0;33m",
|
LogLevel.warn: "\033[0;33m",
|
||||||
# Error: red
|
# Error: red
|
||||||
@ -151,7 +153,7 @@ class TimeColorFormatter(BaseColorFormatter):
|
|||||||
# Debug: cyan
|
# Debug: cyan
|
||||||
LogLevel.debug: "\033[0;36m",
|
LogLevel.debug: "\033[0;36m",
|
||||||
# Info: white
|
# Info: white
|
||||||
LogLevel.info: "\033[0;37m",
|
LogLevel.info: "\033[0;38;2;255;255;255m",
|
||||||
# Warn: yellow
|
# Warn: yellow
|
||||||
LogLevel.warn: "\033[0;33m",
|
LogLevel.warn: "\033[0;33m",
|
||||||
# Error: red
|
# Error: red
|
||||||
@ -192,7 +194,7 @@ class TraceColorFormatter(BaseColorFormatter):
|
|||||||
# Debug: cyan
|
# Debug: cyan
|
||||||
LogLevel.debug: "\033[38;2;0;255;180m",
|
LogLevel.debug: "\033[38;2;0;255;180m",
|
||||||
# Info: white
|
# Info: white
|
||||||
LogLevel.info: "\033[38;2;0;255;180m",
|
LogLevel.info: "\033[0;38;2;255;255;255m",
|
||||||
# Warn: yellow
|
# Warn: yellow
|
||||||
LogLevel.warn: "\033[38;2;0;255;180m",
|
LogLevel.warn: "\033[38;2;0;255;180m",
|
||||||
# Error: red
|
# Error: red
|
||||||
@ -245,7 +247,7 @@ class MessageColorFormatter(BaseColorFormatter):
|
|||||||
# Debug: blue
|
# Debug: blue
|
||||||
LogLevel.debug: "\033[38;2;138;173;244m",
|
LogLevel.debug: "\033[38;2;138;173;244m",
|
||||||
# Info: no color
|
# Info: no color
|
||||||
LogLevel.info: "",
|
LogLevel.info: "\033[0;38;2;255;255;255m",
|
||||||
# Warn: yellow
|
# Warn: yellow
|
||||||
LogLevel.warn: "\033[0;33m",
|
LogLevel.warn: "\033[0;33m",
|
||||||
# Error: red
|
# Error: red
|
||||||
|
@ -21,6 +21,7 @@ class Logger(Options):
|
|||||||
outputs: List[BaseOutputStream] = [StdioOutputStream()]
|
outputs: List[BaseOutputStream] = [StdioOutputStream()]
|
||||||
|
|
||||||
logger_name: str = "root"
|
logger_name: str = "root"
|
||||||
|
default_tag: Optional[str] = None
|
||||||
|
|
||||||
enable: bool = True
|
enable: bool = True
|
||||||
level: int = 20 # info
|
level: int = 20 # info
|
||||||
@ -37,6 +38,7 @@ class Logger(Options):
|
|||||||
enable=self.enable,
|
enable=self.enable,
|
||||||
level=self.level,
|
level=self.level,
|
||||||
outputs=self.outputs.copy(),
|
outputs=self.outputs.copy(),
|
||||||
|
default_tag=self.default_tag,
|
||||||
)
|
)
|
||||||
|
|
||||||
def log_for(self, level: int) -> bool:
|
def log_for(self, level: int) -> bool:
|
||||||
@ -54,29 +56,54 @@ class Logger(Options):
|
|||||||
def add_output(self, output: BaseOutputStream) -> None:
|
def add_output(self, output: BaseOutputStream) -> None:
|
||||||
"""
|
"""
|
||||||
Add an output to the list of outputs.
|
Add an output to the list of outputs.
|
||||||
|
:param output:
|
||||||
Args:
|
:return:
|
||||||
output (BaseOutputStream): The output to be added.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
None
|
|
||||||
"""
|
"""
|
||||||
self.outputs.append(output)
|
self.outputs.append(output)
|
||||||
self.level = min(self.level, output.level)
|
self.level = min(self.level, output.level)
|
||||||
|
|
||||||
def remove_output(self, output: BaseOutputStream) -> None:
|
def remove_output(self, output: BaseOutputStream) -> None:
|
||||||
"""
|
"""
|
||||||
Removes the specified output from the list of outputs.
|
Remove an output from the list of outputs.
|
||||||
|
:param output: BaseOutputStream
|
||||||
Args:
|
:return:
|
||||||
output (BaseOutputStream): The output to be removed.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
None
|
|
||||||
"""
|
"""
|
||||||
self.outputs.remove(output)
|
self.outputs.remove(output)
|
||||||
self.level = max(self.level, *[output.level for output in self.outputs])
|
self.level = max(self.level, *[output.level for output in self.outputs])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tag(self):
|
||||||
|
"""
|
||||||
|
Get the default tag.
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return self.default_tag
|
||||||
|
|
||||||
|
@tag.setter
|
||||||
|
def tag(self, tag: str) -> None:
|
||||||
|
"""
|
||||||
|
Set the default tag.
|
||||||
|
:param tag: str
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
self.default_tag = tag
|
||||||
|
|
||||||
|
def set_tag(self, tag: str) -> "Logger":
|
||||||
|
"""
|
||||||
|
Set the default tag.
|
||||||
|
:param tag: str
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
self.default_tag = tag
|
||||||
|
return self
|
||||||
|
|
||||||
|
def clear_tag(self) -> None:
|
||||||
|
"""
|
||||||
|
Clear the default tag.
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
self.default_tag = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def global_level(self) -> int:
|
def global_level(self) -> int:
|
||||||
"""
|
"""
|
||||||
@ -108,7 +135,7 @@ class Logger(Options):
|
|||||||
tag: Optional[str] = None,
|
tag: Optional[str] = None,
|
||||||
end: str = "\n",
|
end: str = "\n",
|
||||||
split: str = " ",
|
split: str = " ",
|
||||||
flush: bool = True,
|
flush: bool = None,
|
||||||
level: int = 20, # info
|
level: int = 20, # info
|
||||||
# log_time: Optional[float] = None,
|
# log_time: Optional[float] = None,
|
||||||
# logger_name: str = 'root',
|
# logger_name: str = 'root',
|
||||||
@ -131,6 +158,9 @@ class Logger(Options):
|
|||||||
stack_trace = up_stack
|
stack_trace = up_stack
|
||||||
else:
|
else:
|
||||||
stack_trace = stack
|
stack_trace = stack
|
||||||
|
# 处理标签
|
||||||
|
if tag is None and self.default_tag is not None:
|
||||||
|
tag = self.default_tag
|
||||||
|
|
||||||
message = LogMessage(
|
message = LogMessage(
|
||||||
messages=messages, # type: ignore
|
messages=messages, # type: ignore
|
||||||
@ -158,7 +188,7 @@ class Logger(Options):
|
|||||||
tag: Optional[str] = None,
|
tag: Optional[str] = None,
|
||||||
end: str = "\n",
|
end: str = "\n",
|
||||||
split: str = " ",
|
split: str = " ",
|
||||||
flush: bool = True,
|
flush: bool = None,
|
||||||
stack_trace: Optional[FrameType] = None,
|
stack_trace: Optional[FrameType] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
if not self.log_for(LogLevel.info):
|
if not self.log_for(LogLevel.info):
|
||||||
@ -179,7 +209,7 @@ class Logger(Options):
|
|||||||
tag: Optional[str] = None,
|
tag: Optional[str] = None,
|
||||||
end: str = "\n",
|
end: str = "\n",
|
||||||
split: str = " ",
|
split: str = " ",
|
||||||
flush: bool = True,
|
flush: bool = None,
|
||||||
stack_trace: Optional[FrameType] = None,
|
stack_trace: Optional[FrameType] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
if not self.log_for(LogLevel.trace):
|
if not self.log_for(LogLevel.trace):
|
||||||
@ -200,7 +230,7 @@ class Logger(Options):
|
|||||||
tag: Optional[str] = None,
|
tag: Optional[str] = None,
|
||||||
end: str = "\n",
|
end: str = "\n",
|
||||||
split: str = " ",
|
split: str = " ",
|
||||||
flush: bool = True,
|
flush: bool = None,
|
||||||
stack_trace: Optional[FrameType] = None,
|
stack_trace: Optional[FrameType] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
if not self.log_for(LogLevel.fine):
|
if not self.log_for(LogLevel.fine):
|
||||||
@ -221,7 +251,7 @@ class Logger(Options):
|
|||||||
tag: Optional[str] = None,
|
tag: Optional[str] = None,
|
||||||
end: str = "\n",
|
end: str = "\n",
|
||||||
split: str = " ",
|
split: str = " ",
|
||||||
flush: bool = True,
|
flush: bool = None,
|
||||||
stack_trace: Optional[FrameType] = None,
|
stack_trace: Optional[FrameType] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
if not self.log_for(LogLevel.debug):
|
if not self.log_for(LogLevel.debug):
|
||||||
@ -242,7 +272,7 @@ class Logger(Options):
|
|||||||
tag: Optional[str] = None,
|
tag: Optional[str] = None,
|
||||||
end: str = "\n",
|
end: str = "\n",
|
||||||
split: str = " ",
|
split: str = " ",
|
||||||
flush: bool = True,
|
flush: bool = None,
|
||||||
stack_trace: Optional[FrameType] = None,
|
stack_trace: Optional[FrameType] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
if not self.log_for(LogLevel.warn):
|
if not self.log_for(LogLevel.warn):
|
||||||
@ -263,7 +293,7 @@ class Logger(Options):
|
|||||||
tag: Optional[str] = None,
|
tag: Optional[str] = None,
|
||||||
end: str = "\n",
|
end: str = "\n",
|
||||||
split: str = " ",
|
split: str = " ",
|
||||||
flush: bool = True,
|
flush: bool = None,
|
||||||
stack_trace: Optional[FrameType] = None,
|
stack_trace: Optional[FrameType] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
if not self.log_for(LogLevel.error):
|
if not self.log_for(LogLevel.error):
|
||||||
@ -284,7 +314,7 @@ class Logger(Options):
|
|||||||
tag: Optional[str] = None,
|
tag: Optional[str] = None,
|
||||||
end: str = "\n",
|
end: str = "\n",
|
||||||
split: str = " ",
|
split: str = " ",
|
||||||
flush: bool = True,
|
flush: bool = None,
|
||||||
stack_trace: Optional[FrameType] = None,
|
stack_trace: Optional[FrameType] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
if not self.log_for(LogLevel.fatal):
|
if not self.log_for(LogLevel.fatal):
|
||||||
|
@ -61,10 +61,11 @@ class StdioOutputStream(BaseOutputStream):
|
|||||||
return None
|
return None
|
||||||
if message.level < self.level:
|
if message.level < self.level:
|
||||||
return None
|
return None
|
||||||
|
out_msg = self.formatter.format_message(message)
|
||||||
if message.flush is not None:
|
if message.flush is not None:
|
||||||
print(self.formatter.format_message(message), end="", flush=message.flush)
|
print(out_msg, end="", flush=message.flush)
|
||||||
else:
|
else:
|
||||||
print(self.formatter.format_message(message), end="", flush=True)
|
print(out_msg, end="", flush=True)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def write_stderr(self, message: LogMessage) -> None:
|
def write_stderr(self, message: LogMessage) -> None:
|
||||||
@ -115,6 +116,7 @@ class FileCacheOutputStream(BaseOutputStream):
|
|||||||
flush_timer: Optional[threading.Timer] = None
|
flush_timer: Optional[threading.Timer] = None
|
||||||
|
|
||||||
file_path: Path = Path("./logs")
|
file_path: Path = Path("./logs")
|
||||||
|
# if contain {time} -> time.strftime("%Y-%m-%d_%H-%M-%S", time.gmtime(time.time)
|
||||||
file_name: str
|
file_name: str
|
||||||
# file mode: always 'a'
|
# file mode: always 'a'
|
||||||
file_encoding: str = "utf-8"
|
file_encoding: str = "utf-8"
|
||||||
@ -138,9 +140,17 @@ class FileCacheOutputStream(BaseOutputStream):
|
|||||||
file_swap_on_both: bool = False # swap file when both size and time limit reached
|
file_swap_on_both: bool = False # swap file when both size and time limit reached
|
||||||
|
|
||||||
def init(self, **kwargs) -> bool:
|
def init(self, **kwargs) -> bool:
|
||||||
|
# 时间取整
|
||||||
self.file_start_time = round(time.time())
|
self.file_start_time = round(time.time())
|
||||||
|
# 初始化文件名
|
||||||
|
if "{time}" in self.file_name:
|
||||||
|
self.file_name = self.file_name.format(time=time.strftime(
|
||||||
|
"%Y-%m-%d_%H-%M-%S", time.gmtime(self.file_start_time)
|
||||||
|
))
|
||||||
|
# 初始化缓存
|
||||||
if self.text_cache is None:
|
if self.text_cache is None:
|
||||||
self.text_cache = io.StringIO()
|
self.text_cache = io.StringIO()
|
||||||
|
# 检查文件名
|
||||||
self.get_file_path()
|
self.get_file_path()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -224,8 +234,7 @@ class FileCacheOutputStream(BaseOutputStream):
|
|||||||
if file_time > self.file_time_limit:
|
if file_time > self.file_time_limit:
|
||||||
time_pass = False
|
time_pass = False
|
||||||
if (self.file_swap_on_both and size_pass and time_pass) or (
|
if (self.file_swap_on_both and size_pass and time_pass) or (
|
||||||
not self.file_swap_on_both and (size_pass or time_pass)
|
not self.file_swap_on_both and (size_pass or time_pass)):
|
||||||
):
|
|
||||||
# 两个都满足
|
# 两个都满足
|
||||||
# 或者只有一个满足
|
# 或者只有一个满足
|
||||||
if size_pass and time_pass:
|
if size_pass and time_pass:
|
||||||
@ -243,6 +252,7 @@ class FileCacheOutputStream(BaseOutputStream):
|
|||||||
if text == "":
|
if text == "":
|
||||||
return None
|
return None
|
||||||
current_file = self.check_flush()
|
current_file = self.check_flush()
|
||||||
|
# 检查文件是否存在
|
||||||
if not current_file.exists():
|
if not current_file.exists():
|
||||||
current_file.parent.mkdir(parents=True, exist_ok=True)
|
current_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
current_file.touch(exist_ok=True)
|
current_file.touch(exist_ok=True)
|
||||||
|
@ -87,7 +87,7 @@ class LogMessage:
|
|||||||
def format_message(self) -> str:
|
def format_message(self) -> str:
|
||||||
if self.split is None:
|
if self.split is None:
|
||||||
self.split = " "
|
self.split = " "
|
||||||
return self.split.join(self.messages) + self.end
|
return self.split.join(str(item) for item in self.messages) + self.end
|
||||||
|
|
||||||
def format_for_message(self) -> Dict[str, str]:
|
def format_for_message(self) -> Dict[str, str]:
|
||||||
basic_info = self.option()
|
basic_info = self.option()
|
||||||
|
@ -13,4 +13,13 @@ nuitka_config_type = Dict[str, Union[str, bool, List[Union[str, tuple]]]]
|
|||||||
raw_config_type = Dict[str, Union[nuitka_config_type, str]]
|
raw_config_type = Dict[str, Union[nuitka_config_type, str]]
|
||||||
parse_config_function = Callable[[raw_config_type], nuitka_config_type]
|
parse_config_function = Callable[[raw_config_type], nuitka_config_type]
|
||||||
|
|
||||||
__all__ = ["reader", "compile", "raw_config_type", "parse_config_function", "nuitka_config_type"]
|
# fmt: off
|
||||||
|
__all__ = [
|
||||||
|
"reader",
|
||||||
|
"compile",
|
||||||
|
"raw_config_type",
|
||||||
|
"parse_config_function",
|
||||||
|
"nuitka_config_type"
|
||||||
|
]
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
|
@ -480,16 +480,17 @@ class CompilerHelper(Options):
|
|||||||
self.product_version,
|
self.product_version,
|
||||||
self.product_version,
|
self.product_version,
|
||||||
)
|
)
|
||||||
|
if self.icon_path is not None:
|
||||||
cmd_list += format_cmd(
|
cmd_list += format_cmd(
|
||||||
"--macos-app-icon=", self.icon_path.absolute(), self.icon_path
|
"--macos-app-icon=", self.icon_path.absolute(), self.icon_path
|
||||||
)
|
)
|
||||||
elif platform.system() == "Windows":
|
elif platform.system() == "Windows" and self.icon_path is not None:
|
||||||
cmd_list += format_cmd(
|
cmd_list += format_cmd(
|
||||||
"--windows-icon-from-ico=",
|
"--windows-icon-from-ico=",
|
||||||
self.icon_path.absolute(),
|
self.icon_path.absolute(),
|
||||||
self.icon_path,
|
self.icon_path,
|
||||||
)
|
)
|
||||||
elif platform.system() == "Linux":
|
elif platform.system() == "Linux" and self.icon_path is not None:
|
||||||
cmd_list += format_cmd(
|
cmd_list += format_cmd(
|
||||||
"--linux-icon=", self.icon_path.absolute(), self.icon_path
|
"--linux-icon=", self.icon_path.absolute(), self.icon_path
|
||||||
)
|
)
|
||||||
|
@ -9,7 +9,6 @@ import platform
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, Union, List
|
|
||||||
|
|
||||||
from lib_not_dr.nuitka import nuitka_config_type
|
from lib_not_dr.nuitka import nuitka_config_type
|
||||||
|
|
||||||
@ -63,10 +62,10 @@ TOML_READERS = (
|
|||||||
# try tomllib first
|
# try tomllib first
|
||||||
"toml", # slow pure python toml reader
|
"toml", # slow pure python toml reader
|
||||||
"rtoml", # rust based toml reader
|
"rtoml", # rust based toml reader
|
||||||
"tomlkit", # pure python toml reader
|
|
||||||
"tomli", # pure python toml reader
|
"tomli", # pure python toml reader
|
||||||
"pytomlpp", # cpp based toml reader
|
"pytomlpp", # cpp based toml reader
|
||||||
"qtoml", # pure python toml reader, but faster than toml
|
"qtoml", # pure python toml reader, but faster than toml
|
||||||
|
"tomlkit", # pure python toml reader with style
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -82,9 +81,11 @@ def get_toml_reader():
|
|||||||
return loaded
|
return loaded
|
||||||
except ImportError:
|
except ImportError:
|
||||||
continue
|
continue
|
||||||
error_msg = """No toml reader found, please install any below by pip:
|
error_msg = """\033[31mNo toml reader found, please install any below by pip:
|
||||||
%s
|
{toml} or use Python 3.11+ or install lib-not-dr with lib_not_dr[nuitka] option
|
||||||
or use Python 3.11+""" % " ".join(TOML_READERS)
|
没有找到 toml 解析器, 请考虑安装下列之一的 toml 库
|
||||||
|
{toml} 或者使用 Python 3.11+ 或者使用 lib_not_dr[nuitka] 安装 lib-not-dr\033[0m
|
||||||
|
""".format(toml="\n ".join(TOML_READERS))
|
||||||
raise ImportError(error_msg) from None
|
raise ImportError(error_msg) from None
|
||||||
|
|
||||||
|
|
||||||
@ -141,7 +142,6 @@ def cli_main() -> None:
|
|||||||
toml = toml_loads(f.read())
|
toml = toml_loads(f.read())
|
||||||
|
|
||||||
nuitka_config = pyproject_toml(toml)
|
nuitka_config = pyproject_toml(toml)
|
||||||
print(f"config is: {nuitka_config}")
|
|
||||||
cli_config = parse_raw_config_by_script(nuitka_config)
|
cli_config = parse_raw_config_by_script(nuitka_config)
|
||||||
|
|
||||||
subprocess_command = gen_subprocess_args(cli_config)
|
subprocess_command = gen_subprocess_args(cli_config)
|
||||||
|
@ -39,7 +39,8 @@ def pyproject_toml(toml_data: dict) -> raw_config_type:
|
|||||||
|
|
||||||
if "main" not in nuitka_config["cli"]:
|
if "main" not in nuitka_config["cli"]:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"'main' not define in lib-not-dr(lndl).nuitka.cli section\ndefine it with 'main = [<main.py>]'"
|
"'main' not define in lib-not-dr(lndl).nuitka.cli section\n"
|
||||||
|
"define it with 'main = [<main.py>]'"
|
||||||
)
|
)
|
||||||
|
|
||||||
return nuitka_config
|
return nuitka_config
|
||||||
@ -78,7 +79,13 @@ def get_cli_nuitka_args() -> dict:
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
# start from --
|
# start from --
|
||||||
|
try:
|
||||||
index = sys.argv.index("--")
|
index = sys.argv.index("--")
|
||||||
|
except ValueError:
|
||||||
|
# 按理来说不应该出现这种情况
|
||||||
|
# 毕竟是先找到 -- 再进来的
|
||||||
|
warn("-- not found in sys.argv, but entered the get_cli_nuitka_args()")
|
||||||
|
return {}
|
||||||
new_args = sys.argv[index + 1 :]
|
new_args = sys.argv[index + 1 :]
|
||||||
arg_dict = {}
|
arg_dict = {}
|
||||||
for arg in new_args:
|
for arg in new_args:
|
||||||
@ -86,15 +93,17 @@ def get_cli_nuitka_args() -> dict:
|
|||||||
warn(f"invalid arg: {arg}")
|
warn(f"invalid arg: {arg}")
|
||||||
else:
|
else:
|
||||||
arg = arg[2:] # remove --
|
arg = arg[2:] # remove --
|
||||||
# arg_name: --<name>=<value>
|
|
||||||
arg_name = arg.split("=")[0]
|
|
||||||
if "=" in arg:
|
if "=" in arg:
|
||||||
arg_value = arg.split("=")[1]
|
# arg: --<name>=<value>
|
||||||
|
spilter = arg.find("=")
|
||||||
|
arg_name = arg[:spilter]
|
||||||
|
arg_value = arg[spilter + 1 :]
|
||||||
else:
|
else:
|
||||||
|
# arg: --<name>
|
||||||
|
arg_name = arg
|
||||||
arg_value = True
|
arg_value = True
|
||||||
arg_dict[arg_name] = arg_value
|
arg_dict[arg_name] = arg_value
|
||||||
|
|
||||||
print(f"cli config: {arg_dict}")
|
|
||||||
return arg_dict
|
return arg_dict
|
||||||
|
|
||||||
|
|
||||||
@ -108,7 +117,8 @@ def merge_cli_config(toml_config: dict, cli_config: dict) -> dict:
|
|||||||
for name, value in cli_config.items():
|
for name, value in cli_config.items():
|
||||||
if name in toml_config:
|
if name in toml_config:
|
||||||
warn(
|
warn(
|
||||||
f"\033[33mcli config will overwrite toml config\n{name}:{toml_config[name]} -> {value}\033[0m"
|
"\033[33mcli config will overwrite toml config\n"
|
||||||
|
f"{name}:{toml_config[name]} -> {value}\033[0m"
|
||||||
)
|
)
|
||||||
if isinstance(toml_config[name], bool):
|
if isinstance(toml_config[name], bool):
|
||||||
if not isinstance(value, bool):
|
if not isinstance(value, bool):
|
||||||
@ -144,17 +154,18 @@ def gen_subprocess_args(
|
|||||||
nuitka_config = merge_cli_config(nuitka_config, get_cli_nuitka_args())
|
nuitka_config = merge_cli_config(nuitka_config, get_cli_nuitka_args())
|
||||||
|
|
||||||
def parse_value(arg_name, arg_value) -> list:
|
def parse_value(arg_name, arg_value) -> list:
|
||||||
if isinstance(value, bool):
|
if isinstance(arg_value, bool):
|
||||||
warn(f"bool value is not supported in list config {arg_name}")
|
warn(f"bool value is not supported in list config {arg_name}")
|
||||||
return []
|
return []
|
||||||
elif isinstance(value, str):
|
elif isinstance(arg_value, str):
|
||||||
return [f"--{arg_name}={arg_value}"]
|
return [f"--{arg_name}={arg_value}"]
|
||||||
else:
|
else:
|
||||||
return [f"--{arg_name}={arg_value[0]}={arg_value[1]}"]
|
return [f"--{arg_name}={arg_value[0]}={arg_value[1]}"]
|
||||||
|
|
||||||
for name, value in nuitka_config.items():
|
for name, value in nuitka_config.items():
|
||||||
if value is True:
|
if isinstance(value, bool):
|
||||||
# --<name>
|
# --<name>
|
||||||
|
if value:
|
||||||
cmd_list.append(f"--{name}")
|
cmd_list.append(f"--{name}")
|
||||||
continue
|
continue
|
||||||
elif isinstance(value, str):
|
elif isinstance(value, str):
|
||||||
@ -182,6 +193,8 @@ def gen_subprocess_args(
|
|||||||
for item in value:
|
for item in value:
|
||||||
cmd_list += parse_value(name, item)
|
cmd_list += parse_value(name, item)
|
||||||
continue
|
continue
|
||||||
|
else:
|
||||||
|
warn(f"invalid config {name}:{value} {type(value)}")
|
||||||
|
|
||||||
return cmd_list
|
return cmd_list
|
||||||
|
|
||||||
@ -194,8 +207,11 @@ def parse_raw_config_by_script(raw_config: raw_config_type) -> nuitka_config_typ
|
|||||||
:param raw_config:
|
:param raw_config:
|
||||||
:return: parsed config
|
:return: parsed config
|
||||||
"""
|
"""
|
||||||
if (script_name := raw_config.get("script")) is None:
|
raw_cli_config: nuitka_config_type = raw_config.get("cli", {}) # type: ignore
|
||||||
return raw_config["cli"]
|
|
||||||
|
if (script_name := raw_config.get("script")) is None: # type: ignore
|
||||||
|
return raw_cli_config
|
||||||
|
script_name: str
|
||||||
print(f'reading script {script_name}')
|
print(f'reading script {script_name}')
|
||||||
script_path = Path(script_name)
|
script_path = Path(script_name)
|
||||||
|
|
||||||
@ -206,25 +222,22 @@ def parse_raw_config_by_script(raw_config: raw_config_type) -> nuitka_config_typ
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"script {script_path} import failed ignore it\n{e}")
|
print(f"script {script_path} import failed ignore it\n{e}")
|
||||||
sys.path.remove(str(script_path.parent))
|
sys.path.remove(str(script_path.parent))
|
||||||
return raw_config["cli"]
|
return raw_cli_config
|
||||||
|
|
||||||
sys.path.remove(str(script_path.parent))
|
sys.path.remove(str(script_path.parent))
|
||||||
|
|
||||||
if not hasattr(script_module, "main"):
|
if not hasattr(script_module, "main"):
|
||||||
print(f"script {script_path} has no paser function ignore it")
|
print(f"script {script_path} has no paser function ignore it")
|
||||||
return raw_config["cli"]
|
return raw_cli_config
|
||||||
|
|
||||||
|
parse_func: parse_config_function = getattr(script_module, "main")
|
||||||
try:
|
try:
|
||||||
parsed_config = script_module.main(raw_config)
|
parsed_config = parse_func(raw_config)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"script {script_path} parse failed ignore it")
|
print(f"script {script_path} parse failed ignore it")
|
||||||
print(e)
|
print(e)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return raw_config["cli"]
|
return raw_cli_config
|
||||||
|
|
||||||
# if not isinstance(parsed_config, dict):
|
|
||||||
# print(f"script {script_path} parse failed ignore it")
|
|
||||||
# return raw_config["cli"]
|
|
||||||
|
|
||||||
return parsed_config
|
return parsed_config
|
||||||
|
|
||||||
|
@ -21,13 +21,16 @@ from typing import (
|
|||||||
Iterable,
|
Iterable,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# fmt: off
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"get_type_hints_",
|
"get_type_hints_",
|
||||||
"Options",
|
"Options",
|
||||||
"OptionsError",
|
"OptionsError",
|
||||||
"OptionNotFound",
|
"OptionNotFound",
|
||||||
|
"OptionNotFilled",
|
||||||
"OptionNameNotDefined",
|
"OptionNameNotDefined",
|
||||||
]
|
]
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
|
|
||||||
def get_type_hints_(cls: Type):
|
def get_type_hints_(cls: Type):
|
||||||
@ -61,6 +64,10 @@ class OptionNotFound(OptionsError):
|
|||||||
"""某个选项没有找到"""
|
"""某个选项没有找到"""
|
||||||
|
|
||||||
|
|
||||||
|
class OptionNotFilled(OptionsError):
|
||||||
|
"""某个选项没有填写"""
|
||||||
|
|
||||||
|
|
||||||
class Options:
|
class Options:
|
||||||
"""
|
"""
|
||||||
一个用于存储选项 / 提供 API 定义 的类
|
一个用于存储选项 / 提供 API 定义 的类
|
||||||
@ -73,11 +80,17 @@ class Options:
|
|||||||
定义 一些需要的方法
|
定义 一些需要的方法
|
||||||
子类: 继承 新的 Options 类
|
子类: 继承 新的 Options 类
|
||||||
实现定义的方法
|
实现定义的方法
|
||||||
|
_check_options: 用于检查是否输入的选项都是已经定义的选项
|
||||||
|
_check_filled: 用于检查是否所有的选项都已经填写
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = "Option Base"
|
name = "Option Base"
|
||||||
cached_options: Dict[str, Union[str, Any]] = {}
|
cached_options: Dict[str, Union[str, Any]] = {}
|
||||||
|
|
||||||
|
# 用于检查是否输入的选项都是已经定义的选项
|
||||||
_check_options: bool = True
|
_check_options: bool = True
|
||||||
|
# 用于检查是否所有的选项都已经填写
|
||||||
|
_check_filled: bool = False
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
@ -88,12 +101,17 @@ class Options:
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
self._options: Dict[str, Union[Callable, object]] = {}
|
self._options: Dict[str, Union[Callable, object]] = {}
|
||||||
self.flush_option()
|
self.flush_option()
|
||||||
|
miss_list = []
|
||||||
|
if self._check_filled:
|
||||||
|
miss_list = [key for key in self.cached_options]
|
||||||
for option, value in kwargs.items():
|
for option, value in kwargs.items():
|
||||||
if option not in self.cached_options and self._check_options:
|
if option not in self.cached_options and self._check_options:
|
||||||
raise OptionNameNotDefined(
|
raise OptionNameNotDefined(
|
||||||
f"option: {option} with value: {value} is not defined"
|
f"option: {option} with value: {value} is not defined"
|
||||||
)
|
)
|
||||||
setattr(self, option, value)
|
setattr(self, option, value)
|
||||||
|
if self._check_filled:
|
||||||
|
miss_list.remove(option)
|
||||||
run_load_file = True
|
run_load_file = True
|
||||||
if hasattr(self, "init"):
|
if hasattr(self, "init"):
|
||||||
run_load_file = self.init(**kwargs) # 默认 False/None
|
run_load_file = self.init(**kwargs) # 默认 False/None
|
||||||
@ -103,6 +121,10 @@ class Options:
|
|||||||
self.load_file()
|
self.load_file()
|
||||||
except Exception:
|
except Exception:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
if self._check_filled and miss_list:
|
||||||
|
raise OptionNotFilled(
|
||||||
|
f"option: {miss_list} is not filled in {self.name}"
|
||||||
|
)
|
||||||
self.flush_option()
|
self.flush_option()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@ -118,12 +140,12 @@ class Options:
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
_options: Dict[str, Union[Callable, object]] = {}
|
_options: Dict[str, Union[Callable, object]] = {}
|
||||||
|
|
||||||
def init(self, **kwargs) -> bool:
|
def init(self, **kwargs) -> bool: # type: ignore
|
||||||
"""如果子类定义了这个函数,则会在 __init__ 之后调用这个函数
|
"""如果子类定义了这个函数,则会在 __init__ 之后调用这个函数
|
||||||
返回值为 True 则不会调用 load_file 函数
|
返回值为 True 则不会调用 load_file 函数
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def load_file(self) -> bool:
|
def load_file(self) -> bool: # type: ignore
|
||||||
"""如果子类定义了这个函数,则会在 __init__ 和 init 之后再调用这个函数
|
"""如果子类定义了这个函数,则会在 __init__ 和 init 之后再调用这个函数
|
||||||
|
|
||||||
请注意,这个函数请尽量使用 try 包裹住可能出现错误的部分
|
请注意,这个函数请尽量使用 try 包裹住可能出现错误的部分
|
||||||
@ -137,8 +159,9 @@ class Options:
|
|||||||
"""
|
"""
|
||||||
values = {}
|
values = {}
|
||||||
for ann in self.__annotations__: # 获取类型注释
|
for ann in self.__annotations__: # 获取类型注释
|
||||||
values[ann] = getattr(self, ann, None)
|
try:
|
||||||
if values[ann] is None:
|
values[ann] = getattr(self, ann)
|
||||||
|
except AttributeError:
|
||||||
values[ann] = self.__annotations__[ann]
|
values[ann] = self.__annotations__[ann]
|
||||||
|
|
||||||
if not hasattr(self, "_options"):
|
if not hasattr(self, "_options"):
|
||||||
@ -216,7 +239,7 @@ class Options:
|
|||||||
max_len_value = max(max_len_value, len(str(value)))
|
max_len_value = max(max_len_value, len(str(value)))
|
||||||
max_len_value_t = max(max_len_value_t, len(str(value_t)))
|
max_len_value_t = max(max_len_value_t, len(str(value_t)))
|
||||||
option_list.append([key, value, value_t])
|
option_list.append([key, value, value_t])
|
||||||
return [option_list, max_len_key, max_len_value, max_len_value_t] # noqa
|
return [option_list, max_len_key, max_len_value, max_len_value_t] # type: ignore
|
||||||
|
|
||||||
def as_markdown(self, longest: Optional[int] = None) -> str:
|
def as_markdown(self, longest: Optional[int] = None) -> str:
|
||||||
"""
|
"""
|
||||||
@ -274,14 +297,15 @@ class Options:
|
|||||||
# 所以可以直接修改 string
|
# 所以可以直接修改 string
|
||||||
for v in value[0]:
|
for v in value[0]:
|
||||||
if len(str(v[0])) > option_len:
|
if len(str(v[0])) > option_len:
|
||||||
v[0] = f"{str(v[0])[:value_len - 3]}..."
|
v[0] = f"{str(v[0])[:value_len - 3]}..." # type: ignore
|
||||||
if len(str(v[1])) > value_len:
|
if len(str(v[1])) > value_len:
|
||||||
v[1] = f"{str(v[1])[:value_len - 3]}..."
|
v[1] = f"{str(v[1])[:value_len - 3]}..." # type: ignore
|
||||||
if len(str(v[2])) > value_type_len:
|
if len(str(v[2])) > value_type_len:
|
||||||
v[2] = f"{str(v[2])[:value_len - 3]}.."
|
v[2] = f"{str(v[2])[:value_len - 3]}.." # type: ignore
|
||||||
|
|
||||||
cache.write(
|
cache.write(
|
||||||
f"| Option{' ' * (option_len - 3)}| Value{' ' * (value_len - 2)}| Value Type{' ' * (value_type_len - 7)}|\n"
|
f"| Option{' ' * (option_len - 3)}| Value{' ' * (value_len - 2)}"
|
||||||
|
f"| Value Type{' ' * (value_type_len - 7)}|\n"
|
||||||
)
|
)
|
||||||
cache.write(
|
cache.write(
|
||||||
f'|:{"-" * (option_len + 3)}|:{"-" * (value_len + 3)}|:{"-" * (value_type_len + 3)}|\n'
|
f'|:{"-" * (option_len + 3)}|:{"-" * (value_len + 3)}|:{"-" * (value_type_len + 3)}|\n'
|
||||||
|
@ -8,3 +8,10 @@
|
|||||||
TMD 啊啊啊啊啊啊啊啊啊
|
TMD 啊啊啊啊啊啊啊啊啊
|
||||||
这里不是用来直接调用的啊啊啊啊啊
|
这里不是用来直接调用的啊啊啊啊啊
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from lib_not_dr.nuitka.reader import cli_main
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
cli_main()
|
||||||
|
|
||||||
|
main = cli_main
|
||||||
|
Loading…
Reference in New Issue
Block a user