xiaoyi-utils 0.1.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- xiaoyi_utils-0.1.3/PKG-INFO +68 -0
- xiaoyi_utils-0.1.3/README.md +59 -0
- xiaoyi_utils-0.1.3/pyproject.toml +22 -0
- xiaoyi_utils-0.1.3/src/xiaoyi_utils/__init__.py +5 -0
- xiaoyi_utils-0.1.3/src/xiaoyi_utils/date/__init__.py +23 -0
- xiaoyi_utils-0.1.3/src/xiaoyi_utils/date/core.py +83 -0
- xiaoyi_utils-0.1.3/src/xiaoyi_utils/hash/__init__.py +5 -0
- xiaoyi_utils-0.1.3/src/xiaoyi_utils/hash/core.py +33 -0
- xiaoyi_utils-0.1.3/src/xiaoyi_utils/json_util/__init__.py +5 -0
- xiaoyi_utils-0.1.3/src/xiaoyi_utils/json_util/core.py +45 -0
- xiaoyi_utils-0.1.3/src/xiaoyi_utils/markdown/__init__.py +5 -0
- xiaoyi_utils-0.1.3/src/xiaoyi_utils/markdown/core.py +34 -0
- xiaoyi_utils-0.1.3/src/xiaoyi_utils/text/__init__.py +19 -0
- xiaoyi_utils-0.1.3/src/xiaoyi_utils/text/core.py +53 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: xiaoyi-utils
|
|
3
|
+
Version: 0.1.3
|
|
4
|
+
Summary: 一个用于学习 Python 公共库构建和发布的示例工程
|
|
5
|
+
Requires-Python: >=3.14
|
|
6
|
+
Requires-Dist: markdown>=3.5
|
|
7
|
+
Requires-Dist: python-dateutil>=2.8
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# xiaoyi-utils
|
|
11
|
+
|
|
12
|
+
一个用于学习 Python 公共库构建和发布的示例工程,提供常用工具函数。
|
|
13
|
+
|
|
14
|
+
## 安装
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install -e .
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## 模块说明
|
|
21
|
+
|
|
22
|
+
### date — 日期时间
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from xiaoyi_utils.date import parse_date, format_datetime, add_days, diff_days
|
|
26
|
+
|
|
27
|
+
dt = parse_date("2026-09-03")
|
|
28
|
+
print(format_datetime(dt, "%Y年%m月%d日"))
|
|
29
|
+
print(add_days(dt, 7))
|
|
30
|
+
print(diff_days("2026-01-01", "2026-09-03"))
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### markdown — Markdown 处理
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from xiaoyi_utils.markdown import md_to_html, md_to_plain
|
|
37
|
+
|
|
38
|
+
html = md_to_html("# Hello **world**")
|
|
39
|
+
plain = md_to_plain("# Hello **world**")
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### text — 文本处理
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from xiaoyi_utils.text import truncate, slugify, camel_to_snake
|
|
46
|
+
|
|
47
|
+
print(truncate("这是一段很长的文本", 6))
|
|
48
|
+
print(slugify("Hello World 你好"))
|
|
49
|
+
print(camel_to_snake("UserProfile"))
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### hash — 哈希摘要
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from xiaoyi_utils.hash import md5, sha256, file_hash
|
|
56
|
+
|
|
57
|
+
print(sha256("hello"))
|
|
58
|
+
print(file_hash("README.md"))
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### json_util — JSON 工具
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from xiaoyi_utils.json_util import safe_loads, pretty_dumps, read_json, write_json
|
|
65
|
+
|
|
66
|
+
data = safe_loads('{"name": "xiaoyi"}', default={})
|
|
67
|
+
print(pretty_dumps(data))
|
|
68
|
+
```
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# xiaoyi-utils
|
|
2
|
+
|
|
3
|
+
一个用于学习 Python 公共库构建和发布的示例工程,提供常用工具函数。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install -e .
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 模块说明
|
|
12
|
+
|
|
13
|
+
### date — 日期时间
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from xiaoyi_utils.date import parse_date, format_datetime, add_days, diff_days
|
|
17
|
+
|
|
18
|
+
dt = parse_date("2026-09-03")
|
|
19
|
+
print(format_datetime(dt, "%Y年%m月%d日"))
|
|
20
|
+
print(add_days(dt, 7))
|
|
21
|
+
print(diff_days("2026-01-01", "2026-09-03"))
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### markdown — Markdown 处理
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from xiaoyi_utils.markdown import md_to_html, md_to_plain
|
|
28
|
+
|
|
29
|
+
html = md_to_html("# Hello **world**")
|
|
30
|
+
plain = md_to_plain("# Hello **world**")
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### text — 文本处理
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from xiaoyi_utils.text import truncate, slugify, camel_to_snake
|
|
37
|
+
|
|
38
|
+
print(truncate("这是一段很长的文本", 6))
|
|
39
|
+
print(slugify("Hello World 你好"))
|
|
40
|
+
print(camel_to_snake("UserProfile"))
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### hash — 哈希摘要
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from xiaoyi_utils.hash import md5, sha256, file_hash
|
|
47
|
+
|
|
48
|
+
print(sha256("hello"))
|
|
49
|
+
print(file_hash("README.md"))
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### json_util — JSON 工具
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from xiaoyi_utils.json_util import safe_loads, pretty_dumps, read_json, write_json
|
|
56
|
+
|
|
57
|
+
data = safe_loads('{"name": "xiaoyi"}', default={})
|
|
58
|
+
print(pretty_dumps(data))
|
|
59
|
+
```
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# pyproject.toml
|
|
2
|
+
|
|
3
|
+
[build-system] # 构建系统配置,指定构建工具和依赖
|
|
4
|
+
requires = ["hatchling"] # 依赖的后端构建工具
|
|
5
|
+
build-backend = "hatchling.build" # 使用哪个工具的哪个模块来构建项目
|
|
6
|
+
|
|
7
|
+
[project] # 工程描述
|
|
8
|
+
name = "xiaoyi-utils" # 发行版名称
|
|
9
|
+
version = "0.1.3" # 版本号,遵循语义化版本控制
|
|
10
|
+
description = "一个用于学习 Python 公共库构建和发布的示例工程" # 项目简介
|
|
11
|
+
requires-python = ">=3.14" # 指定支持的 Python 版本
|
|
12
|
+
readme = "README.md" # 指定项目的 README 文件路径
|
|
13
|
+
dependencies = [
|
|
14
|
+
"python-dateutil>=2.8", # 依赖的第三方库,指定版本要求
|
|
15
|
+
"Markdown>=3.5", # 另一个依赖
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[tool.hatch.build.targets.sdist] # 配置源代码分发包
|
|
19
|
+
# 配置留空,表示它会默认包含所有项目文件
|
|
20
|
+
|
|
21
|
+
[tool.hatch.build.targets.wheel] # 配置 wheel 包
|
|
22
|
+
packages = ["src/xiaoyi_utils"] # 配置 wheel 包的构建目标,指定包含的包路径
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""日期与时间工具模块。"""
|
|
2
|
+
|
|
3
|
+
from xiaoyi_utils.date.core import (
|
|
4
|
+
add_days,
|
|
5
|
+
add_months,
|
|
6
|
+
diff_days,
|
|
7
|
+
format_datetime,
|
|
8
|
+
is_weekend,
|
|
9
|
+
now,
|
|
10
|
+
parse_date,
|
|
11
|
+
today,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"add_days",
|
|
16
|
+
"add_months",
|
|
17
|
+
"diff_days",
|
|
18
|
+
"format_datetime",
|
|
19
|
+
"is_weekend",
|
|
20
|
+
"now",
|
|
21
|
+
"parse_date",
|
|
22
|
+
"today",
|
|
23
|
+
]
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""日期与时间相关工具。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import date, datetime, timedelta, timezone
|
|
6
|
+
from typing import overload
|
|
7
|
+
|
|
8
|
+
from dateutil import parser as date_parser
|
|
9
|
+
from dateutil.relativedelta import relativedelta
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def now(tz: timezone | None = None) -> datetime:
|
|
13
|
+
"""返回当前时间,默认本地时区。"""
|
|
14
|
+
return datetime.now(tz)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def today() -> date:
|
|
18
|
+
"""返回今天的日期。"""
|
|
19
|
+
return date.today()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def parse_date(value: str | datetime | date, *, dayfirst: bool = False) -> datetime:
|
|
23
|
+
"""解析多种格式的日期字符串,返回 datetime。"""
|
|
24
|
+
if isinstance(value, datetime):
|
|
25
|
+
return value
|
|
26
|
+
if isinstance(value, date):
|
|
27
|
+
return datetime(value.year, value.month, value.day)
|
|
28
|
+
return date_parser.parse(value, dayfirst=dayfirst)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def format_datetime(
|
|
32
|
+
value: datetime | date | str,
|
|
33
|
+
fmt: str = "%Y-%m-%d %H:%M:%S",
|
|
34
|
+
*,
|
|
35
|
+
dayfirst: bool = False,
|
|
36
|
+
) -> str:
|
|
37
|
+
"""格式化日期/时间为字符串。"""
|
|
38
|
+
dt = parse_date(value, dayfirst=dayfirst) if isinstance(value, str) else value
|
|
39
|
+
if isinstance(dt, date) and not isinstance(dt, datetime):
|
|
40
|
+
dt = datetime(dt.year, dt.month, dt.day)
|
|
41
|
+
return dt.strftime(fmt)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def add_days(value: datetime | date | str, days: int, *, dayfirst: bool = False) -> datetime:
|
|
45
|
+
"""在指定日期上增加天数。"""
|
|
46
|
+
dt = parse_date(value, dayfirst=dayfirst) if isinstance(value, str) else value
|
|
47
|
+
if isinstance(dt, date) and not isinstance(dt, datetime):
|
|
48
|
+
dt = datetime(dt.year, dt.month, dt.day)
|
|
49
|
+
return dt + timedelta(days=days)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def add_months(value: datetime | date | str, months: int, *, dayfirst: bool = False) -> datetime:
|
|
53
|
+
"""在指定日期上增加月数。"""
|
|
54
|
+
dt = parse_date(value, dayfirst=dayfirst) if isinstance(value, str) else value
|
|
55
|
+
if isinstance(dt, date) and not isinstance(dt, datetime):
|
|
56
|
+
dt = datetime(dt.year, dt.month, dt.day)
|
|
57
|
+
return dt + relativedelta(months=months)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@overload
|
|
61
|
+
def diff_days(start: datetime | date | str, end: datetime | date | str, *, dayfirst: bool = False) -> int: ...
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def diff_days(
|
|
65
|
+
start: datetime | date | str,
|
|
66
|
+
end: datetime | date | str,
|
|
67
|
+
*,
|
|
68
|
+
dayfirst: bool = False,
|
|
69
|
+
) -> int:
|
|
70
|
+
"""计算两个日期之间的天数差(end - start)。"""
|
|
71
|
+
start_dt = parse_date(start, dayfirst=dayfirst) if isinstance(start, str) else start
|
|
72
|
+
end_dt = parse_date(end, dayfirst=dayfirst) if isinstance(end, str) else end
|
|
73
|
+
|
|
74
|
+
start_date = start_dt.date() if isinstance(start_dt, datetime) else start_dt
|
|
75
|
+
end_date = end_dt.date() if isinstance(end_dt, datetime) else end_dt
|
|
76
|
+
return (end_date - start_date).days
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def is_weekend(value: datetime | date | str, *, dayfirst: bool = False) -> bool:
|
|
80
|
+
"""判断是否为周末(周六或周日)。"""
|
|
81
|
+
dt = parse_date(value, dayfirst=dayfirst) if isinstance(value, str) else value
|
|
82
|
+
target = dt.date() if isinstance(dt, datetime) else dt
|
|
83
|
+
return target.weekday() >= 5
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""哈希与摘要工具。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _digest(data: bytes, algorithm: str) -> str:
|
|
10
|
+
hasher = hashlib.new(algorithm)
|
|
11
|
+
hasher.update(data)
|
|
12
|
+
return hasher.hexdigest()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def md5(text: str | bytes) -> str:
|
|
16
|
+
"""计算 MD5 十六进制摘要。"""
|
|
17
|
+
data = text.encode("utf-8") if isinstance(text, str) else text
|
|
18
|
+
return _digest(data, "md5")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def sha256(text: str | bytes) -> str:
|
|
22
|
+
"""计算 SHA256 十六进制摘要。"""
|
|
23
|
+
data = text.encode("utf-8") if isinstance(text, str) else text
|
|
24
|
+
return _digest(data, "sha256")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def file_hash(path: str | Path, *, algorithm: str = "sha256", chunk_size: int = 8192) -> str:
|
|
28
|
+
"""计算文件的哈希值。"""
|
|
29
|
+
hasher = hashlib.new(algorithm)
|
|
30
|
+
with Path(path).open("rb") as fp:
|
|
31
|
+
while chunk := fp.read(chunk_size):
|
|
32
|
+
hasher.update(chunk)
|
|
33
|
+
return hasher.hexdigest()
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""JSON 读写与格式化工具。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, TypeVar
|
|
8
|
+
|
|
9
|
+
T = TypeVar("T")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def safe_loads(text: str, default: T | None = None) -> Any | T:
|
|
13
|
+
"""安全解析 JSON 字符串,失败时返回 default。"""
|
|
14
|
+
try:
|
|
15
|
+
return json.loads(text)
|
|
16
|
+
except (json.JSONDecodeError, TypeError):
|
|
17
|
+
return default
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def pretty_dumps(data: Any, *, indent: int = 2, ensure_ascii: bool = False) -> str:
|
|
21
|
+
"""格式化输出 JSON 字符串。"""
|
|
22
|
+
return json.dumps(data, indent=indent, ensure_ascii=ensure_ascii, default=str)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def read_json(path: str | Path, *, default: T | None = None) -> Any | T:
|
|
26
|
+
"""读取 JSON 文件,失败时返回 default。"""
|
|
27
|
+
try:
|
|
28
|
+
with Path(path).open("r", encoding="utf-8") as fp:
|
|
29
|
+
return json.load(fp)
|
|
30
|
+
except (OSError, json.JSONDecodeError):
|
|
31
|
+
return default
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def write_json(
|
|
35
|
+
path: str | Path,
|
|
36
|
+
data: Any,
|
|
37
|
+
*,
|
|
38
|
+
indent: int = 2,
|
|
39
|
+
ensure_ascii: bool = False,
|
|
40
|
+
) -> None:
|
|
41
|
+
"""写入 JSON 文件。"""
|
|
42
|
+
target = Path(path)
|
|
43
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
44
|
+
with target.open("w", encoding="utf-8") as fp:
|
|
45
|
+
json.dump(data, fp, indent=indent, ensure_ascii=ensure_ascii, default=str)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Markdown 相关工具。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
import markdown
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def md_to_html(text: str, *, extensions: list[str] | None = None) -> str:
|
|
11
|
+
"""将 Markdown 文本转换为 HTML。"""
|
|
12
|
+
ext = extensions or ["extra", "codehilite"]
|
|
13
|
+
return markdown.markdown(text, extensions=ext)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def md_to_plain(text: str) -> str:
|
|
17
|
+
"""去除 Markdown 标记,提取纯文本。"""
|
|
18
|
+
html = md_to_html(text, extensions=[])
|
|
19
|
+
plain = re.sub(r"<[^>]+>", "", html)
|
|
20
|
+
plain = re.sub(r"\s+", " ", plain)
|
|
21
|
+
return plain.strip()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def is_markdown(text: str) -> bool:
|
|
25
|
+
"""简单判断文本是否包含常见 Markdown 语法。"""
|
|
26
|
+
patterns = (
|
|
27
|
+
r"^#{1,6}\s", # 标题
|
|
28
|
+
r"\*\*.+\*\*", # 粗体
|
|
29
|
+
r"\[.+\]\(.+\)", # 链接
|
|
30
|
+
r"^\s*[-*+]\s", # 无序列表
|
|
31
|
+
r"^\s*\d+\.\s", # 有序列表
|
|
32
|
+
r"```", # 代码块
|
|
33
|
+
)
|
|
34
|
+
return any(re.search(pattern, text, re.MULTILINE) for pattern in patterns)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""文本处理工具模块。"""
|
|
2
|
+
|
|
3
|
+
from xiaoyi_utils.text.core import (
|
|
4
|
+
camel_to_snake,
|
|
5
|
+
is_blank,
|
|
6
|
+
remove_whitespace,
|
|
7
|
+
slugify,
|
|
8
|
+
snake_to_camel,
|
|
9
|
+
truncate,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"camel_to_snake",
|
|
14
|
+
"is_blank",
|
|
15
|
+
"remove_whitespace",
|
|
16
|
+
"slugify",
|
|
17
|
+
"snake_to_camel",
|
|
18
|
+
"truncate",
|
|
19
|
+
]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""文本处理工具。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
import unicodedata
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def truncate(text: str, length: int, *, suffix: str = "...") -> str:
|
|
10
|
+
"""截断文本,超出部分用后缀代替。"""
|
|
11
|
+
if length <= 0:
|
|
12
|
+
return ""
|
|
13
|
+
if len(text) <= length:
|
|
14
|
+
return text
|
|
15
|
+
if len(suffix) >= length:
|
|
16
|
+
return suffix[:length]
|
|
17
|
+
return text[: length - len(suffix)] + suffix
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def slugify(text: str, *, separator: str = "-") -> str:
|
|
21
|
+
"""将文本转为 URL 友好的 slug。"""
|
|
22
|
+
normalized = unicodedata.normalize("NFKD", text)
|
|
23
|
+
ascii_text = normalized.encode("ascii", "ignore").decode("ascii")
|
|
24
|
+
slug = re.sub(r"[^\w\s-]", "", ascii_text.lower())
|
|
25
|
+
slug = re.sub(r"[\s_-]+", separator, slug).strip(separator)
|
|
26
|
+
return slug
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def camel_to_snake(name: str) -> str:
|
|
30
|
+
"""驼峰命名转蛇形命名。"""
|
|
31
|
+
s1 = re.sub(r"(.)([A-Z][a-z]+)", r"\1_\2", name)
|
|
32
|
+
return re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", s1).lower()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def snake_to_camel(name: str, *, upper_first: bool = False) -> str:
|
|
36
|
+
"""蛇形命名转驼峰命名。"""
|
|
37
|
+
parts = name.split("_")
|
|
38
|
+
if not parts:
|
|
39
|
+
return name
|
|
40
|
+
head, *tail = parts
|
|
41
|
+
camel = head if upper_first else head.lower()
|
|
42
|
+
camel += "".join(word.capitalize() for word in tail)
|
|
43
|
+
return camel
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def remove_whitespace(text: str) -> str:
|
|
47
|
+
"""移除所有空白字符。"""
|
|
48
|
+
return re.sub(r"\s+", "", text)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def is_blank(text: str | None) -> bool:
|
|
52
|
+
"""判断文本是否为空或仅含空白。"""
|
|
53
|
+
return text is None or not text.strip()
|