Compare commits
317 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
169a60690d | ||
|
|
819c29b843 | ||
|
|
f1f875a325 | ||
|
|
8175fa7fae | ||
|
|
60bbadde5b | ||
|
|
a09aadb40f | ||
|
|
a178233898 | ||
|
|
f752e7fe47 | ||
|
|
52afa7d3e6 | ||
|
|
95867d1a82 | ||
|
|
12556b3d48 | ||
|
|
9694d199d5 | ||
|
|
71c295474a | ||
|
|
daa4d71cc9 | ||
|
|
fe784dc01a | ||
|
|
9c79951e0d | ||
|
|
00cedaad11 | ||
|
|
a827a26733 | ||
|
|
76e38c3004 | ||
|
|
9cb1d12901 | ||
|
|
17dab506d3 | ||
|
|
1fb8aff952 | ||
|
|
c04bc468b4 | ||
|
|
0001c58876 | ||
|
|
4f1d7526f1 | ||
|
|
84d5a7ec19 | ||
|
|
a7b70e4cb2 | ||
|
|
79248500cb | ||
|
|
de1f1b0fe4 | ||
|
|
d48175430b | ||
|
|
9ad191ed39 | ||
|
|
b9e8dea1b0 | ||
|
|
f54dc6494d | ||
|
|
9b65b07710 | ||
|
|
36ab458cb6 | ||
|
|
6f95939dd3 | ||
|
|
20711b1513 | ||
|
|
bd792cbdd7 | ||
|
|
a8ba8108b0 | ||
|
|
2e32922701 | ||
|
|
2a308a722c | ||
|
|
6554bf6288 | ||
|
|
8cbc63cf2d | ||
|
|
3cf3e7ee92 | ||
|
|
8f14512b76 | ||
|
|
d9522a2191 | ||
|
|
bc14753a17 | ||
|
|
b8b84f134f | ||
|
|
7a7f9c3ea3 | ||
|
|
223b885071 | ||
|
|
851f49fb64 | ||
|
|
be9028b1c2 | ||
|
|
b5d3660179 | ||
|
|
267aa302f5 | ||
|
|
f2b1f33cca | ||
|
|
82bf514329 | ||
|
|
8fc0d3da96 | ||
|
|
8c214c028d | ||
|
|
69c4145010 | ||
|
|
bf96ba6d75 | ||
|
|
91f0287751 | ||
|
|
6a361507ef | ||
|
|
e9c0e74dc7 | ||
|
|
1fe3ed58d1 | ||
|
|
9db5a7cdd3 | ||
|
|
91969356ea | ||
|
|
cee3a162d6 | ||
|
|
36e3f73440 | ||
|
|
9f4b2c0b61 | ||
|
|
4105c96e73 | ||
|
|
3d70c4b5d0 | ||
|
|
da1c3b600c | ||
|
|
ebcfd807f7 | ||
|
|
a4ab4e9a2d | ||
|
|
85290de257 | ||
|
|
88ad3dcfee | ||
|
|
82a5d654a7 | ||
|
|
33c07c0f0a | ||
|
|
2d05f1cc1e | ||
|
|
e2e90b221e | ||
|
|
3ab3721976 | ||
|
|
bbf255e798 | ||
|
|
78a5c482da | ||
|
|
be3e339011 | ||
|
|
cb34e49040 | ||
|
|
7912f2c896 | ||
|
|
af1411eb41 | ||
|
|
13d481130c | ||
|
|
af519d39b3 | ||
|
|
61083c3f0b | ||
|
|
85467568c2 | ||
|
|
8e1f1eb20e | ||
|
|
81b6de81d9 | ||
|
|
3c2b9f4a2c | ||
|
|
c4222ac48a | ||
|
|
280765c529 | ||
|
|
48012bb96a | ||
|
|
38fa058255 | ||
|
|
1055a1b9ca | ||
|
|
05c773d147 | ||
|
|
0b3d531e4a | ||
|
|
ecdd3b22c9 | ||
|
|
85063702f5 | ||
|
|
8507b2f164 | ||
|
|
17a4204731 | ||
|
|
c29cdf1cf1 | ||
|
|
aceca964aa | ||
|
|
d550f50f2c | ||
|
|
1e1b5071aa | ||
|
|
2e8cad87b5 | ||
|
|
19719cdf2f | ||
|
|
e9fe084af1 | ||
|
|
b0047bd7ae | ||
|
|
0ceb0f737e | ||
|
|
a3b1889fa3 | ||
|
|
84fd706c2a | ||
|
|
5665fe5639 | ||
|
|
324a4a136e | ||
|
|
5644df5a05 | ||
|
|
05102b34d5 | ||
|
|
63fbd10e27 | ||
|
|
cf7c7c9286 | ||
|
|
db3a06857f | ||
|
|
bca9818708 | ||
|
|
acc49808ff | ||
|
|
e617a41f01 | ||
|
|
034cff1be0 | ||
|
|
1a8c126c6a | ||
|
|
d12feae3ce | ||
|
|
a416f0c750 | ||
|
|
087cb49bd2 | ||
|
|
b5af8fef91 | ||
|
|
33b2e181be | ||
|
|
fa323578b5 | ||
|
|
83fb5a7baf | ||
|
|
035e99d378 | ||
|
|
f7e15146a2 | ||
|
|
ae3d30e9f5 | ||
|
|
c04dc48eb2 | ||
|
|
a38c0db1cd | ||
|
|
83a1ae8c34 | ||
|
|
57050222c1 | ||
|
|
eadd84e926 | ||
|
|
1d12a3c1b2 | ||
|
|
fa5f0ef421 | ||
|
|
61721a6980 | ||
|
|
b10c09fe87 | ||
|
|
3693efb89e | ||
|
|
1557bddc57 | ||
|
|
0150307f73 | ||
|
|
36c83c3e12 | ||
|
|
a7456a69fe | ||
|
|
efc75a9af7 | ||
|
|
09212337f4 | ||
|
|
7bda1386b6 | ||
|
|
2441e7cd98 | ||
|
|
0390b75734 | ||
|
|
eb0c13a949 | ||
|
|
180c35c2c7 | ||
|
|
749940777b | ||
|
|
6147f5ad8c | ||
|
|
f53e4062b3 | ||
|
|
7b6e971f7e | ||
|
|
c1d20cbff1 | ||
|
|
078455dfe6 | ||
|
|
773e5ee975 | ||
|
|
47464a1adf | ||
|
|
cf9572922a | ||
|
|
6943674b0b | ||
|
|
6c21fd5a44 | ||
|
|
cc9a81df6c | ||
|
|
f4181dc9d2 | ||
|
|
59ec216a97 | ||
|
|
eaf24bc39d | ||
|
|
97c4fb834b | ||
|
|
bcb18eddd1 | ||
|
|
2e96f8dc04 | ||
|
|
d33ffcec41 | ||
|
|
79b79eed22 | ||
|
|
2555d43705 | ||
|
|
6e411dbf2b | ||
|
|
57b26f9a88 | ||
|
|
cb16855423 | ||
|
|
f4babfc191 | ||
|
|
b6835fd87f | ||
|
|
2b762ffe6e | ||
|
|
7ac27caa5a | ||
|
|
aa40511547 | ||
|
|
90f0970a31 | ||
|
|
5fac34cd29 | ||
|
|
4a697b2dab | ||
|
|
85aff65a0b | ||
|
|
62e761a38f | ||
|
|
b6666447aa | ||
|
|
2755ed4d99 | ||
|
|
76d0b4ce6a | ||
|
|
abf90dd9d0 | ||
|
|
ce2a5bbd1a | ||
|
|
329e1efb72 | ||
|
|
ce414a3857 | ||
|
|
144dacbc3a | ||
|
|
efd7378d50 | ||
|
|
2055a325b5 | ||
|
|
14f89d0dd9 | ||
|
|
c90bab64c4 | ||
|
|
cd4f4e1cf6 | ||
|
|
8f0552c9a9 | ||
|
|
03cc457c6c | ||
|
|
3c3e033111 | ||
|
|
b704dcefd0 | ||
|
|
ead0a1f235 | ||
|
|
7776f0e721 | ||
|
|
ef9b349aea | ||
|
|
2d8467ea11 | ||
|
|
ea11caca78 | ||
|
|
b6b4d988ba | ||
|
|
63dfad0518 | ||
|
|
7afa60607f | ||
|
|
9b807dee2b | ||
|
|
e2ad45e72f | ||
|
|
caf909cd97 | ||
|
|
f74c3b6940 | ||
|
|
11df7dd8e1 | ||
|
|
9332c4a17b | ||
|
|
e0ab27ddf8 | ||
|
|
1ec75f0d97 | ||
|
|
68fb05396f | ||
|
|
6e795985c5 | ||
|
|
248a105165 | ||
|
|
4ac16fe3ce | ||
|
|
3efb5f773e | ||
|
|
0304f4ac9b | ||
|
|
d7a0333803 | ||
|
|
37742a253c | ||
|
|
9e45eb59f3 | ||
|
|
629687318c | ||
|
|
ac85a3e328 | ||
|
|
f9904b5738 | ||
|
|
2a9a3c5eda | ||
|
|
74efaad870 | ||
|
|
58fbf7445e | ||
|
|
242ba04c4c | ||
|
|
75508a4dd0 | ||
|
|
c988508386 | ||
|
|
e61c27b2ec | ||
|
|
c955360497 | ||
|
|
b86c4c7cec | ||
|
|
e68c3ccd07 | ||
|
|
334498f6f8 | ||
|
|
9b0c90cf26 | ||
|
|
5028f6b279 | ||
|
|
5e3ff79fa2 | ||
|
|
f4fc3ef34a | ||
|
|
fc18982b60 | ||
|
|
cc40440559 | ||
|
|
92529db33f | ||
|
|
f60f6b561f | ||
|
|
9bbf7e2c6c | ||
|
|
0e27ae3261 | ||
|
|
d127b2e549 | ||
|
|
0721b83788 | ||
|
|
89418e9154 | ||
|
|
e0427293c5 | ||
|
|
883320c816 | ||
|
|
500a543504 | ||
|
|
131d16ac00 | ||
|
|
4f66f2cd01 | ||
|
|
266490d2f2 | ||
|
|
c02a01a503 | ||
|
|
1a8cfbb449 | ||
|
|
a1836bb6e4 | ||
|
|
9960f7c784 | ||
|
|
9367cbe9ab | ||
|
|
f3c1461ca7 | ||
|
|
fcba0d0935 | ||
|
|
cd887fa0c0 | ||
|
|
630131c7a6 | ||
|
|
dc86797da6 | ||
|
|
4d14e97eb0 | ||
|
|
82da182bb2 | ||
|
|
6fe2022728 | ||
|
|
2417a33189 | ||
|
|
c9bdb2d335 | ||
|
|
d42d80993e | ||
|
|
2670510121 | ||
|
|
2e49060eb2 | ||
|
|
fffdf8dcdc | ||
|
|
753d5e2322 | ||
|
|
3ec7ebd4b5 | ||
|
|
3f89807da0 | ||
|
|
b15cd16379 | ||
|
|
58110b7c8c | ||
|
|
db217be35e | ||
|
|
3d440ffeb3 | ||
|
|
170925a1bd | ||
|
|
5340cea2b5 | ||
|
|
30f01ddcb9 | ||
|
|
ac3dffe1de | ||
|
|
e5962cf6e4 | ||
|
|
762c8a1926 | ||
|
|
c0b5268202 | ||
|
|
448bb70398 | ||
|
|
70f5c3315d | ||
|
|
501d4ab957 | ||
|
|
54e008b91e | ||
|
|
e1c51310fd | ||
|
|
ee244176d7 | ||
|
|
fe1b325702 | ||
|
|
baa7325707 | ||
|
|
ad1d3c40a6 | ||
|
|
c22776a73f | ||
|
|
ba318e9c8d | ||
|
|
4667288acc | ||
|
|
a27642bc38 | ||
|
|
ac88a050d6 | ||
|
|
74ac8d2bd8 | ||
|
|
4d43d358ba |
33
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
33
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,34 +1,41 @@
|
||||
name: Bug 反馈
|
||||
description: 功能运行不正常 / 失效
|
||||
title: "[BUG] <title>"
|
||||
title: "[BUG] "
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
遇到问题请先尝试使用[Action](https://github.com/tindy2013/subconverter/actions)中的最新版本
|
||||
遇到问题请先尝试使用最新版本镜像。
|
||||
如果是使用公共转换服务中遇到的问题,请先联系服务的提供者。
|
||||
Please note that if the bug report does not include reproduction steps, configuration files, or relevant URL content, it will not be considered.
|
||||
- type: checkboxes
|
||||
id: version-check
|
||||
attributes:
|
||||
label: 确认版本最新
|
||||
description: 对于您遇到的BUG,是否尝试在最新Action编译的版本中复现?
|
||||
description: 对于您遇到的 Bug,是否尝试在最新 Action 编译的版本中复现?
|
||||
options:
|
||||
- label: 我已经确认在最新Action编译的版本中复现
|
||||
- label: 我已经确认在最新 Action 编译的版本中复现
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: public-check
|
||||
attributes:
|
||||
label: 已尝试使用公共实例
|
||||
description: 对于您遇到的 Bug,是否尝试在本项目的公共实例(api.asailor.org)中复现?
|
||||
options:
|
||||
- label: 我已经确认在公共实例中复现
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: issue-check
|
||||
attributes:
|
||||
label: 检索issue
|
||||
description: 对于您遇到的BUG,是否已经确认之前没有其他issue涉及?
|
||||
description: 对于您遇到的 Bug,是否已经确认之前没有其他 Issue 涉及?
|
||||
options:
|
||||
- label: 我已经确认之前没有issue涉及此BUG
|
||||
- label: 我已经确认之前没有 Issue 涉及此 Bug
|
||||
required: true
|
||||
- type: input
|
||||
id: subconverter-version
|
||||
attributes:
|
||||
label: subconverter版本
|
||||
description: subconverter版本
|
||||
label: SubConverter-Extended 版本
|
||||
description: SubConverter-Extended 版本
|
||||
placeholder: 请输入详细的版本号
|
||||
validations:
|
||||
required: true
|
||||
@@ -36,7 +43,7 @@ body:
|
||||
id: convert
|
||||
attributes:
|
||||
label: 转换过程
|
||||
description: BUG发生在什么配置转换为什么配置的过程中?
|
||||
description: Bug 发生在什么配置转换为什么配置的过程中?
|
||||
placeholder: 请输入“xxx转换为xxx配置”
|
||||
validations:
|
||||
required: true
|
||||
@@ -44,7 +51,7 @@ body:
|
||||
id: config
|
||||
attributes:
|
||||
label: 转换设置
|
||||
description: 转换时使用的subconverter配置文件或外部配置链接
|
||||
description: 转换时使用的配置文件或外部配置链接
|
||||
placeholder: 请输入
|
||||
validations:
|
||||
required: true
|
||||
@@ -52,7 +59,7 @@ body:
|
||||
id: description
|
||||
attributes:
|
||||
label: 复现步骤
|
||||
description: BUG的详细复现步骤
|
||||
description: Bug 的详细复现步骤
|
||||
placeholder: 请输入
|
||||
validations:
|
||||
required: true
|
||||
@@ -76,7 +83,7 @@ body:
|
||||
id: logs
|
||||
attributes:
|
||||
label: 错误信息
|
||||
description: subconverter转换时的日志/信息输出
|
||||
description: SubConverter-Extended 转换时的日志/信息输出
|
||||
render: shell
|
||||
placeholder: 请输入
|
||||
validations:
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 阅读文档
|
||||
url: https://github.com/tindy2013/subconverter/blob/master/README-cn.md
|
||||
url: https://github.com/Aethersailor/SubConverter-Extended/blob/master/README-cn.md
|
||||
about: 建议您发布issue前先仔细阅读项目文档
|
||||
10
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
10
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -1,6 +1,6 @@
|
||||
name: 建议新功能
|
||||
description: 建议此项目增加的功能
|
||||
title: "[Feature] <title>"
|
||||
title: "[Feature] "
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: ensure
|
||||
@@ -8,9 +8,9 @@ body:
|
||||
label: verify
|
||||
description: 在提交之前,请确认完成以下选项
|
||||
options:
|
||||
- label: 我已经仔细阅读[项目文档](https://github.com/tindy2013/subconverter/blob/master/README-cn.md),确认现有功能无法解决我的需求
|
||||
- label: 我已经仔细阅读[项目文档](https://github.com/Aethersailor/SubConverter-Extended/blob/master/README-cn.md),确认现有功能无法解决我的需求
|
||||
required: true
|
||||
- label: 我已经检索过现有[issue](https://github.com/tindy2013/subconverter/issues),确认与现有issue的内容并不重复
|
||||
- label: 我已经检索过现有[issue](https://github.com/Aethersailor/SubConverter-Extended/issues),确认与现有issue的内容并不重复
|
||||
required: true
|
||||
- label: 我已经尝试自行解决,确认自己没有能力解决
|
||||
required: true
|
||||
@@ -18,7 +18,7 @@ body:
|
||||
id: description
|
||||
attributes:
|
||||
label: 功能描述
|
||||
description: 请详细、清晰地表达你要提出的论述,例如这个问题如何影响到你?你想实现什么功能?目前 subconverter 的行为是什么?
|
||||
description: 请详细、清晰地表达你要提出的论述,例如这个问题如何影响到你?你想实现什么功能?目前 SubConverter-Extended 的行为是什么?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
@@ -27,4 +27,4 @@ body:
|
||||
label: 可能的解决方案
|
||||
description: 您期望的解决方案,可能的实现方法或者可供参考的示例
|
||||
validations:
|
||||
required: false
|
||||
required: false
|
||||
|
||||
20
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
20
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
<!--
|
||||
🛑 必读警告 (CRITICAL WARNING) 🛑
|
||||
所有的 PR 必须指向 `dev` 分支!请不要给 `master` 分支提 PR!
|
||||
All PRs MUST point to the `dev` branch! Please DO NOT submit PRs to `master`!
|
||||
-->
|
||||
|
||||
## 📝 更新内容 (Description of Changes)
|
||||
|
||||
-
|
||||
-
|
||||
|
||||
## 🔗 相关 Issue (Related Issues)
|
||||
|
||||
- Fixes #
|
||||
|
||||
## ✅ 提交前的自查 (Checklist)
|
||||
|
||||
- [ ] 我确认当前 PR 的目标分支是 `dev`,而不是 `master`。(I confirm the target branch is `dev`, not `master`.)
|
||||
- [ ] 我已经在本地测试了我的代码。(I have tested my code locally.)
|
||||
|
||||
56
.github/actions/smoke-docker-image/action.yml
vendored
Normal file
56
.github/actions/smoke-docker-image/action.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
name: Smoke test Docker image
|
||||
description: Run a Docker image and verify that the SubConverter HTTP endpoint responds.
|
||||
|
||||
inputs:
|
||||
image:
|
||||
description: Image reference, preferably pinned by digest.
|
||||
required: true
|
||||
platform:
|
||||
description: Docker platform to run, such as linux/amd64 or linux/arm/v7.
|
||||
required: true
|
||||
port:
|
||||
description: Container port exposed by SubConverter.
|
||||
required: false
|
||||
default: "25500"
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Run image and check /version
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
IMAGE="${{ inputs.image }}"
|
||||
PLATFORM="${{ inputs.platform }}"
|
||||
CONTAINER_PORT="${{ inputs.port }}"
|
||||
|
||||
CID="$(docker run -d --platform "$PLATFORM" -e PORT="$CONTAINER_PORT" -p "127.0.0.1::${CONTAINER_PORT}" "$IMAGE")"
|
||||
trap 'docker rm -f "$CID" >/dev/null 2>&1 || true' EXIT
|
||||
|
||||
HOST_PORT="$(docker port "$CID" "${CONTAINER_PORT}/tcp" | awk -F: 'END {print $NF}')"
|
||||
URL="http://127.0.0.1:${HOST_PORT}/version"
|
||||
|
||||
docker exec "$CID" sh -c '
|
||||
test "${TZ:-}" = "Asia/Shanghai"
|
||||
test -e /usr/share/zoneinfo/Asia/Shanghai
|
||||
test "$(date +%z)" = "+0800"
|
||||
'
|
||||
|
||||
for _ in $(seq 1 30); do
|
||||
if curl -fsS "$URL" >/tmp/subconverter-smoke.out; then
|
||||
head -c 200 /tmp/subconverter-smoke.out
|
||||
python3 scripts/run-subconverter-smoke.py --base-url "http://127.0.0.1:${HOST_PORT}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! docker ps --filter "id=$CID" --format '{{.ID}}' | grep -q .; then
|
||||
docker logs "$CID" || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sleep 2
|
||||
done
|
||||
|
||||
docker logs "$CID" || true
|
||||
exit 1
|
||||
60
.github/actions/smoke-linux-artifact/action.yml
vendored
Normal file
60
.github/actions/smoke-linux-artifact/action.yml
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
name: Smoke test Linux portable artifact
|
||||
description: Extract a Linux release artifact and verify that the HTTP endpoint responds.
|
||||
|
||||
inputs:
|
||||
artifact:
|
||||
description: Path to the tar.gz artifact.
|
||||
required: true
|
||||
port:
|
||||
description: Port used for the smoke test.
|
||||
required: false
|
||||
default: "25500"
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Run artifact and check /version
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
ARTIFACT="${{ inputs.artifact }}"
|
||||
PORT="${{ inputs.port }}"
|
||||
WORKDIR="$(mktemp -d)"
|
||||
PID=""
|
||||
|
||||
cleanup() {
|
||||
if [ -n "$PID" ] && kill -0 "$PID" >/dev/null 2>&1; then
|
||||
kill "$PID" >/dev/null 2>&1 || true
|
||||
wait "$PID" >/dev/null 2>&1 || true
|
||||
fi
|
||||
rm -rf "$WORKDIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
tar -xzf "$ARTIFACT" -C "$WORKDIR"
|
||||
cd "$WORKDIR/SubConverter-Extended"
|
||||
|
||||
PORT="$PORT" PREF_PATH="$PWD/base/pref.toml" ./start.sh >"$WORKDIR/stdout.log" 2>"$WORKDIR/stderr.log" &
|
||||
PID="$!"
|
||||
|
||||
URL="http://127.0.0.1:${PORT}/version"
|
||||
for _ in $(seq 1 30); do
|
||||
if curl -fsS "$URL" >"$WORKDIR/response.out"; then
|
||||
head -c 200 "$WORKDIR/response.out"
|
||||
python3 "$GITHUB_WORKSPACE/scripts/run-subconverter-smoke.py" --base-url "http://127.0.0.1:${PORT}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! kill -0 "$PID" >/dev/null 2>&1; then
|
||||
cat "$WORKDIR/stdout.log" || true
|
||||
cat "$WORKDIR/stderr.log" || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sleep 2
|
||||
done
|
||||
|
||||
cat "$WORKDIR/stdout.log" || true
|
||||
cat "$WORKDIR/stderr.log" || true
|
||||
exit 1
|
||||
47
.github/actions/smoke-openwrt-apk/action.yml
vendored
Normal file
47
.github/actions/smoke-openwrt-apk/action.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
name: Smoke test OpenWrt APK artifacts
|
||||
description: Verify that OpenWrt APK artifacts install into an apk root.
|
||||
|
||||
inputs:
|
||||
artifacts:
|
||||
description: Shell glob matching APK artifacts.
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Install APKs into apk roots
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
shopt -s nullglob
|
||||
|
||||
artifacts=( ${{ inputs.artifacts }} )
|
||||
if [ "${#artifacts[@]}" -eq 0 ]; then
|
||||
echo "No APK artifacts matched: ${{ inputs.artifacts }}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for artifact in "${artifacts[@]}"; do
|
||||
file="$(basename "$artifact")"
|
||||
arch="${file%.apk}"
|
||||
arch="${arch##*-openwrt-}"
|
||||
|
||||
docker run --rm \
|
||||
-v "$PWD:/work" \
|
||||
-w /work \
|
||||
-e "APK_ARTIFACT=$artifact" \
|
||||
-e "OPENWRT_ARCH=$arch" \
|
||||
mirror.gcr.io/library/alpine:latest \
|
||||
sh -ec '
|
||||
apk add --root /tmp/apk-root --initdb --arch "$OPENWRT_ARCH" --allow-untrusted "/work/$APK_ARTIFACT"
|
||||
test -x /tmp/apk-root/usr/bin/subconverter-extended
|
||||
test -x /tmp/apk-root/etc/init.d/subconverter-extended
|
||||
test -d /tmp/apk-root/etc/subconverter
|
||||
test -x /tmp/apk-root/opt/subconverter-extended/subconverter
|
||||
test -f /tmp/apk-root/opt/subconverter-extended/base/pref.example.toml
|
||||
test -f /tmp/apk-root/usr/share/doc/subconverter-extended/README.OpenWrt
|
||||
grep -q "/etc/subconverter/pref.toml" /tmp/apk-root/usr/share/doc/subconverter-extended/README.OpenWrt
|
||||
grep -q "CONFIG_DIR=\"/etc/subconverter\"" /tmp/apk-root/usr/bin/subconverter-extended
|
||||
apk info --root /tmp/apk-root subconverter-extended
|
||||
'
|
||||
done
|
||||
93
.github/actions/smoke-windows-artifact/action.yml
vendored
Normal file
93
.github/actions/smoke-windows-artifact/action.yml
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
name: Smoke test Windows portable artifact
|
||||
description: Extract a Windows release artifact and verify that the HTTP endpoint responds.
|
||||
|
||||
inputs:
|
||||
artifact:
|
||||
description: Path to the zip artifact.
|
||||
required: true
|
||||
port:
|
||||
description: Port used for the smoke test.
|
||||
required: false
|
||||
default: "25500"
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Run artifact and check /version
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$artifact = "${{ inputs.artifact }}"
|
||||
$port = "${{ inputs.port }}"
|
||||
$workDir = Join-Path $env:RUNNER_TEMP ("subconverter-windows-" + [guid]::NewGuid().ToString("N"))
|
||||
New-Item -ItemType Directory -Path $workDir | Out-Null
|
||||
|
||||
try {
|
||||
Expand-Archive -Path $artifact -DestinationPath $workDir -Force
|
||||
$root = Join-Path $workDir "SubConverter-Extended"
|
||||
$pref = Join-Path $root "base\pref.toml"
|
||||
$stdout = Join-Path $workDir "stdout.log"
|
||||
$stderr = Join-Path $workDir "stderr.log"
|
||||
|
||||
Remove-Item (Join-Path $root "base\pref.toml") -ErrorAction SilentlyContinue
|
||||
Remove-Item (Join-Path $root "base\pref.yml") -ErrorAction SilentlyContinue
|
||||
Remove-Item (Join-Path $root "base\pref.ini") -ErrorAction SilentlyContinue
|
||||
|
||||
$previousPort = $env:PORT
|
||||
$previousPath = $env:PATH
|
||||
$env:PORT = $port
|
||||
$env:PATH = "$root;$env:PATH"
|
||||
|
||||
$process = Start-Process `
|
||||
-FilePath "pwsh" `
|
||||
-ArgumentList @("-NoProfile", "-ExecutionPolicy", "Bypass", "-File", (Join-Path $root "start.ps1")) `
|
||||
-WorkingDirectory $root `
|
||||
-RedirectStandardOutput $stdout `
|
||||
-RedirectStandardError $stderr `
|
||||
-PassThru
|
||||
$url = "http://127.0.0.1:$port/version"
|
||||
$ok = $false
|
||||
|
||||
for ($i = 0; $i -lt 30; $i++) {
|
||||
try {
|
||||
$response = Invoke-WebRequest -UseBasicParsing -Uri $url -TimeoutSec 2
|
||||
if ($response.StatusCode -eq 200) {
|
||||
$response.Content.Substring(0, [Math]::Min(200, $response.Content.Length))
|
||||
$ok = $true
|
||||
break
|
||||
}
|
||||
} catch {
|
||||
Start-Sleep -Seconds 1
|
||||
}
|
||||
|
||||
if ($process.HasExited) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $ok) {
|
||||
Write-Host "stdout:"
|
||||
if (Test-Path $stdout) { Get-Content $stdout -ErrorAction SilentlyContinue }
|
||||
Write-Host "stderr:"
|
||||
if (Test-Path $stderr) { Get-Content $stderr -ErrorAction SilentlyContinue }
|
||||
throw "Windows artifact smoke test failed."
|
||||
}
|
||||
|
||||
python "$env:GITHUB_WORKSPACE\scripts\run-subconverter-smoke.py" --base-url "http://127.0.0.1:$port"
|
||||
|
||||
if (-not (Test-Path $pref)) {
|
||||
throw "Windows launcher did not create base\pref.toml on first start."
|
||||
}
|
||||
} finally {
|
||||
if ($process -and -not $process.HasExited) {
|
||||
$process.Kill()
|
||||
$process.WaitForExit()
|
||||
}
|
||||
Get-CimInstance Win32_Process -Filter "name = 'subconverter.exe'" -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.ExecutablePath -and $_.ExecutablePath.StartsWith($root, [System.StringComparison]::OrdinalIgnoreCase) } |
|
||||
ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }
|
||||
if ($null -ne $previousPort) { $env:PORT = $previousPort } else { Remove-Item Env:PORT -ErrorAction SilentlyContinue }
|
||||
if ($null -ne $previousPath) { $env:PATH = $previousPath }
|
||||
Remove-Item -Recurse -Force $workDir -ErrorAction SilentlyContinue
|
||||
}
|
||||
10
.github/dependabot.yml
vendored
10
.github/dependabot.yml
vendored
@@ -6,6 +6,8 @@ updates:
|
||||
target-branch: "dev"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
open-pull-requests-limit: 10
|
||||
rebase-strategy: "auto"
|
||||
groups:
|
||||
actions:
|
||||
patterns:
|
||||
@@ -21,6 +23,12 @@ updates:
|
||||
target-branch: "dev"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
open-pull-requests-limit: 10
|
||||
rebase-strategy: "auto"
|
||||
groups:
|
||||
docker:
|
||||
patterns:
|
||||
- "*"
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "docker"
|
||||
@@ -32,6 +40,8 @@ updates:
|
||||
target-branch: "dev"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
open-pull-requests-limit: 10
|
||||
rebase-strategy: "auto"
|
||||
groups:
|
||||
go-dependencies:
|
||||
patterns:
|
||||
|
||||
2
.github/mihomo-meta.rev
vendored
2
.github/mihomo-meta.rev
vendored
@@ -1 +1 @@
|
||||
3aa668c55704399ede35cd710a48c20111d23ea6
|
||||
fc8c5a24b16991f98cd736950c17d1aa306a5041
|
||||
|
||||
20
.github/upstream-subconverter.applied.json
vendored
Normal file
20
.github/upstream-subconverter.applied.json
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
[
|
||||
{
|
||||
"sha": "ca209069bef6aa192ea4c6d63acc8fcc2125ad03",
|
||||
"subject": "Update subparser.cpp",
|
||||
"time": "2026-05-21T02:19:51+00:00",
|
||||
"paths": [
|
||||
"src/parser/subparser.cpp"
|
||||
],
|
||||
"copilot_reason": "Single-line parser tweak (case-insensitive regex for wssettings). No generator or bridge changes."
|
||||
},
|
||||
{
|
||||
"sha": "79369b0e7617e24366b57cbfb0402e6293d04fb4",
|
||||
"subject": "Update subparser.cpp",
|
||||
"time": "2026-05-21T02:19:51+00:00",
|
||||
"paths": [
|
||||
"src/parser/subparser.cpp"
|
||||
],
|
||||
"copilot_reason": "Small parser-only adjustments to plugin option handling; backward-compatible parsing changes."
|
||||
}
|
||||
]
|
||||
1
.github/upstream-subconverter.seen
vendored
Normal file
1
.github/upstream-subconverter.seen
vendored
Normal file
@@ -0,0 +1 @@
|
||||
633ecd5a3b33cf288658f0910fb2cc5faabd351c
|
||||
53
.github/upstream-subconverter.skipped.json
vendored
Normal file
53
.github/upstream-subconverter.skipped.json
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
[
|
||||
{
|
||||
"sha": "c6b9661088941e56cd3def92f579add450276626",
|
||||
"subject": "add vless encryption field support.(#59) fix AnyTLS and TUIC IPV6 filed parse and some AnyTLS.(#58) fix macos arm build.",
|
||||
"time": "2026-05-21T02:19:51+00:00",
|
||||
"reason": "Adds VLESS encryption and AnyTLS/TUIC parsing fixes while also changing generator/export code — requires coordinated exporter updates.",
|
||||
"copilot_decision": {
|
||||
"sha": "c6b9661088941e56cd3def92f579add450276626",
|
||||
"decision": "needs_output_adapter",
|
||||
"risk": "medium",
|
||||
"reason": "Adds VLESS encryption and AnyTLS/TUIC parsing fixes while also changing generator/export code — requires coordinated exporter updates.",
|
||||
"required_tests": [
|
||||
"explodeStdVless_encryption_field",
|
||||
"explodeSingbox_vless_encryption_capture",
|
||||
"singbox_export_roundtrip_with_encryption"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"sha": "bca37c74367792077d35a3260cb65bd0e75ae8e2",
|
||||
"subject": "Enhance proxy configuration to support the WebSocket transport protocol and custom headers.",
|
||||
"time": "2026-05-21T02:19:51+00:00",
|
||||
"reason": "Introduces WebSocket host/path/header parsing and generator ws-opts mapping; parser and exporter must be applied together.",
|
||||
"copilot_decision": {
|
||||
"sha": "bca37c74367792077d35a3260cb65bd0e75ae8e2",
|
||||
"decision": "needs_output_adapter",
|
||||
"risk": "medium",
|
||||
"reason": "Introduces WebSocket host/path/header parsing and generator ws-opts mapping; parser and exporter must be applied together.",
|
||||
"required_tests": [
|
||||
"explodeTrojan_ws_host_fallback",
|
||||
"proxyToClash_ws_opts_generation",
|
||||
"clash_ws_roundtrip_headers"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"sha": "877a1291a0b618ad52f98ea1ffaddb5d17c1c22f",
|
||||
"subject": "keep dialer-proxy when converting Clash subscriptions",
|
||||
"time": "2026-05-21T02:19:51+00:00",
|
||||
"reason": "Propagates dialer/underlying_proxy across many proxy types and updates generator; application requires exporter changes to preserve detour/dialer-proxy.",
|
||||
"copilot_decision": {
|
||||
"sha": "877a1291a0b618ad52f98ea1ffaddb5d17c1c22f",
|
||||
"decision": "needs_output_adapter",
|
||||
"risk": "medium",
|
||||
"reason": "Propagates dialer/underlying_proxy across many proxy types and updates generator; application requires exporter changes to preserve detour/dialer-proxy.",
|
||||
"required_tests": [
|
||||
"explodeClash_underlying_proxy_propagation",
|
||||
"proxy_export_preserves_dialer_proxy",
|
||||
"clash_subscription_conversion_dialer_proxy"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
73
.github/workflows/auto-merge-dependabot.yml
vendored
73
.github/workflows/auto-merge-dependabot.yml
vendored
@@ -1,84 +1,31 @@
|
||||
name: Auto-merge Dependabot PRs
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, reopened]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
checks: read
|
||||
|
||||
jobs:
|
||||
auto-merge:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.actor == 'dependabot[bot]'
|
||||
steps:
|
||||
- name: Check PR labels
|
||||
id: check-labels
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const labels = context.payload.pull_request.labels.map(l => l.name);
|
||||
const hasAutomerge = labels.includes('automerge');
|
||||
console.log('PR labels:', labels);
|
||||
console.log('Has automerge label:', hasAutomerge);
|
||||
return hasAutomerge;
|
||||
|
||||
- name: Wait for build workflow
|
||||
if: steps.check-labels.outputs.result == 'true'
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
|
||||
const maxWait = 30 * 60 * 1000; // 30 minutes
|
||||
const pollInterval = 30 * 1000; // 30 seconds
|
||||
let elapsed = 0;
|
||||
|
||||
while (elapsed < maxWait) {
|
||||
const { data: checks } = await github.rest.checks.listForRef({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: context.payload.pull_request.head.sha,
|
||||
});
|
||||
|
||||
const buildCheck = checks.check_runs.find(
|
||||
check => check.name.includes('Build')
|
||||
);
|
||||
|
||||
if (buildCheck) {
|
||||
console.log(`Build status: ${buildCheck.status} / ${buildCheck.conclusion}`);
|
||||
|
||||
if (buildCheck.status === 'completed') {
|
||||
if (buildCheck.conclusion === 'success') {
|
||||
console.log('Build succeeded! Proceeding to merge.');
|
||||
return true;
|
||||
} else {
|
||||
core.setFailed(`Build failed with conclusion: ${buildCheck.conclusion}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Waiting for build... (${elapsed/1000}s elapsed)`);
|
||||
await wait(pollInterval);
|
||||
elapsed += pollInterval;
|
||||
}
|
||||
|
||||
core.setFailed('Build did not complete within 30 minutes');
|
||||
return false;
|
||||
|
||||
- name: Enable auto-merge
|
||||
if: steps.check-labels.outputs.result == 'true'
|
||||
uses: actions/github-script@v8
|
||||
- name: Merge Dependabot PR
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const pullNumber = context.payload.pull_request.number;
|
||||
const title = context.payload.pull_request.title;
|
||||
|
||||
await github.rest.pulls.merge({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: context.payload.pull_request.number,
|
||||
pull_number: pullNumber,
|
||||
merge_method: 'squash',
|
||||
commit_title: `${context.payload.pull_request.title} (#${context.payload.pull_request.number})`,
|
||||
commit_title: `${title} (#${pullNumber})`,
|
||||
});
|
||||
console.log('PR merged successfully!');
|
||||
|
||||
console.log('Dependabot PR merged successfully.');
|
||||
|
||||
59
.github/workflows/block-master-prs.yml
vendored
Normal file
59
.github/workflows/block-master-prs.yml
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
name: Block PRs to Master
|
||||
on:
|
||||
pull_request_target:
|
||||
branches:
|
||||
- master
|
||||
types: [opened, reopened, synchronize]
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
block-master-pr:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Actor
|
||||
id: check
|
||||
run: |
|
||||
# 允许拥有者(你自己)或者特定机器人的 PR 提给 master
|
||||
# 这里把你的 GitHub username 填入(假设为 Aethersailor)
|
||||
if [[ "${{ github.actor }}" == "Aethersailor" || "${{ github.actor }}" == "github-actions[bot]" || "${{ github.actor }}" == "dependabot[bot]" ]]; then
|
||||
echo "is_allowed=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "is_allowed=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Close PR and Leave Comment
|
||||
if: steps.check.outputs.is_allowed == 'false'
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const message = `
|
||||
🚫 **自动拦截提示 (Auto-Block)** 🚫
|
||||
|
||||
感谢你的提交!(Thanks for your contribution!)
|
||||
|
||||
本项目严格使用双分支开发模型:
|
||||
- \`master\` 分支仅用于最终发布。
|
||||
- **所有的代码贡献和 PR 必须指向 \`dev\` 分支。**
|
||||
|
||||
这个 PR 已经被自动关闭。请将你本地的分支目标修改为 \`dev\` 后再重新提交一个 PR。非常感谢你的理解与支持!
|
||||
|
||||
(Please change the target branch of your PR to \`dev\` and resubmit. The \`master\` branch is for releases only.)
|
||||
`;
|
||||
|
||||
// 评论
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: message
|
||||
});
|
||||
|
||||
// 关闭 PR
|
||||
await github.rest.pulls.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: context.issue.number,
|
||||
state: 'closed'
|
||||
});
|
||||
879
.github/workflows/build-dockerhub.yml
vendored
879
.github/workflows/build-dockerhub.yml
vendored
File diff suppressed because it is too large
Load Diff
13
.github/workflows/codeql.yml
vendored
13
.github/workflows/codeql.yml
vendored
@@ -15,6 +15,7 @@ jobs:
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.language }})
|
||||
runs-on: ubuntu-latest
|
||||
if: github.actor != 'dependabot[bot]'
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
@@ -37,6 +38,8 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
token: ${{ secrets.PAT_TOKEN || github.token }}
|
||||
|
||||
# [OPTIMIZATION] Cache build artifacts (QuickJSPP & LibCron)
|
||||
- name: Cache Build Artifacts
|
||||
@@ -71,9 +74,13 @@ jobs:
|
||||
# [SETUP] Build Go library (libmihomo.a) for C++ linkage
|
||||
- name: Setup Go
|
||||
if: matrix.language == 'c-cpp'
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: 'stable'
|
||||
shell: bash
|
||||
run: |
|
||||
if ! command -v go >/dev/null 2>&1; then
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends golang-go
|
||||
fi
|
||||
go version
|
||||
|
||||
- name: Build libmihomo.a
|
||||
if: matrix.language == 'c-cpp'
|
||||
|
||||
170
.github/workflows/sync-dev-to-master.yml
vendored
170
.github/workflows/sync-dev-to-master.yml
vendored
@@ -2,6 +2,28 @@ name: Sync Dev to Master
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_mode:
|
||||
description: "Release mode: new creates a tag, overwrite rebuilds an existing release, sync_only only syncs branches."
|
||||
required: true
|
||||
default: "new"
|
||||
type: choice
|
||||
options:
|
||||
- new
|
||||
- overwrite
|
||||
- sync_only
|
||||
version:
|
||||
description: "Release tag, e.g. v1.2.3. Empty auto bumps for new or uses latest existing tag for overwrite."
|
||||
required: false
|
||||
type: string
|
||||
confirm_overwrite:
|
||||
description: "Type OVERWRITE to confirm rebuilding an existing release."
|
||||
required: false
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: sync-dev-to-master
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
sync-branches:
|
||||
@@ -30,8 +52,8 @@ jobs:
|
||||
CONFLICTED_FILES=$(git diff --name-only --diff-filter=U)
|
||||
|
||||
for file in $CONFLICTED_FILES; do
|
||||
if [[ "$file" == README*.md ]]; then
|
||||
# For README files, keep master version
|
||||
if [[ "$file" == README*.md || "$file" == docs/images/readme-flow-*.svg ]]; then
|
||||
# For README files and their diagram assets, keep master version
|
||||
echo "Keeping master version of $file"
|
||||
git checkout --ours "$file"
|
||||
else
|
||||
@@ -43,18 +65,26 @@ jobs:
|
||||
done
|
||||
fi
|
||||
|
||||
echo "Restoring master README..."
|
||||
# Restore README.md from HEAD (master) state, discarding changes from dev
|
||||
echo "Restoring master README and diagram assets..."
|
||||
# Restore README.md and its diagram assets from HEAD (master) state, discarding changes from dev
|
||||
git checkout HEAD -- README.md 2>/dev/null || true
|
||||
git checkout HEAD -- 'README-*.md' 2>/dev/null || true
|
||||
git checkout HEAD -- 'docs/images/readme-flow-*.svg' 2>/dev/null || true
|
||||
|
||||
# Re-add README files to staging if they were modified
|
||||
# Re-add README files and diagram assets to staging if they were modified
|
||||
git add README.md 2>/dev/null || true
|
||||
git add README-*.md 2>/dev/null || true
|
||||
git add docs/images/readme-flow-*.svg 2>/dev/null || true
|
||||
|
||||
# Check if there are changes to commit (excluding README)
|
||||
if git diff --staged --quiet; then
|
||||
echo "No changes to commit (maybe dev is already merged or only README changed)"
|
||||
# Commit whenever a merge is in progress, even if protected docs made
|
||||
# the final tree identical to master. This records dev as merged.
|
||||
if [ -f .git/MERGE_HEAD ]; then
|
||||
echo "Committing merge..."
|
||||
git commit -m "chore: sync dev to master"
|
||||
echo "Pushing..."
|
||||
git push origin master
|
||||
elif git diff --staged --quiet; then
|
||||
echo "No changes to commit (maybe dev is already merged or only protected docs changed)"
|
||||
else
|
||||
echo "Committing..."
|
||||
git commit -m "chore: sync dev to master"
|
||||
@@ -62,3 +92,127 @@ jobs:
|
||||
git push origin master
|
||||
fi
|
||||
|
||||
- name: Resolve Release Tag
|
||||
id: release_tag
|
||||
if: ${{ inputs.release_mode != 'sync_only' }}
|
||||
env:
|
||||
RELEASE_MODE: ${{ inputs.release_mode }}
|
||||
REQUESTED_VERSION: ${{ inputs.version }}
|
||||
CONFIRM_OVERWRITE: ${{ inputs.confirm_overwrite }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
git fetch --tags --force origin
|
||||
|
||||
REQUESTED_VERSION="${REQUESTED_VERSION:-}"
|
||||
REQUESTED_VERSION="${REQUESTED_VERSION#"${REQUESTED_VERSION%%[![:space:]]*}"}"
|
||||
REQUESTED_VERSION="${REQUESTED_VERSION%"${REQUESTED_VERSION##*[![:space:]]}"}"
|
||||
|
||||
if [ "$RELEASE_MODE" = "overwrite" ] && [ "${CONFIRM_OVERWRITE:-}" != "OVERWRITE" ]; then
|
||||
echo "::error::confirm_overwrite must be exactly 'OVERWRITE' when release_mode=overwrite."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -n "$REQUESTED_VERSION" ]; then
|
||||
TAG_NAME="$REQUESTED_VERSION"
|
||||
if [[ "$TAG_NAME" != v* ]]; then
|
||||
TAG_NAME="v$TAG_NAME"
|
||||
fi
|
||||
echo "Using manually requested release tag: $TAG_NAME"
|
||||
else
|
||||
LATEST_TAG="$(git tag --list 'v*.*.*' --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1 || true)"
|
||||
|
||||
if [ -z "$LATEST_TAG" ]; then
|
||||
if [ "$RELEASE_MODE" = "new" ]; then
|
||||
TAG_NAME="v0.1.0"
|
||||
echo "No existing vX.Y.Z tag found. Starting at $TAG_NAME"
|
||||
else
|
||||
echo "::error::No existing vX.Y.Z tag found to overwrite."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
if [ "$RELEASE_MODE" = "new" ]; then
|
||||
VERSION="${LATEST_TAG#v}"
|
||||
IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION"
|
||||
TAG_NAME="v${MAJOR}.${MINOR}.$((PATCH + 1))"
|
||||
echo "Auto bumped release tag from $LATEST_TAG to $TAG_NAME"
|
||||
else
|
||||
TAG_NAME="$LATEST_TAG"
|
||||
echo "No release tag requested. Using latest existing release tag: $TAG_NAME"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ ! "$TAG_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "::error::Invalid release tag '$TAG_NAME'. Use vX.Y.Z, for example v1.2.3."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$RELEASE_MODE" = "new" ]; then
|
||||
if git rev-parse -q --verify "refs/tags/$TAG_NAME" >/dev/null; then
|
||||
echo "::error::Tag '$TAG_NAME' already exists locally."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if git ls-remote --exit-code --tags origin "refs/tags/$TAG_NAME" >/dev/null 2>&1; then
|
||||
echo "::error::Tag '$TAG_NAME' already exists on origin."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
if ! git rev-parse -q --verify "refs/tags/$TAG_NAME" >/dev/null; then
|
||||
echo "::error::Tag '$TAG_NAME' does not exist locally."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! git ls-remote --exit-code --tags origin "refs/tags/$TAG_NAME" >/dev/null 2>&1; then
|
||||
echo "::error::Tag '$TAG_NAME' does not exist on origin."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TAG_COMMIT="$(git rev-list -n 1 "$TAG_NAME")"
|
||||
MASTER_COMMIT="$(git rev-parse HEAD)"
|
||||
echo "Overwrite release tag: $TAG_NAME"
|
||||
echo "Tag commit: $TAG_COMMIT"
|
||||
echo "Master commit used for rebuilt assets: $MASTER_COMMIT"
|
||||
fi
|
||||
|
||||
echo "tag_name=$TAG_NAME" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Create and Push Release Tag
|
||||
if: ${{ inputs.release_mode == 'new' }}
|
||||
env:
|
||||
TAG_NAME: ${{ steps.release_tag.outputs.tag_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
git tag -a "$TAG_NAME" -m "Release $TAG_NAME"
|
||||
git push origin "$TAG_NAME"
|
||||
|
||||
- name: Dispatch Overwrite Release Build
|
||||
if: ${{ inputs.release_mode == 'overwrite' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.PAT_TOKEN }}
|
||||
TAG_NAME: ${{ steps.release_tag.outputs.tag_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
gh workflow run build-dockerhub.yml \
|
||||
--ref master \
|
||||
-f release_tag="$TAG_NAME" \
|
||||
-f overwrite_existing_release=true
|
||||
|
||||
echo "Dispatched release rebuild for $TAG_NAME from master."
|
||||
|
||||
- name: Dispatch Sync-only Master Build
|
||||
if: ${{ inputs.release_mode == 'sync_only' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.PAT_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
MASTER_COMMIT="$(git rev-parse HEAD)"
|
||||
|
||||
gh workflow run build-dockerhub.yml \
|
||||
--ref master
|
||||
|
||||
echo "release_mode=sync_only; synced dev to master at $MASTER_COMMIT and dispatched a master build without creating or overwriting a release."
|
||||
|
||||
391
.github/workflows/sync-upstream-parser.yml
vendored
Normal file
391
.github/workflows/sync-upstream-parser.yml
vendored
Normal file
@@ -0,0 +1,391 @@
|
||||
name: Sync Upstream Parser
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 20 * * *"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
dry_run:
|
||||
description: "Plan/classify only; do not apply, test, commit, or push."
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
since_upstream_sha:
|
||||
description: "Optional upstream commit SHA to plan from for dry-run testing."
|
||||
required: false
|
||||
type: string
|
||||
max_commits:
|
||||
description: "Maximum upstream commits to inspect."
|
||||
required: false
|
||||
default: "30"
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
|
||||
concurrency:
|
||||
group: sync-upstream-parser-dev
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
# Scheduled workflows are read from the default branch, but parser syncs
|
||||
# must be planned, tested, and committed on dev.
|
||||
TARGET_BRANCH: dev
|
||||
UPSTREAM_REPO: https://github.com/asdlokj1qpi233/subconverter.git
|
||||
UPSTREAM_BRANCH: master
|
||||
SYNC_DRY_RUN: ${{ github.event.inputs.dry_run || 'false' }}
|
||||
SYNC_SINCE: ${{ github.event.inputs.since_upstream_sha || '' }}
|
||||
SYNC_MAX_COMMITS: ${{ github.event.inputs.max_commits || '30' }}
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout dev
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ env.TARGET_BRANCH }}
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.PAT_TOKEN || github.token }}
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Fetch upstream
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git remote add upstream "$UPSTREAM_REPO" 2>/dev/null || git remote set-url upstream "$UPSTREAM_REPO"
|
||||
git fetch --no-tags upstream "$UPSTREAM_BRANCH"
|
||||
|
||||
- name: Check protected integrations before sync
|
||||
run: python3 scripts/check_sync_guards.py
|
||||
|
||||
- name: Plan upstream parser sync
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -n "$SYNC_SINCE" ] && [ "$SYNC_DRY_RUN" != "true" ]; then
|
||||
echo "::error::since_upstream_sha is allowed only when dry_run=true."
|
||||
exit 1
|
||||
fi
|
||||
PLAN_ARGS=(
|
||||
plan
|
||||
--upstream-ref "upstream/$UPSTREAM_BRANCH"
|
||||
--max-commits "$SYNC_MAX_COMMITS"
|
||||
--output upstream-sync-candidates.json
|
||||
--report upstream-sync-plan.md
|
||||
)
|
||||
if [ -n "$SYNC_SINCE" ]; then
|
||||
PLAN_ARGS+=(--since "$SYNC_SINCE")
|
||||
fi
|
||||
python3 scripts/sync_upstream_parser.py "${PLAN_ARGS[@]}"
|
||||
|
||||
- name: Inspect sync plan
|
||||
id: plan
|
||||
run: |
|
||||
set -euo pipefail
|
||||
python3 - <<'PY' >> "$GITHUB_OUTPUT"
|
||||
import json
|
||||
data = json.load(open("upstream-sync-candidates.json", encoding="utf-8"))
|
||||
candidates = data.get("candidates", [])
|
||||
reviewable = [item for item in candidates if item.get("reviewable_by_ai")]
|
||||
print(f"candidate_count={len(candidates)}")
|
||||
print(f"reviewable_count={len(reviewable)}")
|
||||
print(f"has_candidates={'true' if candidates else 'false'}")
|
||||
print(f"has_reviewable={'true' if reviewable else 'false'}")
|
||||
PY
|
||||
|
||||
- name: Write sync summary
|
||||
if: always()
|
||||
run: |
|
||||
python3 - <<'PY' >> "$GITHUB_STEP_SUMMARY"
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
print("## Upstream Parser Sync")
|
||||
print("")
|
||||
print(f"- Workflow ref: `{os.environ.get('GITHUB_REF_NAME', '')}`")
|
||||
print(f"- Target branch: `{os.environ.get('TARGET_BRANCH', '')}`")
|
||||
print(f"- Dry run: `{os.environ.get('SYNC_DRY_RUN', 'false')}`")
|
||||
print(f"- Manual since override: `{os.environ.get('SYNC_SINCE') or 'none'}`")
|
||||
print("")
|
||||
|
||||
plan_path = Path("upstream-sync-candidates.json")
|
||||
if not plan_path.exists():
|
||||
print("Plan file was not generated.")
|
||||
raise SystemExit(0)
|
||||
|
||||
data = json.loads(plan_path.read_text(encoding="utf-8"))
|
||||
candidates = data.get("candidates", [])
|
||||
rule_safe = [item for item in candidates if item.get("safe_by_rules")]
|
||||
reviewable = [item for item in candidates if item.get("reviewable_by_ai")]
|
||||
print(f"- Cursor file: `{data.get('cursor_file') or 'unknown'}`")
|
||||
print(f"- Seen: `{data.get('seen') or 'none'}`")
|
||||
print(f"- Stored seen: `{data.get('stored_seen') or 'none'}`")
|
||||
print(f"- Upstream head: `{data.get('upstream_head') or 'unknown'}`")
|
||||
print(f"- Total pending commits: `{data.get('total_commit_count', 0)}`")
|
||||
print(f"- Candidate commits: `{len(candidates)}`")
|
||||
print(f"- Rule-safe candidates: `{len(rule_safe)}`")
|
||||
print(f"- AI-reviewable candidates: `{len(reviewable)}`")
|
||||
print(f"- Truncated batch: `{data.get('truncated', False)}`")
|
||||
print(f"- Planned cursor advance: `{data.get('advance_to') or 'none'}`")
|
||||
if not candidates:
|
||||
print("")
|
||||
print("No upstream commits are pending for the current seen marker.")
|
||||
PY
|
||||
|
||||
- name: Check Copilot token
|
||||
id: copilot_token
|
||||
if: steps.plan.outputs.has_reviewable == 'true'
|
||||
env:
|
||||
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
|
||||
run: |
|
||||
if [ -n "${COPILOT_GITHUB_TOKEN:-}" ]; then
|
||||
echo "available=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "available=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Set up Node.js
|
||||
if: steps.plan.outputs.has_reviewable == 'true' && steps.copilot_token.outputs.available == 'true'
|
||||
uses: actions/setup-node@v6
|
||||
|
||||
- name: Ask Copilot to classify parser candidates
|
||||
id: copilot_classify
|
||||
if: steps.plan.outputs.has_reviewable == 'true' && steps.copilot_token.outputs.available == 'true'
|
||||
continue-on-error: true
|
||||
env:
|
||||
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
npm install -g @github/copilot
|
||||
|
||||
PROMPT="$(cat <<'EOF'
|
||||
You are reviewing upstream parser changes for SubConverter-Extended.
|
||||
|
||||
The project has protected Mihomo/proxy-provider integrations. Only
|
||||
parser whitelist paths will ever be applied automatically. A change may
|
||||
be approved only when applying just those parser paths is low risk and
|
||||
does not require output adapter changes or other non-whitelisted files.
|
||||
|
||||
Return only strict JSON. Do not use Markdown.
|
||||
|
||||
Required schema:
|
||||
{
|
||||
"decisions": [
|
||||
{
|
||||
"sha": "full commit sha from the plan",
|
||||
"decision": "safe_parser_only | needs_output_adapter | touches_protected_area | unsafe | skip",
|
||||
"risk": "low | medium | high",
|
||||
"reason": "short explanation",
|
||||
"required_tests": ["test names"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Use "needs_output_adapter" when the parser changes depend on companion
|
||||
output changes, schema changes outside the whitelist, or generated
|
||||
config changes. Only use "safe_parser_only" with "low" risk when the
|
||||
whitelisted parser diff can be safely applied by itself without touching
|
||||
Mihomo parser bridge, RawParams pass-through, proxy-provider logic,
|
||||
FetchContext handling, nodemanip.cpp, or Clash output internals.
|
||||
EOF
|
||||
)"
|
||||
|
||||
PROMPT="$PROMPT
|
||||
|
||||
$(cat upstream-sync-candidates.json)"
|
||||
|
||||
copilot -p "$PROMPT" --no-ask-user > upstream-sync-decisions.raw
|
||||
|
||||
python3 - <<'PY'
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
raw = Path("upstream-sync-decisions.raw").read_text(encoding="utf-8")
|
||||
start = raw.find("{")
|
||||
end = raw.rfind("}")
|
||||
if start == -1 or end == -1 or end < start:
|
||||
raise SystemExit("Copilot did not return a JSON object.")
|
||||
data = json.loads(raw[start:end + 1])
|
||||
if "decisions" not in data or not isinstance(data["decisions"], list):
|
||||
raise SystemExit("Copilot JSON is missing decisions array.")
|
||||
Path("upstream-sync-decisions.json").write_text(
|
||||
json.dumps(data, indent=2, ensure_ascii=False) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
PY
|
||||
|
||||
- name: Create empty decisions for non-reviewable plan
|
||||
if: steps.plan.outputs.has_candidates == 'true' && steps.plan.outputs.has_reviewable != 'true'
|
||||
run: printf '%s\n' '{"decisions":[]}' > upstream-sync-decisions.json
|
||||
|
||||
- name: Apply approved upstream parser updates
|
||||
id: apply
|
||||
if: env.SYNC_DRY_RUN != 'true' && steps.plan.outputs.has_candidates == 'true' && (steps.plan.outputs.has_reviewable != 'true' || steps.copilot_classify.outcome == 'success')
|
||||
run: |
|
||||
set -euo pipefail
|
||||
python3 scripts/sync_upstream_parser.py apply \
|
||||
--plan upstream-sync-candidates.json \
|
||||
--decisions upstream-sync-decisions.json \
|
||||
--result upstream-sync-result.json \
|
||||
--report upstream-sync-result.md
|
||||
|
||||
python3 - <<'PY' >> "$GITHUB_OUTPUT"
|
||||
import json
|
||||
data = json.load(open("upstream-sync-result.json", encoding="utf-8"))
|
||||
print(f"applied_count={len(data.get('applied', []))}")
|
||||
print(f"skipped_count={len(data.get('skipped', []))}")
|
||||
print(f"has_applied={'true' if data.get('applied') else 'false'}")
|
||||
print(f"has_skipped={'true' if data.get('skipped') else 'false'}")
|
||||
PY
|
||||
|
||||
- name: Skip apply in dry run
|
||||
if: env.SYNC_DRY_RUN == 'true'
|
||||
run: echo "Dry run enabled; skipping apply, smoke tests, commit, and push."
|
||||
|
||||
- name: Check protected integrations after sync
|
||||
if: steps.apply.outcome == 'success'
|
||||
run: python3 scripts/check_sync_guards.py
|
||||
|
||||
- name: Determine changed source files
|
||||
id: changes
|
||||
if: steps.apply.outcome == 'success'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
changed_files="$(
|
||||
{
|
||||
git diff --name-only
|
||||
git diff --cached --name-only
|
||||
} | sort -u
|
||||
)"
|
||||
|
||||
if [ -z "$changed_files" ]; then
|
||||
echo "has_changes=false" >> "$GITHUB_OUTPUT"
|
||||
echo "has_source_changes=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "has_changes=true" >> "$GITHUB_OUTPUT"
|
||||
if printf '%s\n' "$changed_files" | grep -Eq '^(src|bridge|include|CMakeLists\.txt|Dockerfile|docker/Dockerfile)'; then
|
||||
echo "has_source_changes=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "has_source_changes=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Build and smoke test source changes
|
||||
if: steps.changes.outputs.has_source_changes == 'true'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
docker build --target builder -t subconverter-upstream-sync-test --build-arg THREADS=2 .
|
||||
|
||||
CID="$(docker run -d -e PORT=25500 -p '127.0.0.1::25500' subconverter-upstream-sync-test /src/subconverter -f /src/base/pref.example.toml)"
|
||||
trap 'docker rm -f "$CID" >/dev/null 2>&1 || true' EXIT
|
||||
|
||||
HOST_PORT="$(docker port "$CID" 25500/tcp | awk -F: 'END {print $NF}')"
|
||||
BASE_URL="http://127.0.0.1:${HOST_PORT}"
|
||||
|
||||
for i in $(seq 1 60); do
|
||||
if curl -fsS "$BASE_URL/version" >/tmp/version.out; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
curl -fsS "$BASE_URL/version" >/tmp/version.out
|
||||
|
||||
curl -fsS "$BASE_URL/sub?target=clash&url=https%3A%2F%2Fexample.com%2Fsub" > /tmp/clash-provider.yml
|
||||
grep -q "proxy-providers:" /tmp/clash-provider.yml
|
||||
|
||||
curl -fsS "$BASE_URL/sub?target=clash&url=ss%3A%2F%2FY2hhY2hhMjAtaWV0Zi1wb2x5MTMwNTpwYXNzd29yZA%40www.example.com%3A1080%23Example" > /tmp/clash-node.yml
|
||||
grep -Eq "type: ss|type: \"ss\"" /tmp/clash-node.yml
|
||||
|
||||
curl -fsS "$BASE_URL/sub?target=singbox&url=ss%3A%2F%2FY2hhY2hhMjAtaWV0Zi1wb2x5MTMwNTpwYXNzd29yZA%40www.example.com%3A1080%23Example" > /tmp/singbox.json
|
||||
python3 -m json.tool /tmp/singbox.json >/dev/null
|
||||
|
||||
curl -fsS "$BASE_URL/sub?target=surge&url=ss%3A%2F%2FY2hhY2hhMjAtaWV0Zi1wb2x5MTMwNTpwYXNzd29yZA%40www.example.com%3A1080%23Example" > /tmp/surge.conf
|
||||
test -s /tmp/surge.conf
|
||||
|
||||
- name: Commit sync result to dev
|
||||
if: steps.changes.outputs.has_changes == 'true'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git add \
|
||||
.github/upstream-subconverter.seen \
|
||||
.github/upstream-subconverter.applied.json \
|
||||
.github/upstream-subconverter.skipped.json \
|
||||
src/parser/subparser.cpp \
|
||||
src/parser/subparser.h \
|
||||
src/parser/config/proxy.h
|
||||
|
||||
if git diff --staged --quiet; then
|
||||
echo "No staged sync changes."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "${{ steps.apply.outputs.has_applied }}" = "true" ]; then
|
||||
git commit -m "chore(parser): sync upstream parser updates"
|
||||
else
|
||||
git commit -m "chore(parser): update upstream sync marker [skip ci]"
|
||||
fi
|
||||
|
||||
git fetch --no-tags origin "refs/heads/$TARGET_BRANCH:refs/remotes/origin/$TARGET_BRANCH"
|
||||
git rebase "origin/$TARGET_BRANCH"
|
||||
git push origin "HEAD:refs/heads/$TARGET_BRANCH"
|
||||
|
||||
- name: Upload sync reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: upstream-parser-sync-report
|
||||
path: |
|
||||
upstream-sync-candidates.json
|
||||
upstream-sync-decisions.raw
|
||||
upstream-sync-decisions.json
|
||||
upstream-sync-plan.md
|
||||
upstream-sync-result.json
|
||||
upstream-sync-result.md
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Report sync items that need attention
|
||||
if: always() && env.SYNC_DRY_RUN != 'true' && (steps.apply.outputs.has_skipped == 'true' || (steps.plan.outputs.has_reviewable == 'true' && (steps.copilot_token.outputs.available != 'true' || steps.copilot_classify.outcome != 'success')))
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const title = 'Upstream parser sync requires attention';
|
||||
const marker = '<!-- upstream-parser-sync-attention -->';
|
||||
let body = `${marker}\nAutomated upstream parser sync on \`${process.env.TARGET_BRANCH}\` needs attention.\n\n`;
|
||||
|
||||
if (fs.existsSync('upstream-sync-result.md')) {
|
||||
body += fs.readFileSync('upstream-sync-result.md', 'utf8');
|
||||
} else if (fs.existsSync('upstream-sync-plan.md')) {
|
||||
body += fs.readFileSync('upstream-sync-plan.md', 'utf8');
|
||||
body += '\n\nCopilot classification was unavailable or failed, so reviewable candidates were not applied.\n';
|
||||
}
|
||||
|
||||
const {data: issues} = await github.rest.issues.listForRepo({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: 'open',
|
||||
labels: undefined,
|
||||
per_page: 50
|
||||
});
|
||||
const existing = issues.find(issue => issue.title === title);
|
||||
if (existing) {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: existing.number,
|
||||
body
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.create({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
title,
|
||||
body
|
||||
});
|
||||
}
|
||||
25
.github/workflows/watch-mihomo-meta.yml
vendored
25
.github/workflows/watch-mihomo-meta.yml
vendored
@@ -8,24 +8,34 @@ on:
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: watch-mihomo-meta-dev
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
# Scheduled workflows run from the default branch, but the marker belongs to dev.
|
||||
TARGET_BRANCH: dev
|
||||
MIHOMO_REPO: https://github.com/MetaCubeX/mihomo.git
|
||||
MARKER_FILE: .github/mihomo-meta.rev
|
||||
|
||||
jobs:
|
||||
watch:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout dev
|
||||
- name: Checkout target branch
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: dev
|
||||
ref: ${{ env.TARGET_BRANCH }}
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.PAT_TOKEN || github.token }}
|
||||
|
||||
- name: Check Meta updates
|
||||
env:
|
||||
MIHOMO_REPO: https://github.com/MetaCubeX/mihomo.git
|
||||
MARKER_FILE: .github/mihomo-meta.rev
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
echo "Workflow ref: ${GITHUB_REF}"
|
||||
echo "Tracking Meta revision on ${TARGET_BRANCH}"
|
||||
|
||||
latest=$(git ls-remote "$MIHOMO_REPO" Meta | awk '{print $1}')
|
||||
if [ -z "$latest" ]; then
|
||||
echo "Failed to resolve Meta HEAD"
|
||||
@@ -77,4 +87,7 @@ jobs:
|
||||
fi
|
||||
|
||||
git commit -m "$msg" || exit 0
|
||||
git push origin HEAD:dev
|
||||
|
||||
git fetch --no-tags origin "refs/heads/$TARGET_BRANCH:refs/remotes/origin/$TARGET_BRANCH"
|
||||
git rebase "origin/$TARGET_BRANCH"
|
||||
git push origin "HEAD:refs/heads/$TARGET_BRANCH"
|
||||
|
||||
13
.gitignore
vendored
13
.gitignore
vendored
@@ -8,4 +8,15 @@ scripts/yaml-cpp
|
||||
.DS_Store
|
||||
src/.DS_Store
|
||||
|
||||
buildkeys/
|
||||
build
|
||||
bridge/libmihomo.a
|
||||
bridge/libmihomo.so
|
||||
bridge/libmihomo.dll
|
||||
bridge/libmihomo.dll.a
|
||||
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
upstream-sync-*.json
|
||||
upstream-sync-*.md
|
||||
upstream-sync-decisions.raw
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
CMAKE_MINIMUM_REQUIRED(VERSION 3.5)
|
||||
PROJECT(subconverter LANGUAGES CXX)
|
||||
SET(BUILD_TARGET_NAME ${PROJECT_NAME})
|
||||
CMAKE_MINIMUM_REQUIRED(VERSION 3.5)
|
||||
SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/")
|
||||
INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/include/")
|
||||
|
||||
@@ -42,6 +42,7 @@ ENDIF()
|
||||
# Suppress warnings from third-party libraries
|
||||
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
||||
add_compile_options(-Wno-array-bounds)
|
||||
add_compile_options(-Wno-unused-but-set-parameter)
|
||||
endif()
|
||||
|
||||
IF(NOT MSVC)
|
||||
@@ -71,9 +72,15 @@ ADD_EXECUTABLE(${BUILD_TARGET_NAME}
|
||||
src/generator/config/ruleconvert.cpp
|
||||
src/generator/config/subexport.cpp
|
||||
src/generator/template/templates.cpp
|
||||
src/handler/dashboard_auth.cpp
|
||||
src/handler/dashboard_page.cpp
|
||||
src/handler/inspect_page.cpp
|
||||
src/handler/interfaces.cpp
|
||||
src/handler/webapp_page.cpp
|
||||
src/handler/multithread.cpp
|
||||
src/handler/statistics.cpp
|
||||
src/handler/upload.cpp
|
||||
src/handler/version_page.cpp
|
||||
src/handler/webget.cpp
|
||||
src/handler/settings.cpp
|
||||
src/main.cpp
|
||||
@@ -114,7 +121,7 @@ FIND_PACKAGE(CURL 7.54.0 REQUIRED)
|
||||
TARGET_LINK_DIRECTORIES(${BUILD_TARGET_NAME} PRIVATE ${CURL_LIBRARY_DIRS})
|
||||
TARGET_INCLUDE_DIRECTORIES(${BUILD_TARGET_NAME} PRIVATE ${CURL_INCLUDE_DIRS})
|
||||
TARGET_LINK_LIBRARIES(${BUILD_TARGET_NAME} CURL::libcurl)
|
||||
TARGET_COMPILE_DEFINITIONS(${BUILD_TARGET_NAME} PRIVATE -DCURL_STATICLIB)
|
||||
TARGET_COMPILE_DEFINITIONS(${BUILD_TARGET_NAME} PRIVATE CURL_STATICLIB)
|
||||
|
||||
FIND_PACKAGE(Rapidjson REQUIRED)
|
||||
TARGET_INCLUDE_DIRECTORIES(${BUILD_TARGET_NAME} PRIVATE ${RAPIDJSON_INCLUDE_DIRS})
|
||||
@@ -127,16 +134,28 @@ FIND_LIBRARY(YAML_CPP_LIBRARY NAMES yaml-cpp yaml-cppd PATHS ${YAML_CPP_LIBRARY_
|
||||
TARGET_LINK_DIRECTORIES(${BUILD_TARGET_NAME} PRIVATE ${YAML_CPP_LIBRARY_DIRS})
|
||||
TARGET_INCLUDE_DIRECTORIES(${BUILD_TARGET_NAME} PRIVATE ${YAML_CPP_INCLUDE_DIRS})
|
||||
TARGET_LINK_LIBRARIES(${BUILD_TARGET_NAME} ${YAML_CPP_LIBRARY})
|
||||
TARGET_COMPILE_DEFINITIONS(${BUILD_TARGET_NAME} PRIVATE -DYAML_CPP_STATIC_DEFINE)
|
||||
TARGET_COMPILE_DEFINITIONS(${BUILD_TARGET_NAME} PRIVATE YAML_CPP_STATIC_DEFINE)
|
||||
|
||||
FIND_PACKAGE(PCRE2 REQUIRED)
|
||||
TARGET_INCLUDE_DIRECTORIES(${BUILD_TARGET_NAME} PRIVATE ${PCRE2_INCLUDE_DIRS})
|
||||
TARGET_LINK_LIBRARIES(${BUILD_TARGET_NAME} ${PCRE2_LIBRARY})
|
||||
TARGET_COMPILE_DEFINITIONS(${BUILD_TARGET_NAME} PRIVATE -DPCRE2_STATIC)
|
||||
TARGET_COMPILE_DEFINITIONS(${BUILD_TARGET_NAME} PRIVATE PCRE2_STATIC)
|
||||
|
||||
FIND_PACKAGE(QuickJS REQUIRED)
|
||||
TARGET_INCLUDE_DIRECTORIES(${BUILD_TARGET_NAME} PRIVATE ${QUICKJS_INCLUDE_DIRS})
|
||||
TARGET_LINK_LIBRARIES(${BUILD_TARGET_NAME} ${QUICKJS_LIBRARIES})
|
||||
SET(CMAKE_REQUIRED_INCLUDES ${QUICKJS_INCLUDE_DIRS})
|
||||
CHECK_CXX_SOURCE_COMPILES(
|
||||
"
|
||||
#include <quickjs/quickjs.h>
|
||||
int main(){ return sizeof(&JS_SetHostUnhandledPromiseRejectionTracker) > 0 ? 0 : 1; }
|
||||
" HAVE_QUICKJS_HOST_UNHANDLED_PROMISE_REJECTION_TRACKER)
|
||||
UNSET(CMAKE_REQUIRED_INCLUDES)
|
||||
IF(HAVE_QUICKJS_HOST_UNHANDLED_PROMISE_REJECTION_TRACKER)
|
||||
TARGET_COMPILE_DEFINITIONS(${BUILD_TARGET_NAME} PRIVATE QUICKJS_HAS_HOST_UNHANDLED_PROMISE_REJECTION_TRACKER)
|
||||
ELSE()
|
||||
TARGET_COMPILE_DEFINITIONS(${BUILD_TARGET_NAME} PRIVATE JS_SetHostUnhandledPromiseRejectionTracker=JS_SetHostPromiseRejectionTracker)
|
||||
ENDIF()
|
||||
|
||||
FIND_PACKAGE(LibCron REQUIRED)
|
||||
TARGET_INCLUDE_DIRECTORIES(${BUILD_TARGET_NAME} PRIVATE ${LIBCRON_INCLUDE_DIRS})
|
||||
@@ -148,7 +167,7 @@ TARGET_LINK_LIBRARIES(${BUILD_TARGET_NAME} ${LIBCRON_LIBRARIES})
|
||||
IF(EXISTS "${CMAKE_SOURCE_DIR}/bridge/libmihomo.so")
|
||||
MESSAGE(STATUS "Found mihomo Go shared library (.so), enabling native parser support")
|
||||
TARGET_INCLUDE_DIRECTORIES(${BUILD_TARGET_NAME} PRIVATE "${CMAKE_SOURCE_DIR}/bridge")
|
||||
TARGET_COMPILE_DEFINITIONS(${BUILD_TARGET_NAME} PRIVATE -DUSE_MIHOMO_PARSER)
|
||||
TARGET_COMPILE_DEFINITIONS(${BUILD_TARGET_NAME} PRIVATE USE_MIHOMO_PARSER)
|
||||
|
||||
# Link pthread (required by Go runtime on Linux)
|
||||
IF(UNIX AND NOT APPLE)
|
||||
@@ -161,7 +180,7 @@ IF(EXISTS "${CMAKE_SOURCE_DIR}/bridge/libmihomo.so")
|
||||
ELSEIF(EXISTS "${CMAKE_SOURCE_DIR}/bridge/libmihomo.a")
|
||||
MESSAGE(STATUS "Found mihomo Go static library (.a), enabling native parser support")
|
||||
TARGET_INCLUDE_DIRECTORIES(${BUILD_TARGET_NAME} PRIVATE "${CMAKE_SOURCE_DIR}/bridge")
|
||||
TARGET_COMPILE_DEFINITIONS(${BUILD_TARGET_NAME} PRIVATE -DUSE_MIHOMO_PARSER)
|
||||
TARGET_COMPILE_DEFINITIONS(${BUILD_TARGET_NAME} PRIVATE USE_MIHOMO_PARSER)
|
||||
|
||||
# Link pthread BEFORE Go library (required by Go runtime)
|
||||
IF(UNIX AND NOT APPLE)
|
||||
@@ -209,7 +228,7 @@ ADD_LIBRARY(${BUILD_TARGET_NAME} STATIC
|
||||
src/utils/regexp.cpp
|
||||
src/utils/string.cpp
|
||||
src/utils/urlencode.cpp)
|
||||
TARGET_COMPILE_DEFINITIONS(${BUILD_TARGET_NAME} PRIVATE -DNO_JS_RUNTIME -DNO_WEBGET)
|
||||
TARGET_COMPILE_DEFINITIONS(${BUILD_TARGET_NAME} PRIVATE NO_JS_RUNTIME NO_WEBGET)
|
||||
|
||||
TARGET_INCLUDE_DIRECTORIES(${BUILD_TARGET_NAME} PUBLIC src)
|
||||
|
||||
@@ -223,12 +242,12 @@ FIND_LIBRARY(YAML_CPP_LIBRARY NAMES yaml-cpp yaml-cppd PATHS ${YAML_CPP_LIBRARY_
|
||||
TARGET_LINK_DIRECTORIES(${BUILD_TARGET_NAME} PRIVATE ${YAML_CPP_LIBRARY_DIRS})
|
||||
TARGET_INCLUDE_DIRECTORIES(${BUILD_TARGET_NAME} PRIVATE ${YAML_CPP_INCLUDE_DIRS})
|
||||
TARGET_LINK_LIBRARIES(${BUILD_TARGET_NAME} PRIVATE ${YAML_CPP_LIBRARY})
|
||||
TARGET_COMPILE_DEFINITIONS(${BUILD_TARGET_NAME} PRIVATE -DYAML_CPP_STATIC_DEFINE)
|
||||
TARGET_COMPILE_DEFINITIONS(${BUILD_TARGET_NAME} PRIVATE YAML_CPP_STATIC_DEFINE)
|
||||
|
||||
FIND_PACKAGE(PCRE2 REQUIRED)
|
||||
TARGET_INCLUDE_DIRECTORIES(${BUILD_TARGET_NAME} PRIVATE ${PCRE2_INCLUDE_DIRS})
|
||||
TARGET_LINK_LIBRARIES(${BUILD_TARGET_NAME} PRIVATE ${PCRE2_LIBRARY})
|
||||
TARGET_COMPILE_DEFINITIONS(${BUILD_TARGET_NAME} PRIVATE -DPCRE2_STATIC)
|
||||
TARGET_COMPILE_DEFINITIONS(${BUILD_TARGET_NAME} PRIVATE PCRE2_STATIC)
|
||||
|
||||
IF(WIN32)
|
||||
TARGET_LINK_LIBRARIES(${BUILD_TARGET_NAME} PRIVATE ws2_32)
|
||||
@@ -237,9 +256,9 @@ ENDIF()
|
||||
ENDIF() #BUILD_STATIC_LIBRARY
|
||||
|
||||
IF(HAVE_TO_STRING)
|
||||
TARGET_COMPILE_DEFINITIONS(${BUILD_TARGET_NAME} PRIVATE -DHAVE_TO_STRING)
|
||||
TARGET_COMPILE_DEFINITIONS(${BUILD_TARGET_NAME} PRIVATE HAVE_TO_STRING)
|
||||
ENDIF()
|
||||
|
||||
IF(USING_MALLOC_TRIM)
|
||||
TARGET_COMPILE_DEFINITIONS(${BUILD_TARGET_NAME} PRIVATE -DMALLOC_TRIM)
|
||||
TARGET_COMPILE_DEFINITIONS(${BUILD_TARGET_NAME} PRIVATE MALLOC_TRIM)
|
||||
ENDIF()
|
||||
|
||||
122
Dockerfile
122
Dockerfile
@@ -1,11 +1,12 @@
|
||||
# ========== GO BUILD STAGE ==========
|
||||
# 使用 glibc (Debian) 构建 Go 共享库,避免 musl 下 Go runtime 初始化崩溃
|
||||
FROM golang:latest AS go-builder
|
||||
FROM mirror.gcr.io/library/golang:latest AS go-builder
|
||||
|
||||
ARG TARGETARCH
|
||||
ARG TARGETVARIANT
|
||||
ARG MIHOMO_REF="Meta"
|
||||
ARG MIHOMO_CACHE_BUST=1
|
||||
ARG REFRESH_GO_DEPS=false
|
||||
|
||||
WORKDIR /build/bridge
|
||||
|
||||
@@ -14,21 +15,20 @@ RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends git build-essential && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy Go source code FIRST (needed for dependency analysis)
|
||||
# Copy committed Go module files and source.
|
||||
COPY bridge/go.mod bridge/go.sum ./
|
||||
COPY bridge/converter.go ./
|
||||
COPY bridge/preprocess.go ./
|
||||
|
||||
# Initialize new go.mod dynamically
|
||||
RUN go mod init github.com/aethersailor/subconverter-extended/bridge
|
||||
|
||||
# Get latest Mihomo and resolve all dependencies
|
||||
RUN echo "MIHOMO_CACHE_BUST=$MIHOMO_CACHE_BUST" && \
|
||||
go get github.com/metacubex/mihomo@${MIHOMO_REF}
|
||||
|
||||
# Upgrade all dependencies to latest versions (security fix)
|
||||
RUN go get -u all
|
||||
|
||||
# Tidy dependencies (auto-resolves transitive deps)
|
||||
RUN go mod tidy
|
||||
RUN set -xe && \
|
||||
if [ "${REFRESH_GO_DEPS}" = "true" ]; then \
|
||||
echo "MIHOMO_CACHE_BUST=$MIHOMO_CACHE_BUST" && \
|
||||
go get github.com/metacubex/mihomo@${MIHOMO_REF} && \
|
||||
go get -u all && \
|
||||
go mod tidy; \
|
||||
else \
|
||||
go mod download; \
|
||||
fi
|
||||
|
||||
# Copy scripts for scheme generation
|
||||
COPY scripts/ ../scripts/
|
||||
@@ -53,11 +53,13 @@ RUN ls -lh libmihomo.so libmihomo.h
|
||||
|
||||
# ========== C++ BUILD STAGE ==========
|
||||
# 使用 Debian (glibc) 编译,运行时再搬运依赖到 Alpine
|
||||
FROM debian:latest AS builder
|
||||
FROM mirror.gcr.io/library/debian:latest AS builder
|
||||
ARG THREADS="4"
|
||||
ARG SHA=""
|
||||
ARG VERSION="dev"
|
||||
ARG BUILD_DATE=""
|
||||
ARG REFRESH_HEADERS=false
|
||||
ARG SOURCE_DEPS_CACHE_BUST=stable
|
||||
|
||||
WORKDIR /
|
||||
|
||||
@@ -72,9 +74,9 @@ RUN apt-get update && \
|
||||
|
||||
# quickjspp
|
||||
RUN set -xe && \
|
||||
git clone --depth=1 https://github.com/ftk/quickjspp.git && \
|
||||
echo "SOURCE_DEPS_CACHE_BUST=${SOURCE_DEPS_CACHE_BUST}" && \
|
||||
git clone --depth=1 --recurse-submodules --shallow-submodules https://github.com/ftk/quickjspp.git quickjspp && \
|
||||
cd quickjspp && \
|
||||
git submodule update --init && \
|
||||
cmake -DCMAKE_BUILD_TYPE=Release . && \
|
||||
make quickjs -j ${THREADS} && \
|
||||
install -d /usr/lib/quickjs/ && \
|
||||
@@ -85,9 +87,9 @@ RUN set -xe && \
|
||||
|
||||
# libcron
|
||||
RUN set -xe && \
|
||||
git clone https://github.com/PerMalmberg/libcron --depth=1 && \
|
||||
echo "SOURCE_DEPS_CACHE_BUST=${SOURCE_DEPS_CACHE_BUST}" && \
|
||||
git clone --depth=1 --recurse-submodules --shallow-submodules https://github.com/PerMalmberg/libcron.git libcron && \
|
||||
cd libcron && \
|
||||
git submodule update --init && \
|
||||
cmake -DCMAKE_BUILD_TYPE=Release . && \
|
||||
make libcron -j ${THREADS} && \
|
||||
install -m644 libcron/out/Release/liblibcron.a /usr/lib/ && \
|
||||
@@ -96,9 +98,9 @@ RUN set -xe && \
|
||||
install -d /usr/include/date/ && \
|
||||
install -m644 libcron/externals/date/include/date/* /usr/include/date/
|
||||
|
||||
# toml11 (跟随默认分支最新版本)
|
||||
RUN set -xe && \
|
||||
git clone https://github.com/ToruNiina/toml11 --depth=1 && \
|
||||
echo "SOURCE_DEPS_CACHE_BUST=${SOURCE_DEPS_CACHE_BUST}" && \
|
||||
git clone --depth=1 https://github.com/ToruNiina/toml11.git toml11 && \
|
||||
cd toml11 && \
|
||||
cmake -DCMAKE_CXX_STANDARD=11 . && \
|
||||
make install -j ${THREADS}
|
||||
@@ -109,25 +111,35 @@ COPY --from=go-builder /build/bridge/libmihomo.h /usr/include/
|
||||
COPY --from=go-builder /build/bridge/go.mod /src/bridge/go.mod
|
||||
COPY --from=go-builder /build/bridge/go.sum /src/bridge/go.sum
|
||||
|
||||
# build subconverter from THIS repository source
|
||||
# build SubConverter-Extended from THIS repository source
|
||||
WORKDIR /src
|
||||
COPY . /src
|
||||
COPY --from=go-builder /build/bridge/mihomo_schemes.h /src/src/parser/mihomo_schemes.h
|
||||
COPY --from=go-builder /build/bridge/param_compat.h /src/src/parser/param_compat.h
|
||||
|
||||
# Download latest header-only libraries
|
||||
RUN set -xe && \
|
||||
echo "Downloading latest cpp-httplib..." && \
|
||||
curl -fsSL https://raw.githubusercontent.com/yhirose/cpp-httplib/master/httplib.h -o include/httplib.h && \
|
||||
echo "Downloading latest nlohmann/json..." && \
|
||||
curl -fsSL https://github.com/nlohmann/json/releases/latest/download/json.hpp -o include/nlohmann/json.hpp && \
|
||||
echo "Downloading latest inja..." && \
|
||||
curl -fsSL https://raw.githubusercontent.com/pantor/inja/master/single_include/inja/inja.hpp -o include/inja.hpp && \
|
||||
echo "Downloading latest jpcre2..." && \
|
||||
curl -fsSL https://raw.githubusercontent.com/jpcre2/jpcre2/master/src/jpcre2.hpp -o include/jpcre2.hpp && \
|
||||
echo "Copying latest quickjspp from compiled source..." && \
|
||||
cp /usr/include/quickjspp.hpp include/quickjspp.hpp && \
|
||||
echo "All header libraries updated to latest versions"
|
||||
if [ "${REFRESH_HEADERS}" = "true" ]; then \
|
||||
echo "Downloading latest cpp-httplib..." && \
|
||||
curl -fsSL https://raw.githubusercontent.com/yhirose/cpp-httplib/master/httplib.h -o include/httplib.h && \
|
||||
echo "Downloading latest nlohmann/json..." && \
|
||||
curl -fsSL https://github.com/nlohmann/json/releases/latest/download/json.hpp -o include/nlohmann/json.hpp && \
|
||||
echo "Downloading latest inja..." && \
|
||||
curl -fsSL https://raw.githubusercontent.com/pantor/inja/master/single_include/inja/inja.hpp -o include/inja.hpp && \
|
||||
echo "Downloading latest jpcre2..." && \
|
||||
curl -fsSL https://raw.githubusercontent.com/jpcre2/jpcre2/master/src/jpcre2.hpp -o include/jpcre2.hpp && \
|
||||
echo "Copying latest quickjspp from compiled source..." && \
|
||||
cp /usr/include/quickjspp.hpp include/quickjspp.hpp && \
|
||||
echo "Copying latest libcron headers from compiled source..." && \
|
||||
rm -rf include/libcron include/date && \
|
||||
cp -a /libcron/libcron/include/libcron include/libcron && \
|
||||
cp -a /libcron/libcron/externals/date/include/date include/date && \
|
||||
echo "Copying latest toml11 headers from compiled source..." && \
|
||||
rm -rf include/toml11 && \
|
||||
cp /toml11/include/toml.hpp include/toml.hpp && \
|
||||
cp -a /toml11/include/toml11 include/toml11; \
|
||||
else \
|
||||
echo "Using committed header libraries"; \
|
||||
fi
|
||||
|
||||
RUN set -xe && \
|
||||
[ -n "${SHA}" ] && sed -i "s/#define BUILD_ID \"\"/#define BUILD_ID \"${SHA}\"/ " src/version.h || true && \
|
||||
@@ -150,29 +162,14 @@ RUN set -xe && \
|
||||
# 收集 glibc 运行时依赖(动态探测,避免固定版本)
|
||||
RUN set -xe && \
|
||||
mkdir -p /runtime-libs && \
|
||||
ldd /src/subconverter /usr/lib/libmihomo.so | \
|
||||
awk '{for (i=1; i<=NF; i++) if ($i ~ "^/") print $i}' | \
|
||||
sort -u | \
|
||||
while read -r lib; do \
|
||||
if [ -e "$lib" ]; then \
|
||||
mkdir -p "/runtime-libs$(dirname "$lib")" && \
|
||||
cp -aL "$lib" "/runtime-libs$lib"; \
|
||||
fi; \
|
||||
done && \
|
||||
for loader in /lib64/ld-linux-x86-64.so.2 /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 /lib/ld-linux-aarch64.so.1 /lib/aarch64-linux-gnu/ld-linux-aarch64.so.1; do \
|
||||
if [ -e "$loader" ]; then \
|
||||
mkdir -p "/runtime-libs$(dirname "$loader")" && \
|
||||
cp -aL "$loader" "/runtime-libs$loader"; \
|
||||
fi; \
|
||||
done && \
|
||||
libc_path="$(ldd /src/subconverter | awk '$1 == "libc.so.6" {print $3; exit}')" && \
|
||||
libc_dir="$(dirname "${libc_path:-/lib/x86_64-linux-gnu/libc.so.6}")" && \
|
||||
for extra in libnss_dns.so.2 libnss_files.so.2 libnss_compat.so.2 libresolv.so.2; do \
|
||||
if [ -e "$libc_dir/$extra" ]; then \
|
||||
mkdir -p "/runtime-libs$libc_dir" && \
|
||||
cp -aL "$libc_dir/$extra" "/runtime-libs$libc_dir/$extra"; \
|
||||
fi; \
|
||||
done && \
|
||||
ELF_LIBRARY_PATH="/usr/lib:/lib/x86_64-linux-gnu:/usr/lib/x86_64-linux-gnu:/lib/aarch64-linux-gnu:/usr/lib/aarch64-linux-gnu:/lib64" \
|
||||
bash /src/scripts/ci/copy-elf-runtime-deps.sh /runtime-libs \
|
||||
/src/subconverter \
|
||||
/usr/lib/libmihomo.so \
|
||||
libnss_dns.so.2 \
|
||||
libnss_files.so.2 \
|
||||
libnss_compat.so.2 \
|
||||
libresolv.so.2 && \
|
||||
if [ -f /etc/nsswitch.conf ]; then \
|
||||
mkdir -p /runtime-libs/etc && \
|
||||
cp -aL /etc/nsswitch.conf /runtime-libs/etc/nsswitch.conf; \
|
||||
@@ -180,7 +177,7 @@ RUN set -xe && \
|
||||
|
||||
# ========== FINAL STAGE ==========
|
||||
# Alpine 运行时 + 搬运 glibc 依赖(不固定版本)
|
||||
FROM alpine:latest
|
||||
FROM mirror.gcr.io/library/alpine:latest
|
||||
|
||||
ARG VERSION="dev"
|
||||
ARG SHA=""
|
||||
@@ -196,20 +193,19 @@ LABEL \
|
||||
org.opencontainers.image.created="${BUILD_DATE}" \
|
||||
maintainer="Aethersailor"
|
||||
|
||||
RUN apk add --no-cache ca-certificates
|
||||
ENV TZ=Asia/Shanghai
|
||||
RUN apk add --no-cache ca-certificates tzdata && \
|
||||
ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && \
|
||||
echo $TZ > /etc/timezone
|
||||
|
||||
COPY --from=builder /src/subconverter /usr/bin/subconverter
|
||||
COPY --from=builder /src/base /base/
|
||||
COPY --from=builder /usr/lib/libmihomo.so /usr/lib/
|
||||
COPY --from=builder /runtime-libs/ /
|
||||
COPY --from=builder /etc/nsswitch.conf /etc/nsswitch.conf
|
||||
|
||||
# 确保二进制和库可执行
|
||||
RUN chmod +x /usr/bin/subconverter && chmod +x /usr/lib/libmihomo.so
|
||||
|
||||
ENV TZ=Asia/Shanghai
|
||||
ENV LD_LIBRARY_PATH="/lib/x86_64-linux-gnu:/usr/lib/x86_64-linux-gnu:/lib/aarch64-linux-gnu:/usr/lib/aarch64-linux-gnu:/lib64:/usr/lib"
|
||||
RUN ln -sf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||
|
||||
WORKDIR /base
|
||||
RUN set -e && \
|
||||
|
||||
706
README.md
706
README.md
@@ -1,23 +1,28 @@
|
||||
<div align="center">
|
||||
|
||||
<p>
|
||||
<img src="design/favicon-light-proposal.svg#gh-light-mode-only" alt="SubConverter-Extended icon" width="96" height="96">
|
||||
<img src="design/favicon-dark-proposal.svg#gh-dark-mode-only" alt="SubConverter-Extended icon" width="96" height="96">
|
||||
</p>
|
||||
|
||||
# SubConverter-Extended
|
||||
|
||||
**A Modern Evolution of subconverter**
|
||||
|
||||

|
||||

|
||||
[](https://hub.docker.com/r/aethersailor/subconverter-extended)
|
||||
[](LICENSE)
|
||||

|
||||

|
||||
[](https://hub.docker.com/r/aethersailor/subconverter-extended)
|
||||
[](LICENSE)
|
||||
|
||||
<h3>⚡ 现代化的订阅转换后端 | 完美兼容 Mihomo 内核 ⚡</h3>
|
||||
<h3>⚡ 现代化的订阅转换后端 | 深度适配 Mihomo 内核 ⚡</h3>
|
||||
|
||||
<p align="center">
|
||||
<a href="#-项目简介">项目简介</a> •
|
||||
<a href="#-立项原因">立项原因</a> •
|
||||
<a href="#-核心特性">核心特性</a> •
|
||||
<a href="#-快速开始">快速开始</a> •
|
||||
<a href="#-使用文档">使用文档</a> •
|
||||
<a href="#-docker-部署">Docker 部署</a>
|
||||
<a href="#-使用说明">使用说明</a> •
|
||||
<a href="#-配置说明">配置说明</a>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
@@ -27,22 +32,28 @@
|
||||
## 📖 项目简介
|
||||
|
||||
> [!NOTE]
|
||||
> **SubConverter-Extended** 是基于 [asdlokj1qpi233/subconverter](https://github.com/asdlokj1qpi233/subconverter) 深度二次开发的订阅转换后端增强版本。
|
||||
> **SubConverter-Extended** 是基于 [asdlokj1qpi233/subconverter](https://github.com/asdlokj1qpi233/subconverter) 深度二次开发的订阅转换后端增强版本,重点解决传统 subconverter 易被远程订阅服务商屏蔽、节点参数解析不完善、维护滞后等问题。
|
||||
|
||||
它专为协同 [Mihomo](https://github.com/MetaCubeX/mihomo) 内核工作优化,提供更现代、更强大的订阅转换功能。
|
||||
它围绕 [Mihomo](https://github.com/MetaCubeX/mihomo) 内核的实际使用场景进行优化,提供更现代、更稳定的订阅转换能力。
|
||||
|
||||
**核心定位转变**:
|
||||
SubConverter-Extended 不再充当客户端和远程第三方服务商之间的"中转站",而是成为独立的 **"配置融合器"** ——只与客户端通信,不再连接远程订阅服务器。同时基于 Mihomo 内核源码,在编译时自动跟进协议支持。
|
||||
**核心定位**:SubConverter-Extended 不再充当客户端与远程订阅服务商之间的“中转站”,而是作为独立的 **配置融合器** 运行。它只与客户端通信,不再主动连接远程订阅服务器;同时在编译阶段自动跟进 Mihomo 的协议支持。
|
||||
|
||||
<div align="center">
|
||||
<img width="719" height="442" alt="22" src="https://github.com/user-attachments/assets/506ab284-96cf-4aae-a844-bf69f15ce8df" />
|
||||
</div>
|
||||
**远程订阅链接处理流程对比:**
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/images/readme-flow-legacy.svg" alt="传统 subconverter 远程订阅链接处理流程" width="820">
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/images/readme-flow-extended.svg" alt="SubConverter-Extended 远程订阅链接处理流程" width="820">
|
||||
</p>
|
||||
|
||||
**关键差异**:SubConverter-Extended 仅负责生成配置,不再直接连接远程订阅服务器。
|
||||
|
||||
> [!WARNING]
|
||||
> 1. 本项目优先适配 OpenClash,其次是各 Clash 客户端,对其他客户端的支持不作保证。对于修改代码造成的支持范围缩减不作修复。
|
||||
> 2. 本项目保持中立,自身不提供任何规避监管制度的功能。
|
||||
> 3. 本项目仅作计算机编程代码学习研究之用,使用时请严格遵守当地法律法规,请勿用于任何非法用途。
|
||||
> 4. 本项目强烈建议使用合法的第三方服务商。
|
||||
> 1. 本项目保持中立,不提供任何规避监管制度的功能。
|
||||
> 2. 本项目仅用于计算机编程技术学习与研究,使用时请严格遵守当地法律法规,请勿用于任何非法用途。
|
||||
> 3. 建议始终使用合法合规的第三方服务商。
|
||||
|
||||
---
|
||||
|
||||
@@ -50,56 +61,56 @@ SubConverter-Extended 不再充当客户端和远程第三方服务商之间的"
|
||||
|
||||
### 遇到的问题
|
||||
|
||||
在长期使用 subconverter 的过程中,我遇到了几个不如人意的痛点:
|
||||
在长期使用 subconverter 的过程中,主要会遇到以下几个痛点:
|
||||
|
||||
#### 1. 协议支持滞后 🐢
|
||||
|
||||
原版 subconverter 对节点参数的支持是否完善,完全取决于开发者的积极性,其解析器需要依靠人工进行维护。
|
||||
原版 subconverter 对节点参数的支持高度依赖人工维护,其解析器的更新速度通常取决于开发者的时间与精力。
|
||||
|
||||
许多新兴协议(如 `hysteria2`、`tuic`、`anytls` 等)往往在相当长的时间内无法得到完善的支持,而一些老协议(如 `vless`),由于传输层协议等参数的更新,至今也未能做到完美的转换。
|
||||
许多新兴协议(如 `hysteria2`、`tuic`、`anytls` 等)往往无法在第一时间获得完善支持;一些老协议(如 `vless`)也会因为传输层参数持续演进,而长期存在转换不完整的问题。
|
||||
|
||||
在 subconverter 以及其流行分支的仓库中,可见大量相关的 issue。
|
||||
这一现象并非个别案例。在 subconverter 及其多个流行分支的仓库中,都能看到大量与协议支持相关的 issue。
|
||||
|
||||
当然,这并不是开发者的错,任何一位开发者都没有一直为开源社区用爱发电的义务。
|
||||
问题的根源并不在于开发者是否足够积极,而在于这类人工维护模式本身就需要持续投入大量测试和适配成本,长期来看很难稳定覆盖所有协议与参数变化。
|
||||
|
||||
我也曾 fork 过 subconverter 并试图解决对各个协议支持不完美的问题,最后得出的结论就是完全靠人工来维护这样一个项目,势必要花费不少时间和精力去测试,而且很有可能取得不了预期的结果。
|
||||
#### 2. 远程订阅服务商屏蔽问题 🚫
|
||||
|
||||
#### 2. 机场屏蔽问题 🚫
|
||||
|
||||
由于 subconverter 需要连接机场订阅服务器拉取节点,而部分机场出于安全考虑:
|
||||
原版 subconverter 需要主动连接远程订阅服务商的订阅服务器拉取节点,而部分远程订阅服务商出于安全策略,会采取如下限制:
|
||||
|
||||
* 屏蔽海外 IP 访问
|
||||
* 屏蔽 subconverter 的 User-Agent
|
||||
* 限制非客户端的订阅请求
|
||||
* 限制非客户端发起的订阅请求
|
||||
|
||||
这导致了许多用户根本无法正常使用订阅转换服务。
|
||||
这会直接导致许多用户无法正常使用订阅转换服务。
|
||||
|
||||
由于原版 subconverter 需要读取订阅链接内容,对于机场屏蔽特定地区 IP 请求订阅的限制完全无法通过其自身实现绕过。
|
||||
|
||||
对于 UA 的限制虽然可以通过修改或者删除原版中的特定 UA 来绕过,但这无形中也人为制造了和机场运营方的一种对抗。
|
||||
对于按地区限制订阅访问的场景,原版 subconverter 无法从架构层面规避;对于 User-Agent 限制,虽然可以通过修改或删除特定 UA 进行绕过,但这本质上是在工具和服务商之间制造额外对抗,并不是稳妥的长期方案。
|
||||
|
||||
#### 3. 新手友好度不足 🤯
|
||||
|
||||
由于上述问题,subconverter 逐渐被一些开发者和 UP 主视为"过时产物",开始推崇使用 YAML 文件手动管理配置。但很多新手并没有兴趣和时间去研究 YAML 配置文件,更多的是希望“开袋即食”。
|
||||
由于上述问题,subconverter 逐渐被一些开发者和内容创作者视为“过时方案”,转而推崇手动维护 YAML 配置。
|
||||
|
||||
但由于 subconverter 的种种问题,加上当下诸多机场的限制,许多用户常遇到不能解析节点/不能拉取节点/拉下来的节点参数无效等等奇怪的故障。对于很多新手来说,大部分人是并没有能力解决此类问题。
|
||||
但对大量普通用户而言,他们并不希望研究 YAML 细节,更需要的是一套基于 UI、可直接使用、问题边界清晰的操作流程。
|
||||
|
||||
**但也正是基于这一点,正如 [Custom_OpenClash_Rules](https://github.com/Aethersailor/Custom_OpenClash_Rules) 项目所坚持的:**
|
||||
现实情况是,在 subconverter 与远程订阅服务商限制叠加的情况下,用户经常会遇到无法解析节点、无法拉取节点、节点参数失效等问题;而新手用户通常也缺乏足够的排障能力。
|
||||
|
||||
正因如此,正如 [Custom_OpenClash_Rules](https://github.com/Aethersailor/Custom_OpenClash_Rules) 项目一直坚持的理念:
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **最适合新手和普通用户且最具普适性的操作流程,永远是基于 UI 界面的操作流程。**
|
||||
> **最适合新手和普通用户、且最具普适性的操作流程,始终是基于 UI 的操作流程。**
|
||||
|
||||
用户应当拿着订阅链接,点几下鼠标就能根据自己的实际情况配置出最佳效果,并自动享受完善的分流规则更新。
|
||||
理想状态应当是:用户拿到订阅链接后,只需进行少量可视化操作,就能按自身场景生成合适配置,并自动获得后续规则更新。
|
||||
|
||||
而不是繁琐的"手搓配置"、"上传文件"、"手动修改参数",甚至还得到处提问,问的太蠢了还会被人喷,一点儿都不优雅。
|
||||
### 🎯 解决方案
|
||||
|
||||
### 🎯 解决方案
|
||||
如果目标客户端本身基于 Mihomo 内核,那么订阅转换后端完全可以直接在配置文件中生成符合内核要求的 `proxy-provider` 字段,以取代过去“读取订阅内容 -> 解析节点 -> 回写节点参数”的旧流程。
|
||||
|
||||
从单纯服务于 Clash 客户端的角度来讲,完全可以以使用现代 Clash 内核的既有的 `proxy-provider` 功能来取代原版 subconverter 的订阅链接解析功能。
|
||||
这样一来,工具自身无需再承担远程订阅抓取与节点参数适配的职责,从架构上同时规避了协议支持滞后和服务商访问限制这两类问题。
|
||||
|
||||
同样,对于节点链接的解析,也完全可以引入内核的解析模块,来替代过去的人工维护解析器,直接获得最完美的结果。
|
||||
对于本地节点链接解析,则可以直接引入 Mihomo 内核的解析器模块,替代原先需要人工维护的解析器逻辑,使订阅转换后端与 Mihomo 内核的解析能力保持一致。
|
||||
|
||||
**那就自己动手吧。** SubConverter-Extended 因此诞生,让转换工具更匹配现代 Clash 内核的使用场景,**服务于所有保留“订阅转换”接口且使用 Mihomo 内核的 Clash 客户端**。
|
||||
基于这一思路,本项目只需跟随 Mihomo 内核更新,即可在绝大多数场景下自动获得同步的协议与参数支持,而无需重复投入额外的人工适配成本。
|
||||
|
||||
SubConverter-Extended 因此诞生。它是一款更贴合 Mihomo 使用场景的订阅转换工具,**服务于所有保留“订阅转换”接口且使用 Mihomo 内核的 Clash 客户端**。
|
||||
|
||||
---
|
||||
|
||||
@@ -109,99 +120,153 @@ SubConverter-Extended 不再充当客户端和远程第三方服务商之间的"
|
||||
|
||||
| 功能 | 原版 Subconverter | SubConverter-Extended |
|
||||
| :--- | :--- | :--- |
|
||||
| **完美协议支持** | 🛠️ 人工维护解析器,有限支持 | 🤖 **集成 Mihomo 内核的解析器模块,完美支持所有节点链接协议** |
|
||||
| **订阅链接处理** | 📥 下载并解析节点,易被屏蔽 | 🔗 **生成 `proxy-provider`,不再连接机场,由客户端的 Mihomo 内核直接拉取** |
|
||||
| **未来自动跟进** | ⏳ 人工添加维护新协议 | 🔄 **全自动维护,编译时自动扫描 Mihomo 源码添加支持新协议** |
|
||||
| **全局参数透传** | 📝 人工维护全局参数列表 | 🔍 **全自动维护,编译时自动扫描 Mihomo 源码,自动识别硬编码和可覆写参数** |
|
||||
| **节点链接解析** | 🛠️ 人工维护解析器,支持有限 | 🤖 **集成 Mihomo 内核解析模块,自动对齐协议支持** |
|
||||
| **订阅链接处理** | 📥 拉取并解析订阅,容易被屏蔽 | 🔗 **生成 `proxy-provider`,由客户端 Mihomo 内核直接拉取订阅** |
|
||||
| **协议维护方式** | ⏳ 依赖人工新增和维护 | 🔄 **编译时自动扫描 Mihomo 源码,跟进新协议支持** |
|
||||
| **全局参数维护** | 📝 人工维护节点参数列表 | 🔍 **编译时自动识别硬编码参数和可覆写参数** |
|
||||
|
||||
> [!WARNING]
|
||||
> 1. 本项目优先适配 OpenClash,其次是各类 Clash 客户端;对其他客户端的支持不作保证。
|
||||
> 2. 非 Mihomo 内核的客户端连接本项目,将继续调用继承自上游项目的人工维护解析器。
|
||||
> 3. 开发者仅确保完美支持 Mihomo 内核,因代码调整造成的对其他内核客户端的支持范围缩减,原则上不单独回补。
|
||||
|
||||
### 🔥 独特功能
|
||||
|
||||
#### 1. Proxy-Provider 模式 🛡️
|
||||
|
||||
**使用 Mihomo 的 Proxy-Provider 机制**
|
||||
**使用 Mihomo 的 Proxy-Provider 机制**
|
||||
|
||||
**不再下载解析订阅链接**,而是生成客户端可直接使用的配置,交由用户客户端的 Mihomo 内核自行拉取订阅:
|
||||
项目不再下载并解析远程订阅内容,而是生成客户端可直接使用的配置,交由用户客户端内置的 Mihomo 内核自行拉取订阅:
|
||||
|
||||
```yaml
|
||||
# Subconverter-Extended 生成示例内容
|
||||
# SubConverter-Extended 生成示例内容
|
||||
|
||||
proxy-providers:
|
||||
Provider_A1B2C3: # <-- provider 名称可在生成时使用参数实现自定义
|
||||
Provider_A1B2C3: # provider 名称可通过参数自定义
|
||||
type: http
|
||||
url: https://your-subscription-url # <-- 客户端直接连接机场
|
||||
url: https://your-subscription-url # 客户端实际拉取订阅的地址
|
||||
interval: 3600
|
||||
proxy: DIRECT
|
||||
proxy: DIRECT # 默认以直连方式拉取订阅
|
||||
path: ./providers/Provider_A1B2C3.yaml
|
||||
health-check:
|
||||
enable: true
|
||||
url: https://cp.cloudflare.com/generate_204
|
||||
interval: 300
|
||||
override: # <-- override 参数可正确传递用户订阅请求时附加的参数
|
||||
override: # 将请求中附加的覆写参数透传给 provider
|
||||
skip-cert-verify: true
|
||||
udp: true
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> * 使用 proxy-provider 后,由你的客户端内核以**直连**的形式自行拉取订阅。
|
||||
> * 订阅是否能成功,**与本后端无关,与规则无关**。效果等同于你自己制作 yaml 并填写订阅链接。
|
||||
> * 如遇拉取不成功,说明你的订阅链接不正确,或者在国内无法正常直连访问(至少在你所在的位置是如此),请和机场客服对线。
|
||||
> * `proxy-provider` 名称默认自动生成,也可通过文档下方的自定义参数指定
|
||||
> * 使用 `proxy-provider` 后,订阅由客户端内核以**直连**方式自行拉取
|
||||
> * 订阅是否可访问,**与本后端无关,与规则无关**;效果等同于你手动编写 YAML 并填入订阅链接
|
||||
> * 如内核使用 `proxy-provider` 拉取订阅失败,通常意味着订阅链接本身无效,或当前网络环境下无法直连访问该订阅地址,请与远程订阅服务商客服对线
|
||||
|
||||
> [!TIP]
|
||||
> **优势**:
|
||||
> **优势:**
|
||||
>
|
||||
> * ✅ 不再干涉用户节点,交由内核原生处理
|
||||
> * ✅ 不再干预用户节点,交由内核原生处理
|
||||
> * ✅ 订阅更新由客户端控制,无需重新转换
|
||||
> * ✅ 避免机场屏蔽转换服务器的问题
|
||||
> * ✅ 避免远程订阅服务商屏蔽转换服务带来的问题
|
||||
|
||||
#### 2. Mihomo 内核模块集成 🧩
|
||||
|
||||
对于节点链接的处理,直接使用 Mihomo Go 库解析节点链接,确保:
|
||||
对于本地节点链接(如 `vless://` 等格式)的处理,项目直接调用 Mihomo 的 Go 解析库,确保:
|
||||
|
||||
* ✅ 支持 Mihomo 内核可解析的所有节点连接协议(包括但不限于 `hysteria2`, `tuic`, `anytls` 等)
|
||||
* ✅ 参数完全兼容,解析能力与 Mihomo 内核对齐,无需手动适配
|
||||
* ✅ 新协议零延迟支持(编译时跟随 Mihomo 更新)
|
||||
* ✅ 原生支持 Mihomo 内核可解析的全部节点链接协议(包括但不限于 `hysteria2`、`tuic`、`anytls` 等)
|
||||
* ✅ 解析能力与 Mihomo 内核自动对齐,无需手动补丁式维护
|
||||
* ✅ 新协议可随 Mihomo 更新同步获得支持
|
||||
|
||||
#### 3. 兼容性保证 🤝
|
||||
#### 3. GitHub 原生文件地址回落 🌐
|
||||
|
||||
* ✅ **无缝切换**:完全兼容传统 subconverter 的 API 接口,确保客户端用户零学习成本,无缝切换。
|
||||
* ✅ **模板兼容**:继续沿用传统的订阅转换外部模板,无需修改任何内容,由后端内置逻辑确保 `proxy-provider` 模式在分流规则中正确生成。
|
||||
* ✅ **无忧更新**:编译时自动遍历 [Mihomo 内核源码仓库](https://github.com/MetaCubeX/mihomo/meta),自动提取内核解析模块,并读取当前最新支持的协议格式,自动识别可被全局参数覆盖的节点参数,确保永远对齐 Mihomo 内核支持解析的所有节点连接协议。
|
||||
当远程外部配置、规则集或 `!!import` 引用的是 GitHub 原生文件地址时,后端会优先访问原始 GitHub 原始地址;当原始地址因网络问题无法正常获取时,自动改用 `cdn.jsdelivr.net` 加速地址重试。非 GitHub 地址不受影响。
|
||||
|
||||
#### 4. 新手友好 👶
|
||||
此项改进旨在优化中国大陆地区的自部署用户使用托管在 GitHub 上的模板和规则时的访问性能。
|
||||
|
||||
* ✅ 使用 **[Custom_OpenClash_Rules](https://github.com/Aethersailor/Custom_OpenClash_Rules)** 远程配置模板替代默认模板
|
||||
* ✅ 锁死 API 模式,关闭 API 模式相关接口,避免新手误配置降低安全性
|
||||
* ✅ 简化参数,专注核心功能
|
||||
支持的 GitHub 文件地址形式包括:
|
||||
|
||||
```text
|
||||
https://raw.githubusercontent.com/<owner>/<repo>/<ref>/<path>
|
||||
https://github.com/<owner>/<repo>/raw/<ref>/<path>
|
||||
https://github.com/<owner>/<repo>/blob/<ref>/<path>
|
||||
```
|
||||
|
||||
#### 4. 请求诊断台 🔎
|
||||
|
||||
内置 `explain=true` 诊断模式和 `/inspect` 网页诊断台,方便在不改变实际转换逻辑的前提下排查请求:
|
||||
|
||||
* ✅ 展示请求参数是否被识别、是否生效、是否被项目安全逻辑覆盖
|
||||
* ✅ 汇总外部配置、规则集、自定义组、Provider、输出大小等关键状态
|
||||
* ✅ 对订阅来源等敏感信息只展示预览、长度或短哈希,便于排障时降低泄露风险
|
||||
|
||||
#### 5. 运行仪表盘 📊
|
||||
|
||||
启用统计后,`/dashboard` 可展示服务运行期转换统计,适合公开服务部署者观察后端使用情况:
|
||||
|
||||
* ✅ 展示本次启动、历史总计、最近 24 小时和滚动时间窗口统计、访问者地理位置分布
|
||||
* ✅ 按请求数和规则转换数展示国家 / 地区分布与排行
|
||||
* ✅ 支持统计数据持久化和可选 Basic Auth 验证,便于公网部署时限制访问
|
||||
|
||||
#### 6. 兼容性保证 🤝
|
||||
|
||||
* ✅ **无缝切换**:兼容常见传统 subconverter API 接口,客户端侧几乎无需学习成本即可迁移
|
||||
* ✅ **模板兼容**:继续沿用传统外部模板,由后端内置逻辑确保 `proxy-provider` 模式在分流规则中正确生成
|
||||
* ✅ **自动跟进**:编译时自动遍历 [Mihomo 内核源码仓库](https://github.com/MetaCubeX/mihomo/meta),提取最新解析模块、协议格式与可覆写参数
|
||||
|
||||
#### 7. 新手友好 👶
|
||||
|
||||
* ✅ 使用 **[Custom_OpenClash_Rules](https://github.com/Aethersailor/Custom_OpenClash_Rules)** 远程配置模板,替代默认内置模板与自定义代理组功能
|
||||
* ✅ 锁定 API 模式,强制关闭相关接口,降低新手误配置带来的安全风险
|
||||
* ✅ 精简参数设计,聚焦高频核心场景
|
||||
|
||||
---
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 🌍 使用演示实例 (无需部署)
|
||||
### 🌍 使用演示实例(无需部署)
|
||||
|
||||
如果你不想折腾,可以直接使用我们提供的演示实例:
|
||||
如果你不想自行部署,可以直接使用演示实例:
|
||||
|
||||
> [!TIP]
|
||||
> **地址**:`https://api.asailor.org`
|
||||
>
|
||||
> 
|
||||
|
||||
OpenClash 中已内置该演示实例地址,你也可以在任何支持自定义后端的订阅转换网站或客户端中填入此地址即可调用。
|
||||
OpenClash 已内置该演示实例地址;在其他支持自定义后端的订阅转换网站或客户端中,也可以直接填入该地址进行调用。
|
||||
|
||||
> [!IMPORTANT]
|
||||
> 默认输出**最简配置**,无 DNS 参数,请启用 Clash 客户端中的 DNS 覆写功能!
|
||||
> 例如 OpenClash > 覆写设置 > 自定义上游 DNS 服务器
|
||||
> 或者自行在生成的配置文件中人工补全 DNS 参数
|
||||
> 否则无法解析任何节点域名,导致全部节点无法连接
|
||||
> 默认输出为**最简配置**,不包含 DNS 参数,请在 Clash 客户端中启用 DNS 覆写功能。
|
||||
> 例如:`OpenClash > 覆写设置 > 自定义上游 DNS 服务器`
|
||||
> 否则将无法解析节点域名,导致全部节点无法连接。
|
||||
|
||||
### 🐳 自行部署 (Docker)
|
||||
### 🚀 自行部署
|
||||
|
||||
如果你拥有自己的服务器,推荐使用 Docker 进行部署。
|
||||
推荐优先使用 Docker 部署;如果部署环境不方便运行容器,可以根据 [Release](https://github.com/Aethersailor/SubConverter-Extended/releases/latest) 中的安装包类型选择便携包或 OpenWrt APK。
|
||||
|
||||
> [!WARNING]
|
||||
> *由于开发者业余时间有限,以下部署指南部分内容由 AI 生成,仅供参考。*
|
||||
> [!IMPORTANT]
|
||||
> 如果服务需要被其他设备访问,请将配置中的 `managed_config_prefix` 改为实际访问地址,例如 `http://192.168.1.10:25500` 或 `https://sub.example.com`。公网部署还建议在配置中启用 `public` 安全档位,详见下方“配置说明”。
|
||||
|
||||
#### 1. 一键启动
|
||||
#### 安装包类型速查
|
||||
|
||||
| 类型 | 文件 / 镜像 | 适用场景 |
|
||||
| :--- | :--- | :--- |
|
||||
| Docker 镜像 | `aethersailor/subconverter-extended`、`ghcr.io/aethersailor/subconverter-extended` | 服务器、NAS、软路由容器环境 |
|
||||
| Linux 便携包 | `SubConverter-Extended-<version>-linux-amd64.tar.gz`、`linux-arm64.tar.gz`、`linux-armv7.tar.gz` | 不使用 Docker 的 Linux 主机 |
|
||||
| Windows 便携包 | `SubConverter-Extended-<version>-windows-amd64.zip` | Windows x64 主机 |
|
||||
| OpenWrt APK | `SubConverter-Extended-<version>-openwrt-<arch>.apk` | 使用 `apk` 包管理器的 OpenWrt 25.12+ |
|
||||
| 校验文件 | `SHA256SUMS` | 校验 Release 下载文件完整性 |
|
||||
|
||||
<details>
|
||||
<summary><strong>Docker 部署(推荐)</strong></summary>
|
||||
|
||||
Docker 镜像支持以下平台:
|
||||
|
||||
* `linux/amd64`
|
||||
* `linux/arm64`
|
||||
* `linux/arm/v7`
|
||||
|
||||
#### 一键启动
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
@@ -209,12 +274,11 @@ docker run -d \
|
||||
-p 25500:25500 \
|
||||
--restart unless-stopped \
|
||||
aethersailor/subconverter-extended:latest
|
||||
|
||||
```
|
||||
|
||||
访问 `http://localhost:25500/version` 验证部署。
|
||||
访问 `http://localhost:25500/version` 验证服务是否正常启动。
|
||||
|
||||
#### 2. 自定义配置启动
|
||||
#### 自定义配置启动
|
||||
|
||||
```bash
|
||||
# 删除可能存在的工作目录
|
||||
@@ -225,23 +289,36 @@ mkdir -p /opt/SubConverter-Extended/base
|
||||
|
||||
cd /opt/SubConverter-Extended
|
||||
|
||||
# 下载 SubConverter-Extended 配置文件
|
||||
# 下载配置文件
|
||||
wget -O base/pref.toml \
|
||||
https://gcore.jsdelivr.net/gh/Aethersailor/SubConverter-Extended@master/base/pref.example.toml
|
||||
|
||||
# 如需外部访问,请自行修改 /opt/SubConverter-Extended/base/pref.toml 中的 managed_config_prefix 地址。
|
||||
# 如需外部访问,请修改 base/pref.toml 中的 managed_config_prefix
|
||||
|
||||
# 启动容器并挂载配置
|
||||
docker run -d \
|
||||
--name SubConverter-Extended \
|
||||
-p 25500:25500 \
|
||||
-v /opt/SubConverter-Extended/base/pref.toml:/base/pref.toml \
|
||||
-v /opt/SubConverter-Extended/base/pref.toml:/base/pref.toml:ro \
|
||||
--restart unless-stopped \
|
||||
aethersailor/subconverter-extended:latest
|
||||
|
||||
```
|
||||
|
||||
#### 3. Docker Compose
|
||||
也可以直接用环境变量覆盖常用配置:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name SubConverter-Extended \
|
||||
-p 25500:25500 \
|
||||
-e MANAGED_CONFIG_PREFIX="http://your-domain-or-ip:25500" \
|
||||
-e SUBCONVERTER_SECURITY_PROFILE=public \
|
||||
-e SUBCONVERTER_ALLOW_PUBLIC_UPLOAD=false \
|
||||
--restart unless-stopped \
|
||||
aethersailor/subconverter-extended:latest
|
||||
```
|
||||
|
||||
#### Docker Compose
|
||||
|
||||
```bash
|
||||
# 删除可能存在的工作目录
|
||||
rm -rf /opt/SubConverter-Extended
|
||||
@@ -255,26 +332,224 @@ cd /opt/SubConverter-Extended
|
||||
wget -O docker-compose.yml \
|
||||
https://gcore.jsdelivr.net/gh/Aethersailor/SubConverter-Extended@master/docker-compose.yml
|
||||
|
||||
# 下载 SubConverter-Extended 配置文件
|
||||
# 下载配置文件
|
||||
wget -O base/pref.toml \
|
||||
https://gcore.jsdelivr.net/gh/Aethersailor/SubConverter-Extended@master/base/pref.example.toml
|
||||
|
||||
# 如需外部访问,请自行修改 /opt/SubConverter-Extended/base/pref.toml 中的 managed_config_prefix 地址。
|
||||
# 如需外部访问,请修改 docker-compose.yml 中的 MANAGED_CONFIG_PREFIX,
|
||||
# 或修改 base/pref.toml 中的 managed_config_prefix
|
||||
|
||||
# 启动容器
|
||||
docker-compose up -d
|
||||
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
常用维护命令:
|
||||
|
||||
```bash
|
||||
docker logs -f SubConverter-Extended
|
||||
docker restart SubConverter-Extended
|
||||
docker rm -f SubConverter-Extended
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Linux 便携包部署</strong></summary>
|
||||
|
||||
Linux 便携包适用于不方便运行 Docker 的 Linux 主机。Release 提供 `amd64`、`arm64`、`armv7` 三类包,包内已包含启动脚本和运行时依赖。
|
||||
|
||||
#### 选择架构
|
||||
|
||||
```bash
|
||||
uname -m
|
||||
```
|
||||
|
||||
常见对应关系:
|
||||
|
||||
| `uname -m` 输出 | 选择的包 |
|
||||
| :--- | :--- |
|
||||
| `x86_64` | `linux-amd64.tar.gz` |
|
||||
| `aarch64` / `arm64` | `linux-arm64.tar.gz` |
|
||||
| `armv7l` / `armv7` | `linux-armv7.tar.gz` |
|
||||
|
||||
#### 下载并解压
|
||||
|
||||
```bash
|
||||
# 将 VERSION 替换为 Release 页面中的实际版本号,例如 v1.1.13
|
||||
VERSION=v1.1.13
|
||||
ARCH=amd64
|
||||
INSTALL_DIR=/opt/SubConverter-Extended
|
||||
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
cd /tmp
|
||||
|
||||
curl -fLO "https://github.com/Aethersailor/SubConverter-Extended/releases/download/${VERSION}/SubConverter-Extended-${VERSION}-linux-${ARCH}.tar.gz"
|
||||
curl -fLO "https://github.com/Aethersailor/SubConverter-Extended/releases/download/${VERSION}/SHA256SUMS"
|
||||
sha256sum -c SHA256SUMS --ignore-missing
|
||||
|
||||
tar -xzf "SubConverter-Extended-${VERSION}-linux-${ARCH}.tar.gz" \
|
||||
-C "$INSTALL_DIR" \
|
||||
--strip-components=1
|
||||
```
|
||||
|
||||
#### 启动服务
|
||||
|
||||
```bash
|
||||
cd /opt/SubConverter-Extended
|
||||
chmod +x start.sh subconverter
|
||||
|
||||
# 首次启动会自动从 base/pref.example.toml 创建 base/pref.toml
|
||||
./start.sh
|
||||
```
|
||||
|
||||
访问 `http://localhost:25500/version` 验证服务是否正常启动。
|
||||
|
||||
#### 使用 systemd 常驻运行
|
||||
|
||||
```bash
|
||||
cat >/etc/systemd/system/subconverter-extended.service <<'EOF'
|
||||
[Unit]
|
||||
Description=SubConverter-Extended
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=/opt/SubConverter-Extended
|
||||
ExecStart=/opt/SubConverter-Extended/start.sh
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now subconverter-extended
|
||||
systemctl status subconverter-extended
|
||||
```
|
||||
|
||||
如需把配置文件放在其他位置,可以通过 `PREF_PATH` 指定:
|
||||
|
||||
```bash
|
||||
PREF_PATH=/etc/subconverter/pref.toml /opt/SubConverter-Extended/start.sh
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Windows 便携包部署</strong></summary>
|
||||
|
||||
Windows 便携包适用于 Windows x64 环境,文件名为 `SubConverter-Extended-<version>-windows-amd64.zip`。
|
||||
|
||||
#### 部署步骤
|
||||
|
||||
1. 从 [Release](https://github.com/Aethersailor/SubConverter-Extended/releases/latest) 下载 `windows-amd64.zip`。
|
||||
2. 解压到固定目录,例如 `C:\SubConverter-Extended`。
|
||||
3. 双击运行 `start.bat`,或在 PowerShell 中运行:
|
||||
|
||||
```powershell
|
||||
cd C:\SubConverter-Extended
|
||||
.\start.ps1
|
||||
```
|
||||
|
||||
如果 PowerShell 执行策略阻止脚本运行,可以改用:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File .\start.ps1
|
||||
```
|
||||
|
||||
首次启动时,启动脚本会按顺序查找 `base\pref.toml`、`base\pref.yml`、`base\pref.ini`;如果都不存在,会自动从示例配置创建 `base\pref.toml`。
|
||||
|
||||
访问 `http://localhost:25500/version` 验证服务是否正常启动。首次运行时如果 Windows 防火墙弹窗,请按实际访问范围放行。
|
||||
|
||||
#### 使用自定义配置路径
|
||||
|
||||
PowerShell:
|
||||
|
||||
```powershell
|
||||
$env:PREF_PATH = "D:\subconverter\pref.toml"
|
||||
& C:\SubConverter-Extended\start.ps1
|
||||
```
|
||||
|
||||
CMD:
|
||||
|
||||
```cmd
|
||||
set PREF_PATH=D:\subconverter\pref.toml
|
||||
C:\SubConverter-Extended\start.bat
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>OpenWrt APK 部署</strong></summary>
|
||||
|
||||
OpenWrt APK 包适用于使用 `apk` 包管理器的 OpenWrt 25.12+。该包未签名,安装时需要使用 `--allow-untrusted`。
|
||||
|
||||
#### 选择架构
|
||||
|
||||
```sh
|
||||
apk print-arch
|
||||
```
|
||||
|
||||
下载与输出完全匹配的 APK,例如:
|
||||
|
||||
| `apk print-arch` 输出 | 选择的包 |
|
||||
| :--- | :--- |
|
||||
| `x86_64` | `openwrt-x86_64.apk` |
|
||||
| `aarch64_generic` | `openwrt-aarch64_generic.apk` |
|
||||
| `aarch64_cortex-a53` | `openwrt-aarch64_cortex-a53.apk` |
|
||||
| `aarch64_cortex-a72` | `openwrt-aarch64_cortex-a72.apk` |
|
||||
| `arm_cortex-*` | 对应同名 `openwrt-arm_cortex-*.apk` |
|
||||
|
||||
#### 下载并安装
|
||||
|
||||
```sh
|
||||
# 将 VERSION 替换为 Release 页面中的实际版本号,例如 v1.1.13
|
||||
VERSION=v1.1.13
|
||||
ARCH="$(apk print-arch)"
|
||||
PKG="/tmp/SubConverter-Extended-${VERSION}-openwrt-${ARCH}.apk"
|
||||
|
||||
wget -O "$PKG" \
|
||||
"https://github.com/Aethersailor/SubConverter-Extended/releases/download/${VERSION}/SubConverter-Extended-${VERSION}-openwrt-${ARCH}.apk"
|
||||
|
||||
apk add --allow-untrusted "$PKG"
|
||||
```
|
||||
|
||||
#### 启动服务
|
||||
|
||||
```sh
|
||||
/etc/init.d/subconverter-extended enable
|
||||
/etc/init.d/subconverter-extended start
|
||||
```
|
||||
|
||||
访问 `http://路由器IP:25500/version` 验证服务是否正常启动。
|
||||
|
||||
OpenWrt APK 的默认用户配置位于 `/etc/subconverter/pref.toml`。首次启动时会自动创建该文件,后续升级不会覆盖已有配置。
|
||||
|
||||
常用维护命令:
|
||||
|
||||
```sh
|
||||
/etc/init.d/subconverter-extended restart
|
||||
/etc/init.d/subconverter-extended stop
|
||||
logread -e subconverter
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## 📚 使用文档
|
||||
## 📚 使用说明
|
||||
|
||||
使用方式与原版 subconverter 完全相同。
|
||||
整体使用方式与原版 subconverter 基本一致,常见客户端和订阅转换前端通常无需额外适配即可迁移。
|
||||
|
||||
下方仅重点说明本项目的高频参数、特有能力,以及与原版 subconverter 行为不同的部分;未列出的兼容参数仍可按原版 subconverter 的使用习惯传入。
|
||||
|
||||
> [!IMPORTANT]
|
||||
> 默认输出**最简配置**,无 DNS 参数,请启用各 Clash 客户端中的 DNS 覆写功能!
|
||||
> 或者自行在生成的配置文件中人工补全 DNS 参数
|
||||
> 默认输出为**最简配置**,不包含 DNS 参数,请在各 Clash 客户端中启用 DNS 覆写功能,或在生成的配置文件中自行补全 DNS 配置。
|
||||
|
||||
<details open>
|
||||
<summary><strong>快速调用与常用参数</strong></summary>
|
||||
|
||||
### 常用参数一览
|
||||
|
||||
@@ -285,24 +560,181 @@ docker-compose up -d
|
||||
| `config` | 外部配置文件 | `https://config-url` |
|
||||
| `include` | 包含节点(正则) | `香港\|台湾` |
|
||||
| `exclude` | 排除节点(正则) | `过期\|剩余` |
|
||||
| `emoji` | 添加 Emoji | `true`/`false` |
|
||||
| `emoji` | 添加 Emoji | `true` / `false` |
|
||||
| `explain` | 返回本次转换的 JSON 诊断报告 | `true` |
|
||||
|
||||
#### provider 前缀(仅 Clash/ClashR 订阅链接)
|
||||
### 常见调用示例
|
||||
|
||||
`provider` 不是独立参数,而是写在 `url=` 列表中、放在订阅链接前,用逗号分隔,用于自定义 `proxy-providers` 名称。节点链接不生效。
|
||||
```text
|
||||
https://api.asailor.org/sub?target=clash&url=https%3A%2F%2Fexample.com%2Fsub&config=https%3A%2F%2Fexample.com%2Fconfig.ini
|
||||
```
|
||||
|
||||
```text
|
||||
https://api.asailor.org/sub?target=clash&url=provider%3AHK%2Chttps%3A%2F%2Fexample.com%2Fsub&include=%E9%A6%99%E6%B8%AF&emoji=true
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>诊断与排障</strong></summary>
|
||||
|
||||
### `explain=true` 诊断模式
|
||||
|
||||
在 `/sub` 请求中追加 `explain=true` 后,后端会按同一组参数执行转换流程,但返回 JSON 诊断报告,而不是返回 Clash/Surge/QuanX 配置文件。
|
||||
|
||||
示例:
|
||||
|
||||
```text
|
||||
https://api.asailor.org/sub?target=clash&url=https%3A%2F%2Fexample.com%2Fsub&explain=true
|
||||
```
|
||||
|
||||
这个模式适合排查“参数是否生效”“是否进入 `proxy-provider` 模式”“外部配置是否加载成功”“规则集和节点数量是否符合预期”等问题。报告会包含目标格式、模式开关、输入数量、外部配置状态、规则集统计、provider 数量和输出大小等信息。
|
||||
|
||||
**说明:**
|
||||
|
||||
* `explain=true` 只改变响应内容,不改变实际转换逻辑。
|
||||
* 如果同一请求里包含上传参数,诊断模式会抑制上传,避免排障时产生托管配置写入。
|
||||
* 诊断报告不会直接回显原始订阅地址;provider 来源会以短哈希形式显示,便于区分来源又避免泄露完整链接。
|
||||
|
||||
### `/inspect` 请求诊断台
|
||||
|
||||
如果不方便直接阅读 `explain=true` 返回的 JSON,可以访问 `/inspect` 打开网页诊断台:
|
||||
|
||||
```text
|
||||
https://api.asailor.org/inspect
|
||||
```
|
||||
|
||||
自部署环境可访问:
|
||||
|
||||
```text
|
||||
http://localhost:25500/inspect
|
||||
```
|
||||
|
||||
诊断台支持粘贴完整 `/sub?...` 链接、完整 URL,或仅粘贴查询参数。页面会自动补充 `explain=true` 并以只读方式发起诊断请求,然后展示摘要、已识别参数、未识别参数、生效配置、Provider 信息和原始 JSON。
|
||||
|
||||
这个页面适合排查以下问题:
|
||||
|
||||
* 某个请求参数是否被识别、是否生效、是否被覆盖或抑制
|
||||
* `list=true` 等参数是否被项目强制改写为 `proxy-provider` 模式
|
||||
* `include` / `exclude`、`emoji`、`new_name`、`config` 等外部参数最终是否参与转换
|
||||
* 外部配置、规则集、自定义组、Provider 是否按预期加载或生成
|
||||
|
||||
**说明:**
|
||||
|
||||
* `/inspect` 只是 `explain=true` 诊断报告的可视化界面,不会改变实际转换逻辑。
|
||||
* 页面会隐藏敏感输入的明文,仅展示预览、长度和短哈希等排障信息。
|
||||
* 请求诊断台会保留原始 JSON 区域,方便复制给维护者进一步分析。
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>/dashboard 运行仪表盘</strong></summary>
|
||||
|
||||
### `/dashboard` 使用方法
|
||||
|
||||
`/dashboard` 用于查看运行期转换统计。该功能默认关闭;只有在配置文件中启用 `statistics.enabled` 后,服务才会注册 `/dashboard` 和 `/dashboard/data` 路由。
|
||||
|
||||
启用后可访问:
|
||||
|
||||
```text
|
||||
http://localhost:25500/dashboard
|
||||
```
|
||||
|
||||
公网或反代部署时,请替换为实际域名:
|
||||
|
||||
```text
|
||||
https://sub.example.com/dashboard
|
||||
```
|
||||
|
||||
`/dashboard/data` 会返回仪表盘使用的 JSON 数据,适合接入外部监控或自行排查:
|
||||
|
||||
```text
|
||||
http://localhost:25500/dashboard/data
|
||||
```
|
||||
|
||||
仪表盘主要展示:
|
||||
|
||||
* 服务启动时间、本次运行时长、累计运行时长和启动次数
|
||||
* 成功 `/sub` 转换请求数与规则转换数
|
||||
* 最近 24 小时请求 / 规则转换柱状图
|
||||
* 按 1 小时、1 天、7 天、30 天、半年、1 年和历史总计统计的国家 / 地区分布与排行
|
||||
* 当可信边缘网关提供地区请求头时,展示中国地区请求 / 规则转换地图和排行
|
||||
|
||||
**说明:**
|
||||
|
||||
* 统计只在 `statistics.enabled=true` 后开始写入,启用前的历史请求不会回补。
|
||||
* 统计模块只记录成功的 `GET /sub` 转换请求和规则转换计数,不存储订阅链接、节点内容或访问者 IP。
|
||||
* 国家 / 地区来源于配置的国家码请求头;中国地区来源于配置的地区请求头;无法识别时会归为未知。
|
||||
* Docker 部署如需跨重启保留统计数据,请将 `data_dir` 对应目录挂载为卷,例如 `./stats:/base/stats`。
|
||||
|
||||
### 启用示例(TOML)
|
||||
|
||||
修改 `base/pref.toml` 后重启服务:
|
||||
|
||||
```toml
|
||||
[statistics]
|
||||
enabled = true
|
||||
data_dir = "stats"
|
||||
flush_interval = 5
|
||||
|
||||
[statistics.geo]
|
||||
provider = "header"
|
||||
country_headers = ["CF-IPCountry", "X-Geo-Country", "X-Vercel-IP-Country", "CloudFront-Viewer-Country"]
|
||||
china_region_headers = ["CF-Region-Code", "cf-region-code", "X-Geo-Subdivision"]
|
||||
|
||||
[statistics.dashboard_auth]
|
||||
enabled = true
|
||||
username = "admin"
|
||||
password = "change-this-password"
|
||||
max_failures = 5
|
||||
window_seconds = 300
|
||||
lock_seconds = 900
|
||||
```
|
||||
|
||||
### 新增配置项说明
|
||||
|
||||
| TOML / YAML 配置项 | INI 配置项 | 默认值 | 说明 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `statistics.enabled` | `enabled` | `false` | 是否启用运行期统计和 `/dashboard`。关闭时不会注册 `/dashboard` 与 `/dashboard/data`。 |
|
||||
| `statistics.data_dir` | `data_dir` | `stats` | 统计数据目录,按程序工作目录解析;Docker 中可挂载 `/base/stats` 持久化。 |
|
||||
| `statistics.flush_interval` | `flush_interval` | `5` | 统计数据最小写盘间隔,单位为秒。 |
|
||||
| `statistics.geo.provider` | `geo_provider` | `header` | 国家 / 地区识别方式。`header` 表示读取国家码请求头,`none` 表示全部记为未知。 |
|
||||
| `statistics.geo.country_headers` | `country_headers` | `CF-IPCountry`, `X-Geo-Country`, `X-Vercel-IP-Country`, `CloudFront-Viewer-Country` | `provider=header` 时依次尝试读取的国家码请求头。 |
|
||||
| `statistics.geo.china_region_headers` | `china_region_headers` | `CF-Region-Code`, `cf-region-code`, `X-Geo-Subdivision` | 可信边缘网关注入中国地区码时依次尝试读取的请求头,用于中国地区地图和排行。 |
|
||||
| `statistics.dashboard_auth.enabled` | `dashboard_auth_enabled` | `false` | 是否为 `/dashboard` 和 `/dashboard/data` 启用 Basic Auth。 |
|
||||
| `statistics.dashboard_auth.username` | `dashboard_auth_username` | 空 | Basic Auth 用户名。启用认证后不能为空。 |
|
||||
| `statistics.dashboard_auth.password` | `dashboard_auth_password` | 空 | Basic Auth 密码。启用认证后不能为空;公网部署建议配合 HTTPS。 |
|
||||
| `statistics.dashboard_auth.max_failures` | `dashboard_auth_max_failures` | `5` | 在统计窗口内允许的失败登录次数。 |
|
||||
| `statistics.dashboard_auth.window_seconds` | `dashboard_auth_window_seconds` | `300` | 失败登录统计窗口,单位为秒。 |
|
||||
| `statistics.dashboard_auth.lock_seconds` | `dashboard_auth_lock_seconds` | `900` | 超过失败次数后的锁定时长,单位为秒。 |
|
||||
|
||||
**提示:** `pref.yml` 使用同名嵌套字段;`pref.ini` 的上述 INI 配置项均写在 `[statistics]` 段内。
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Proxy-Provider 自定义名称</strong></summary>
|
||||
|
||||
### `provider` 前缀(仅适用于 Clash/ClashR 订阅链接)
|
||||
|
||||
`provider` 不是独立参数,而是写在 `url=` 列表中、放在订阅链接前,并以逗号分隔,用于自定义 `proxy-providers` 名称;对节点链接不生效。
|
||||
|
||||
示例:
|
||||
|
||||
```text
|
||||
url=provider:HK,https://example.com/sub
|
||||
url=provider:HK,https://a|provider:HK,https://b
|
||||
url=provider%3AHK%2Chttps%3A%2F%2Fexample.com%2Fsub
|
||||
```
|
||||
|
||||
**说明:** 在 OpenClash 这类预置“订阅地址”输入框的软件中,无需填写开头的 `url=`,直接填入等号后的内容即可。
|
||||
|
||||
补充说明:
|
||||
|
||||
* 支持中文;非法字符或空值会回退为默认 `Provider_<MD5>`
|
||||
* 重名会自动追加 `_1`、`_2` 等后缀
|
||||
* 支持中文名称;非法字符或空值会回退为默认 `Provider_<MD5>`
|
||||
* 重名时会自动追加 `_1`、`_2` 等后缀
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
@@ -318,15 +750,67 @@ url=provider%3AHK%2Chttps%3A%2F%2Fexample.com%2Fsub
|
||||
[managed_config]
|
||||
managed_config_prefix = "http://localhost:25500" # 托管配置前缀
|
||||
```
|
||||
非本机部署时,请将此项修改为 SubConverter-Extended 部署机的 IP 地址或域名。
|
||||
|
||||
非本机部署时,请将该项修改为 SubConverter-Extended 实际部署机的 IP 地址或域名。
|
||||
|
||||
### 安全档位
|
||||
|
||||
从本版本开始,主配置文件支持 `[security]` 安全档位,用于区分内网自用部署和公网暴露部署。
|
||||
|
||||
默认值为 `lan`,保持历史行为不变,适合家庭内网、NAS、软路由、旁路由、Docker 内网等自用场景。该档位允许访问本地资源、私有网段资源和 fake-ip 资源,因此现有部署通常无需额外修改配置。
|
||||
|
||||
公网部署建议显式切换为 `public`:
|
||||
|
||||
```toml
|
||||
[security]
|
||||
profile = "public"
|
||||
allow_public_upload = false
|
||||
```
|
||||
|
||||
INI 配置示例:
|
||||
|
||||
```ini
|
||||
[security]
|
||||
profile=public
|
||||
allow_public_upload=false
|
||||
```
|
||||
|
||||
Docker 环境变量示例:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name SubConverter-Extended \
|
||||
-p 25500:25500 \
|
||||
-e SUBCONVERTER_SECURITY_PROFILE=public \
|
||||
-e SUBCONVERTER_ALLOW_PUBLIC_UPLOAD=false \
|
||||
--restart unless-stopped \
|
||||
aethersailor/subconverter-extended:latest
|
||||
```
|
||||
|
||||
| 配置项 / 环境变量 | 默认值 | 说明 |
|
||||
| :--- | :--- | :--- |
|
||||
| `security.profile` / `SUBCONVERTER_SECURITY_PROFILE` | `lan` | 可选值:`lan`、`public`、`strict` |
|
||||
| `security.allow_public_upload` / `SUBCONVERTER_ALLOW_PUBLIC_UPLOAD` | `false` | 仅 `public` 档位生效,用于显式允许公开请求触发上传 |
|
||||
|
||||
档位说明:
|
||||
|
||||
* `lan`:默认档位,保持旧行为,适合可信内网自用部署。
|
||||
* `public`:公网推荐档位。限制公开请求参数、远程外部配置、公开 `!!import` 等不可信来源访问本地、私网和 fake-ip 字面量;项目自带本地模板、部署者配置的默认模板与可信本地配置仍可正常使用。
|
||||
* `strict`:在 `public` 的基础上,始终禁止公开请求触发上传,即使设置 `allow_public_upload=true` 也不会放行。
|
||||
|
||||
> [!NOTE]
|
||||
> `public` 档位不会阻止正常域名在 OpenClash fake-ip DNS 环境下解析到 `198.18.0.0/15` 后继续访问;但会阻止请求方直接传入 `127.0.0.1`、私有地址或 fake-ip 字面量作为抓取目标。
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Docker Hub 镜像标签
|
||||
|
||||
`latest` 与版本标签均为多架构镜像,当前支持 `linux/amd64`、`linux/arm64`、`linux/arm/v7`。
|
||||
|
||||
| 标签 | 用途 | 更新频率 |
|
||||
| :--- | :--- | :--- |
|
||||
| `latest` | 🟢 **稳定版本**(master 分支) | 有 release 时更新 |
|
||||
| `dev` | 🟡 **开发版本**(dev 分支) | 每次 dev 分支推送 |
|
||||
| `latest` | 🟢 **稳定版本**(`master` 分支) | 发布 Release 时更新 |
|
||||
| `dev` | 🟡 **开发版本**(`dev` 分支) | 每次 `dev` 分支推送后更新 |
|
||||
|
||||
---
|
||||
|
||||
@@ -335,7 +819,7 @@ managed_config_prefix = "http://localhost:25500" # 托管配置前缀
|
||||
本项目使用或引用了以下开源项目,在此表示感谢:
|
||||
|
||||
* [MetaCubeX/mihomo](https://github.com/MetaCubeX/mihomo) - Clash 内核,提供节点链接解析能力
|
||||
* [Aethersailor/Custom_OpenClash_Rules](https://github.com/Aethersailor/Custom_OpenClash_Rules) - OpenClash 订阅转换模板、规则集和教程项目
|
||||
* [Aethersailor/Custom_OpenClash_Rules](https://github.com/Aethersailor/Custom_OpenClash_Rules) - OpenClash 订阅转换模板、规则集与教程项目
|
||||
* [asdlokj1qpi233/subconverter](https://github.com/asdlokj1qpi233/subconverter) - 原版 subconverter 项目
|
||||
|
||||
---
|
||||
@@ -363,12 +847,12 @@ managed_config_prefix = "http://localhost:25500" # 托管配置前缀
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
|
||||
**如果这个项目对你有帮助,请给个 ⭐ Star 支持一下!**
|
||||
**如果这个项目对你有帮助,欢迎给一个 ⭐ Star 支持。**
|
||||
|
||||
Made with ❤️ by [Aethersailor](https://github.com/Aethersailor)
|
||||
|
||||
</div>
|
||||
|
||||
@@ -149,6 +149,45 @@ quanx_device_id=
|
||||
;surge_ssr_path=/usr/bin/ssr-local
|
||||
resolve_hostname=true
|
||||
|
||||
[security]
|
||||
;Security profile:
|
||||
;lan - default, legacy behavior for private/LAN deployments. Local, private
|
||||
; and fake-ip resources are allowed.
|
||||
;public - for Internet-facing deployments. Only untrusted request-controlled
|
||||
; fetches are restricted; built-in local templates and trusted config
|
||||
; files continue to work.
|
||||
;strict - same public fetch restrictions, and public upload cannot be enabled.
|
||||
;Environment override: SUBCONVERTER_SECURITY_PROFILE=lan|public|strict
|
||||
profile=lan
|
||||
;Only used by public profile. lan keeps legacy upload behavior; strict always
|
||||
;disables public upload.
|
||||
;Environment override: SUBCONVERTER_ALLOW_PUBLIC_UPLOAD=true|false
|
||||
allow_public_upload=false
|
||||
|
||||
[statistics]
|
||||
;Opt-in runtime statistics and /dashboard. Missing or false keeps it disabled.
|
||||
enabled=false
|
||||
;Put this directory on a Docker volume if statistics should survive restarts.
|
||||
data_dir=stats
|
||||
;Minimum seconds between persistence writes.
|
||||
flush_interval=5
|
||||
;header uses country-only headers such as CF-IPCountry and never stores IPs.
|
||||
;none records all countries as unknown.
|
||||
geo_provider=header
|
||||
country_headers=CF-IPCountry,X-Geo-Country,X-Vercel-IP-Country,CloudFront-Viewer-Country
|
||||
;Used for China regional statistics when visitor location headers are added by a trusted edge.
|
||||
china_region_headers=CF-Region-Code,cf-region-code,X-Geo-Subdivision
|
||||
;Optional Basic authentication for /dashboard and /dashboard/data.
|
||||
;Only applies when statistics enabled=true.
|
||||
;Missing or false keeps the dashboard password disabled.
|
||||
dashboard_auth_enabled=false
|
||||
dashboard_auth_username=
|
||||
dashboard_auth_password=
|
||||
;Failed login attempts allowed within dashboard_auth_window_seconds before lock applies.
|
||||
dashboard_auth_max_failures=5
|
||||
dashboard_auth_window_seconds=300
|
||||
dashboard_auth_lock_seconds=900
|
||||
|
||||
[emojis]
|
||||
add_emoji=false
|
||||
remove_old_emoji=true
|
||||
@@ -271,10 +310,13 @@ port=25500
|
||||
serve_file_root=web
|
||||
|
||||
[advanced]
|
||||
log_level=debug
|
||||
print_debug_info=true
|
||||
log_level=info
|
||||
print_debug_info=false
|
||||
max_pending_connections=10240
|
||||
max_concurrent_threads=4
|
||||
max_concurrent_threads=16
|
||||
;Maximum HTTP worker threads during bursts. Requests above this limit remain
|
||||
;queued instead of being rejected.
|
||||
max_server_threads=128
|
||||
max_allowed_rulesets=128
|
||||
max_allowed_rules=0
|
||||
max_allowed_download_size=0
|
||||
@@ -285,3 +327,7 @@ cache_ruleset=21600
|
||||
script_clean_context=true
|
||||
async_fetch_ruleset=true
|
||||
skip_failed_links=true
|
||||
enable_request_coalescing=true
|
||||
coalesce_retry_on_5xx=true
|
||||
;0 disables completed response caching. Values above 5 seconds are clamped to 5.
|
||||
response_cache_ttl=0
|
||||
|
||||
@@ -168,6 +168,48 @@ quanx_device_id = ""
|
||||
#surge_ssr_path = "/usr/bin/ssr-local"
|
||||
resolve_hostname = true
|
||||
|
||||
[security]
|
||||
# Security profile:
|
||||
# lan - default, legacy behavior for private/LAN deployments. Local, private
|
||||
# and fake-ip resources are allowed.
|
||||
# public - for Internet-facing deployments. Only untrusted request-controlled
|
||||
# fetches are restricted; built-in local templates and trusted config
|
||||
# files continue to work.
|
||||
# strict - same public fetch restrictions, and public upload cannot be enabled.
|
||||
# Environment override: SUBCONVERTER_SECURITY_PROFILE=lan|public|strict
|
||||
profile = "lan"
|
||||
# Only used by public profile. lan keeps legacy upload behavior; strict always
|
||||
# disables public upload.
|
||||
# Environment override: SUBCONVERTER_ALLOW_PUBLIC_UPLOAD=true|false
|
||||
allow_public_upload = false
|
||||
|
||||
[statistics]
|
||||
# Opt-in runtime statistics and /dashboard. Missing or false keeps it disabled
|
||||
# and avoids registering the dashboard or statistics-enabled request handler.
|
||||
enabled = false
|
||||
# Put this directory on a Docker volume if statistics should survive restarts.
|
||||
data_dir = "stats"
|
||||
# Minimum seconds between persistence writes.
|
||||
flush_interval = 5
|
||||
|
||||
[statistics.geo]
|
||||
# header uses country-only headers such as CF-IPCountry and never stores IPs.
|
||||
# none records all countries as unknown.
|
||||
provider = "header"
|
||||
country_headers = ["CF-IPCountry", "X-Geo-Country", "X-Vercel-IP-Country", "CloudFront-Viewer-Country"]
|
||||
|
||||
[statistics.dashboard_auth]
|
||||
# Optional Basic authentication for /dashboard and /dashboard/data.
|
||||
# Only applies when statistics.enabled is true.
|
||||
# Missing or false keeps the dashboard password disabled.
|
||||
enabled = false
|
||||
username = ""
|
||||
password = ""
|
||||
# Failed login attempts allowed within window_seconds before lock_seconds applies.
|
||||
max_failures = 5
|
||||
window_seconds = 300
|
||||
lock_seconds = 900
|
||||
|
||||
[emojis]
|
||||
add_emoji = false
|
||||
remove_old_emoji = true
|
||||
@@ -329,10 +371,13 @@ port = 25500
|
||||
serve_file_root = "web"
|
||||
|
||||
[advanced]
|
||||
log_level = "debug"
|
||||
print_debug_info = true
|
||||
log_level = "info"
|
||||
print_debug_info = false
|
||||
max_pending_connections = 10240
|
||||
max_concurrent_threads = 4
|
||||
max_concurrent_threads = 16
|
||||
# Maximum HTTP worker threads during bursts. Requests above this limit remain
|
||||
# queued instead of being rejected.
|
||||
max_server_threads = 128
|
||||
max_allowed_rulesets = 64
|
||||
max_allowed_rules = 0
|
||||
max_allowed_download_size = 0
|
||||
@@ -343,3 +388,7 @@ cache_ruleset = 21600
|
||||
script_clean_context = true
|
||||
async_fetch_ruleset = true
|
||||
skip_failed_links = true
|
||||
enable_request_coalescing = true
|
||||
coalesce_retry_on_5xx = true
|
||||
# 0 disables completed response caching. If enabled, values above 5 seconds are clamped to 5.
|
||||
response_cache_ttl = 0
|
||||
|
||||
@@ -68,6 +68,47 @@ surge_external_proxy:
|
||||
surge_ssr_path: "" # /usr/bin/ssr-local
|
||||
resolve_hostname: true
|
||||
|
||||
security:
|
||||
# Security profile:
|
||||
# lan - default, legacy behavior for private/LAN deployments. Local,
|
||||
# private and fake-ip resources are allowed.
|
||||
# public - for Internet-facing deployments. Only untrusted request-controlled
|
||||
# fetches are restricted; built-in local templates and trusted config
|
||||
# files continue to work.
|
||||
# strict - same public fetch restrictions, and public upload cannot be enabled.
|
||||
# Environment override: SUBCONVERTER_SECURITY_PROFILE=lan|public|strict
|
||||
profile: lan
|
||||
# Only used by public profile. lan keeps legacy upload behavior; strict always
|
||||
# disables public upload.
|
||||
# Environment override: SUBCONVERTER_ALLOW_PUBLIC_UPLOAD=true|false
|
||||
allow_public_upload: false
|
||||
|
||||
statistics:
|
||||
# Opt-in runtime statistics and /dashboard. Missing or false keeps it disabled.
|
||||
enabled: false
|
||||
# Put this directory on a Docker volume if statistics should survive restarts.
|
||||
data_dir: stats
|
||||
# Minimum seconds between persistence writes.
|
||||
flush_interval: 5
|
||||
geo:
|
||||
# header uses country-only headers such as CF-IPCountry and never stores IPs.
|
||||
# none records all countries as unknown.
|
||||
provider: header
|
||||
country_headers: ["CF-IPCountry", "X-Geo-Country", "X-Vercel-IP-Country", "CloudFront-Viewer-Country"]
|
||||
# Used for China regional statistics when visitor location headers are added by a trusted edge.
|
||||
china_region_headers: ["CF-Region-Code", "cf-region-code", "X-Geo-Subdivision"]
|
||||
dashboard_auth:
|
||||
# Optional Basic authentication for /dashboard and /dashboard/data.
|
||||
# Only applies when statistics.enabled is true.
|
||||
# Missing or false keeps the dashboard password disabled.
|
||||
enabled: false
|
||||
username: ""
|
||||
password: ""
|
||||
# Failed login attempts allowed within window_seconds before lock_seconds applies.
|
||||
max_failures: 5
|
||||
window_seconds: 300
|
||||
lock_seconds: 900
|
||||
|
||||
emojis:
|
||||
add_emoji: false
|
||||
remove_old_emoji: true
|
||||
@@ -142,10 +183,11 @@ server:
|
||||
serve_file_root: web
|
||||
|
||||
advanced:
|
||||
log_level: debug
|
||||
print_debug_info: true
|
||||
log_level: info
|
||||
print_debug_info: false
|
||||
max_pending_connections: 10240
|
||||
max_concurrent_threads: 4
|
||||
max_concurrent_threads: 16
|
||||
max_server_threads: 128
|
||||
max_allowed_rulesets: 64
|
||||
max_allowed_rules: 0
|
||||
max_allowed_download_size: 0
|
||||
@@ -156,3 +198,6 @@ advanced:
|
||||
script_clean_context: true
|
||||
async_fetch_ruleset: true
|
||||
skip_failed_links: true
|
||||
enable_request_coalescing: true
|
||||
coalesce_retry_on_5xx: true
|
||||
response_cache_ttl: 0
|
||||
|
||||
394
base/pref.toml
Normal file
394
base/pref.toml
Normal file
@@ -0,0 +1,394 @@
|
||||
version = 1
|
||||
[common]
|
||||
# API mode is hardcoded to true for security - cannot be configured
|
||||
# Token authentication is disabled - users must provide url parameter
|
||||
|
||||
|
||||
# Default URLs, used when no URL is provided in request, use "|" to separate multiple subscription links, supports local files/URL
|
||||
default_url = []
|
||||
|
||||
# Insert subscription links to requests. Can be used to add node(s) to all exported subscriptions.
|
||||
enable_insert = true
|
||||
# URLs to insert before subscription links, can be used to add node(s) to all exported subscriptions, supports local files/URL
|
||||
insert_url = [""]
|
||||
# Prepend inserted URLs to subscription links. Nodes in insert_url will be added to groups first with non-group-specific match pattern.
|
||||
prepend_insert_url = true
|
||||
|
||||
# Exclude nodes which remarks match the following patterns. Supports regular expression.
|
||||
exclude_remarks = ["(到期|剩余流量|时间|官网|产品)"]
|
||||
|
||||
# Only include nodes which remarks match the following patterns. Supports regular expression.
|
||||
#include_remarks = ["V3.*港"]
|
||||
|
||||
# Enable script support for filtering nodes
|
||||
enable_filter = false
|
||||
# Script used for filtering nodes. Supports inline script and script path. A "filter" function with 1 argument which is a node should be defined in the script.
|
||||
# Example: Inline script: set value to content of script.
|
||||
# Script path: set value to "path:/path/to/script.js".
|
||||
#filter_script = '''
|
||||
#function filter(node) {
|
||||
# const info = JSON.parse(node.ProxyInfo);
|
||||
# if(info.EncryptMethod.includes('chacha20'))
|
||||
# return true;
|
||||
# return false;
|
||||
#}
|
||||
#'''
|
||||
|
||||
default_external_config = "https://testingcf.jsdelivr.net/gh/Aethersailor/Custom_OpenClash_Rules@refs/heads/main/cfg/Custom_Clash.ini"
|
||||
|
||||
# The file scope limit of the 'rule_base' options in external configs.
|
||||
base_path = "base"
|
||||
|
||||
# Clash config base used by the generator, supports local files/URL
|
||||
clash_rule_base = "base/all_base.tpl"
|
||||
|
||||
# Surge config base used by the generator, supports local files/URL
|
||||
surge_rule_base = "base/all_base.tpl"
|
||||
|
||||
# Surfboard config base used by the generator, supports local files/URL
|
||||
surfboard_rule_base = "base/all_base.tpl"
|
||||
|
||||
# Mellow config base used by the generator, supports local files/URL
|
||||
mellow_rule_base = "base/all_base.tpl"
|
||||
|
||||
# Quantumult config base used by the generator, supports local files/URL
|
||||
quan_rule_base = "base/all_base.tpl"
|
||||
|
||||
# Quantumult X config base used by the generator, supports local files/URL
|
||||
quanx_rule_base = "base/all_base.tpl"
|
||||
|
||||
# Loon config base used by the generator, supports local files/URL
|
||||
loon_rule_base = "base/all_base.tpl"
|
||||
|
||||
# Shadowsocks Android config base used by the generator, supports local files/URL
|
||||
sssub_rule_base = "base/all_base.tpl"
|
||||
|
||||
# sing-box config base used by the generator, supports local files/URL
|
||||
singbox_rule_base = "base/all_base.tpl"
|
||||
|
||||
# Proxy used to download rulesets or subscriptions, set to NONE or empty to disable it, set to SYSTEM to use system proxy.
|
||||
# Accept cURL-supported proxies (http:// https:// socks4a:// socks5://)
|
||||
|
||||
proxy_config = "SYSTEM"
|
||||
proxy_ruleset = "SYSTEM"
|
||||
proxy_subscription = "NONE"
|
||||
|
||||
# Append a proxy type string ([SS] [SSR] [VMess]) to node remark.
|
||||
append_proxy_type = false
|
||||
|
||||
# When requesting /sub, reload this config file first.
|
||||
reload_conf_on_request = false
|
||||
|
||||
[[userinfo.stream_rule]]
|
||||
# Rules to extract stream data from node
|
||||
# Format: full_match_regex|new_format_regex
|
||||
# where new_format_regex should be like "total=$1&left=$2&used=$3"
|
||||
match = '^剩余流量:(.*?)\|总流量:(.*)$'
|
||||
replace = 'total=$2&left=$1'
|
||||
|
||||
[[userinfo.stream_rule]]
|
||||
match = '^剩余流量:(.*?) (.*)$'
|
||||
replace = 'total=$1&left=$2'
|
||||
|
||||
[[userinfo.stream_rule]]
|
||||
match = '^Bandwidth: (.*?)/(.*)$'
|
||||
replace = 'used=$1&total=$2'
|
||||
|
||||
[[userinfo.stream_rule]]
|
||||
match = '^.*剩余(.*?)(?:\s*?)@(?:.*)$'
|
||||
replace = 'total=$1'
|
||||
|
||||
[[userinfo.time_rule]]
|
||||
# Rules to extract expire time data from node
|
||||
# Format: full_match_regex|new_format_regex
|
||||
# where new_format_regex should follow this example: yyyy:mm:dd:hh:mm:ss
|
||||
match = '^过期时间:(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)$'
|
||||
replace = '$1:$2:$3:$4:$5:$6'
|
||||
|
||||
[[userinfo.time_rule]]
|
||||
match = '^到期时间:(\d+)-(\d+)-(\d+)$'
|
||||
replace = '$1:$2:$3:0:0:0'
|
||||
|
||||
[[userinfo.time_rule]]
|
||||
match = '^Smart Access expire: (\d+)/(\d+)/(\d+)$'
|
||||
replace = '$1:$2:$3:0:0:0'
|
||||
|
||||
[node_pref]
|
||||
#udp_flag = false
|
||||
#tcp_fast_open_flag = false
|
||||
#skip_cert_verify_flag = false
|
||||
#tls13_flag = false
|
||||
|
||||
sort_flag = false
|
||||
# Script used for sorting nodes. A "compare" function with 2 arguments which are the 2 nodes to be compared should be defined in the script. Supports inline script and script path.
|
||||
# Examples can be seen at the filter_script option in [common] section.
|
||||
#sort_script = '''
|
||||
#function compare(node_a, node_b) {
|
||||
# return info_a.Remark > info_b.Remark;
|
||||
#}
|
||||
#'''
|
||||
|
||||
filter_deprecated_nodes = false
|
||||
append_sub_userinfo = true
|
||||
clash_use_new_field_name = true
|
||||
|
||||
# Generate style of the proxies and proxy groups section of Clash subscriptions.
|
||||
# Supported styles: block, flow, compact
|
||||
# Block: - name: name1 Flow: - {name: name1, key: value} Compact: [{name: name1, key: value},{name: name2, key: value}]
|
||||
# key: value - {name: name2, key: value}
|
||||
# - name: name2
|
||||
# key: value
|
||||
clash_proxies_style = "flow"
|
||||
clash_proxy_groups_style = "flow"
|
||||
|
||||
# add Clash mode to sing-box rules, and add a GLOBAL group to end of outbounds
|
||||
singbox_add_clash_modes = true
|
||||
|
||||
[[node_pref.rename_node]]
|
||||
match = '\(?((x|X)?(\d+)(\.?\d+)?)((\s?倍率?)|(x|X))\)?'
|
||||
replace = "$1x"
|
||||
|
||||
[managed_config]
|
||||
# Append a '#!MANAGED-CONFIG' info to Surge configurations
|
||||
write_managed_config = true
|
||||
|
||||
# Address prefix for MANAGED-CONFIG info, without the trailing "/".
|
||||
managed_config_prefix = "http://127.0.0.1:25500"
|
||||
|
||||
# Managed config update interval in seconds, determine how long the config will be updated.
|
||||
config_update_interval = 86400
|
||||
|
||||
# If config_update_strict is set to true, Surge will require a force update after the interval.
|
||||
config_update_strict = false
|
||||
|
||||
# Device ID to be written to rewrite scripts for some version of Quantumult X
|
||||
quanx_device_id = ""
|
||||
|
||||
[surge_external_proxy]
|
||||
#surge_ssr_path = "/usr/bin/ssr-local"
|
||||
resolve_hostname = true
|
||||
|
||||
[security]
|
||||
# Security profile:
|
||||
# lan - default, legacy behavior for private/LAN deployments. Local, private
|
||||
# and fake-ip resources are allowed.
|
||||
# public - for Internet-facing deployments. Only untrusted request-controlled
|
||||
# fetches are restricted; built-in local templates and trusted config
|
||||
# files continue to work.
|
||||
# strict - same public fetch restrictions, and public upload cannot be enabled.
|
||||
# Environment override: SUBCONVERTER_SECURITY_PROFILE=lan|public|strict
|
||||
profile = "lan"
|
||||
# Only used by public profile. lan keeps legacy upload behavior; strict always
|
||||
# disables public upload.
|
||||
# Environment override: SUBCONVERTER_ALLOW_PUBLIC_UPLOAD=true|false
|
||||
allow_public_upload = false
|
||||
|
||||
[statistics]
|
||||
# Opt-in runtime statistics and /dashboard. Missing or false keeps it disabled
|
||||
# and avoids registering the dashboard or statistics-enabled request handler.
|
||||
enabled = false
|
||||
# Put this directory on a Docker volume if statistics should survive restarts.
|
||||
data_dir = "stats"
|
||||
# Minimum seconds between persistence writes.
|
||||
flush_interval = 5
|
||||
|
||||
[statistics.geo]
|
||||
# header uses country-only headers such as CF-IPCountry and never stores IPs.
|
||||
# none records all countries as unknown.
|
||||
provider = "header"
|
||||
country_headers = ["CF-IPCountry", "X-Geo-Country", "X-Vercel-IP-Country", "CloudFront-Viewer-Country"]
|
||||
|
||||
[statistics.dashboard_auth]
|
||||
# Optional Basic authentication for /dashboard and /dashboard/data.
|
||||
# Only applies when statistics.enabled is true.
|
||||
# Missing or false keeps the dashboard password disabled.
|
||||
enabled = false
|
||||
username = ""
|
||||
password = ""
|
||||
# Failed login attempts allowed within window_seconds before lock_seconds applies.
|
||||
max_failures = 5
|
||||
window_seconds = 300
|
||||
lock_seconds = 900
|
||||
|
||||
[emojis]
|
||||
add_emoji = false
|
||||
remove_old_emoji = true
|
||||
|
||||
[[emojis.emoji]]
|
||||
#match = '(流量|时间|应急)'
|
||||
#emoji = '🏳️🌈'
|
||||
import = "snippets/emoji.toml"
|
||||
|
||||
# [[custom_groups]]
|
||||
# name = "Auto"
|
||||
# type = "url-test"
|
||||
# rule = [".*"]
|
||||
# url = "http://www.gstatic.com/generate_204"
|
||||
# interval = 300
|
||||
# tolerance = 150
|
||||
# lazy = true
|
||||
|
||||
# [[custom_groups]]
|
||||
# name = "Proxy"
|
||||
# type = "select"
|
||||
# rule = [".*", "[]DIRECT"]
|
||||
# disable_udp = false
|
||||
|
||||
# [[custom_groups]]
|
||||
# name = "LoadBalance"
|
||||
# type = "load-balance"
|
||||
# rule = [".*", "[]Proxy", "[]DIRECT"]
|
||||
# interval = 100
|
||||
# strategy = "consistent-hashing"
|
||||
# url = "http://www.gstatic.com/generate_204"
|
||||
|
||||
[[custom_groups]]
|
||||
import = "snippets/groups.toml"
|
||||
|
||||
[ruleset]
|
||||
# Enable generating rules with rulesets
|
||||
enabled = true
|
||||
|
||||
# Overwrite the existing rules in rule_base
|
||||
overwrite_original_rules = false
|
||||
|
||||
# Perform a ruleset update on request
|
||||
update_ruleset_on_request = false
|
||||
|
||||
# [[rulesets]]
|
||||
# group = "Proxy"
|
||||
# ruleset = "https://raw.githubusercontent.com/DivineEngine/Profiles/master/Surge/Ruleset/Unbreak.list"
|
||||
# type = "surge-ruleset"
|
||||
# interval = 86400
|
||||
|
||||
[[rulesets]]
|
||||
import = "snippets/rulesets.toml"
|
||||
|
||||
[template]
|
||||
template_path = ""
|
||||
|
||||
[[template.globals]]
|
||||
key = "clash.http_port"
|
||||
value = "7890"
|
||||
|
||||
[[template.globals]]
|
||||
key = "clash.socks_port"
|
||||
value = "7891"
|
||||
|
||||
[[template.globals]]
|
||||
key = "clash.allow_lan"
|
||||
value = "true"
|
||||
|
||||
[[template.globals]]
|
||||
key = "clash.log_level"
|
||||
value = "info"
|
||||
|
||||
[[template.globals]]
|
||||
key = "clash.external_controller"
|
||||
value = "127.0.0.1:9090"
|
||||
|
||||
[[template.globals]]
|
||||
key = "singbox.allow_lan"
|
||||
value = "true"
|
||||
|
||||
[[template.globals]]
|
||||
key = "singbox.mixed_port"
|
||||
value = "2080"
|
||||
|
||||
[[aliases]]
|
||||
uri = "/clash"
|
||||
target = "/sub?target=clash"
|
||||
|
||||
[[aliases]]
|
||||
uri = "/clashr"
|
||||
target = "/sub?target=clashr"
|
||||
|
||||
[[aliases]]
|
||||
uri = "/surge"
|
||||
target = "/sub?target=surge"
|
||||
|
||||
[[aliases]]
|
||||
uri = "/quan"
|
||||
target = "/sub?target=quan"
|
||||
|
||||
[[aliases]]
|
||||
uri = "/quanx"
|
||||
target = "/sub?target=quanx"
|
||||
|
||||
[[aliases]]
|
||||
uri = "/mellow"
|
||||
target = "/sub?target=mellow"
|
||||
|
||||
[[aliases]]
|
||||
uri = "/surfboard"
|
||||
target = "/sub?target=surfboard"
|
||||
|
||||
[[aliases]]
|
||||
uri = "/loon"
|
||||
target = "/sub?target=loon"
|
||||
|
||||
[[aliases]]
|
||||
uri = "/singbox"
|
||||
target = "/sub?target=singbox"
|
||||
|
||||
[[aliases]]
|
||||
uri = "/ss"
|
||||
target = "/sub?target=ss"
|
||||
|
||||
[[aliases]]
|
||||
uri = "/ssd"
|
||||
target = "/sub?target=ssd"
|
||||
|
||||
[[aliases]]
|
||||
uri = "/sssub"
|
||||
target = "/sub?target=sssub"
|
||||
|
||||
[[aliases]]
|
||||
uri = "/ssr"
|
||||
target = "/sub?target=ssr"
|
||||
|
||||
[[aliases]]
|
||||
uri = "/v2ray"
|
||||
target = "/sub?target=v2ray"
|
||||
|
||||
[[aliases]]
|
||||
uri = "/trojan"
|
||||
target = "/sub?target=trojan"
|
||||
|
||||
[[aliases]]
|
||||
uri = "/test"
|
||||
target = "/render?path=templates/test.tpl"
|
||||
|
||||
#[[tasks]]
|
||||
#name = "tick"
|
||||
#cronexp = "0/10 * * * * ?"
|
||||
#path = "tick.js"
|
||||
#timeout = 3
|
||||
|
||||
[server]
|
||||
listen = "0.0.0.0"
|
||||
port = 25500
|
||||
serve_file_root = "web"
|
||||
|
||||
[advanced]
|
||||
log_level = "info"
|
||||
print_debug_info = false
|
||||
max_pending_connections = 10240
|
||||
max_concurrent_threads = 16
|
||||
# Maximum HTTP worker threads during bursts. Requests above this limit remain
|
||||
# queued instead of being rejected.
|
||||
max_server_threads = 128
|
||||
max_allowed_rulesets = 64
|
||||
max_allowed_rules = 0
|
||||
max_allowed_download_size = 0
|
||||
enable_cache = true
|
||||
cache_subscription = 60
|
||||
cache_config = 300
|
||||
cache_ruleset = 21600
|
||||
script_clean_context = true
|
||||
async_fetch_ruleset = true
|
||||
skip_failed_links = true
|
||||
enable_request_coalescing = true
|
||||
coalesce_retry_on_5xx = true
|
||||
# 0 disables completed response caching. If enabled, values above 5 seconds are clamped to 5.
|
||||
response_cache_ttl = 0
|
||||
146
bridge/README.md
146
bridge/README.md
@@ -1,100 +1,90 @@
|
||||
# Mihomo Parser Bridge - Quick Start Guide
|
||||
# Mihomo Parser Bridge
|
||||
|
||||
## 📦 What's Been Done
|
||||
`bridge/` provides the CGO wrapper that lets SubConverter-Extended reuse
|
||||
Mihomo's native subscription parser.
|
||||
|
||||
已为 SubConverter-Extended 集成 mihomo 的节点解析器(通过 CGO)。
|
||||
## Current status
|
||||
|
||||
### 新增文件
|
||||
The bridge is integrated into the C++ build:
|
||||
|
||||
| 文件 | 用途 |
|
||||
| ------ | ------ |
|
||||
| `bridge/converter.go` | Go 包装函数(调用 mihomo) |
|
||||
| `bridge/go.mod` | Go 依赖管理 |
|
||||
| `bridge/build.sh` | 本地编译脚本 |
|
||||
| `src/parser/mihomo_bridge.h` | C++ 头文件 |
|
||||
| `src/parser/mihomo_bridge.cpp` | C++ 实现 |
|
||||
- `bridge/converter.go` exports `ConvertSubscription` and `FreeString`.
|
||||
- `src/parser/mihomo_bridge.cpp` calls the exported Go functions and converts
|
||||
Mihomo JSON output into C++ proxy nodes.
|
||||
- `src/generator/config/nodemanip.cpp` uses the Mihomo parser when
|
||||
`USE_MIHOMO_PARSER` is defined, then falls back to the legacy parser on
|
||||
parser errors.
|
||||
- `CMakeLists.txt` enables `USE_MIHOMO_PARSER` automatically when either
|
||||
`bridge/libmihomo.so` or `bridge/libmihomo.a` is present.
|
||||
|
||||
### 修改文件
|
||||
## Build modes
|
||||
|
||||
| 文件 | 修改内容 |
|
||||
| ------ | ---------- |
|
||||
| `CMakeLists.txt` | 链接 Go 静态库 |
|
||||
| `Dockerfile` | Alpine 版,添加 Go 编译阶段 |
|
||||
| `Dockerfile.debian` | Debian 版,用于 glibc 二进制 |
|
||||
### Alpine Docker image
|
||||
|
||||
## 🚀 如何编译(Docker)
|
||||
The default `Dockerfile` builds `libmihomo.so` with `go build
|
||||
-buildmode=c-shared`.
|
||||
|
||||
```bash
|
||||
# 在项目根目录执行(使用 Alpine 版)
|
||||
docker build -t subconverter:mihomo .
|
||||
This is the preferred Alpine path because the Go runtime boundary stays inside
|
||||
the shared object, avoiding the musl initialization crash seen with static
|
||||
`c-archive` linking.
|
||||
|
||||
# 或使用 Debian 版
|
||||
docker build -f Dockerfile.debian -t subconverter:mihomo-debian .
|
||||
```
|
||||
### Debian Docker image
|
||||
|
||||
**编译流程**:
|
||||
`docker/Dockerfile.debian` builds `libmihomo.a` with `go build
|
||||
-buildmode=c-archive`.
|
||||
|
||||
1. 第一阶段(Go):编译 `libmihomo.a`
|
||||
2. 第二阶段(C++):编译 subconverter 并链接 Go 库
|
||||
3. 第三阶段:打包最终镜像
|
||||
This path is kept for glibc-based binary builds where static archive linking is
|
||||
stable and easier to package.
|
||||
|
||||
**预期时间**:首次约 7 分钟(有缓存后 ~4 分钟)
|
||||
## Local development
|
||||
|
||||
## 🧪 如何测试
|
||||
|
||||
### 1. 运行容器
|
||||
|
||||
```bash
|
||||
docker run -d -p 25500:25500 subconverter:mihomo
|
||||
# 默认时区已设为 Asia/Shanghai,如需覆盖可传入:
|
||||
# docker run -d -p 25500:25500 -e TZ=Asia/Shanghai subconverter:mihomo
|
||||
```
|
||||
|
||||
### 2. 测试节点解析
|
||||
|
||||
```bash
|
||||
# 测试 SS 链接
|
||||
curl "http://localhost:25500/sub?target=clash&url=ss://..."
|
||||
|
||||
# 测试 VMess 链接
|
||||
curl "http://localhost:25500/sub?target=clash&url=vmess://..."
|
||||
```
|
||||
|
||||
### 3. 验证 mihomo 兼容性
|
||||
|
||||
对比生成的配置与 mihomo 原生解析的结果应该完全一致。
|
||||
|
||||
## ⚠️ 已知问题
|
||||
|
||||
### IDE Lint 错误
|
||||
|
||||
当前 IDE 会报错(缺少 `libmihomo.h`),这是正常的,因为该文件在 Docker 编译时生成。
|
||||
|
||||
**解决方案**:
|
||||
|
||||
1. 本地安装 Go(如果需要本地开发)
|
||||
2. 运行 `cd bridge && bash build.sh`
|
||||
3. IDE 错误会消失
|
||||
|
||||
## 📝 后续步骤
|
||||
|
||||
1. ✅ 构建系统已集成
|
||||
2. ⏳ 等待 Docker 构建测试
|
||||
3. ⏳ 集成到 `src/handler/interfaces.cpp`(调用mihomo::parseSubscription)
|
||||
4. ⏳ 添加单元测试
|
||||
|
||||
## 💡 如何更新 mihomo
|
||||
If your IDE reports that `libmihomo.h` is missing, build the bridge locally:
|
||||
|
||||
```bash
|
||||
cd bridge
|
||||
go get -u github.com/metacubex/mihomo
|
||||
bash build.sh
|
||||
```
|
||||
|
||||
The generated artifacts are:
|
||||
|
||||
- `bridge/libmihomo.h`
|
||||
- `bridge/libmihomo.a` or `bridge/libmihomo.so`, depending on the build path
|
||||
|
||||
The Docker build also regenerates:
|
||||
|
||||
- `src/parser/mihomo_schemes.h`
|
||||
- `src/parser/param_compat.h`
|
||||
|
||||
## Updating Mihomo
|
||||
|
||||
The current checked-in Go module pins the Mihomo module version in `go.mod`.
|
||||
When intentionally updating Mihomo, update and verify the bridge from
|
||||
`bridge/`:
|
||||
|
||||
```bash
|
||||
go get github.com/metacubex/mihomo@<version-or-ref>
|
||||
go mod tidy
|
||||
```
|
||||
|
||||
然后重新构建 Docker 镜像即可。
|
||||
Then regenerate the parser compatibility headers and rebuild the Docker image.
|
||||
|
||||
## 📄 许可证
|
||||
## Testing notes
|
||||
|
||||
本模块(`bridge/`)使用的 Mihomo 解析器源自 [metacubex/mihomo](https://github.com/metacubex/mihomo),遵循 **MIT License**。
|
||||
There is no dedicated automated test suite for the bridge yet. Before changing
|
||||
parser behavior, manually compare representative `ss://`, `vmess://`,
|
||||
`trojan://`, `hysteria2://`, and mixed subscriptions against the generated
|
||||
Clash/Mihomo output.
|
||||
|
||||
整个 SubConverter-Extended 项目遵循 **GPL-3.0 License**。根据许可证兼容性,MIT 代码可以在 GPL-3.0 项目中使用,但整体项目仍然受 GPL-3.0 约束。
|
||||
Recommended future coverage:
|
||||
|
||||
- Go unit tests for subscription preprocessing in `converter.go`.
|
||||
- C++ tests for `mihomo_bridge.cpp` JSON conversion and error handling.
|
||||
- Snapshot tests for `/sub?target=clash` with representative node links.
|
||||
|
||||
## License
|
||||
|
||||
The Mihomo parser dependency comes from
|
||||
[metacubex/mihomo](https://github.com/metacubex/mihomo), which is licensed
|
||||
under the MIT License.
|
||||
|
||||
SubConverter-Extended is licensed under GPL-3.0. MIT-licensed code can be used
|
||||
in this GPL-3.0 project, while the combined project remains GPL-3.0 licensed.
|
||||
|
||||
@@ -20,7 +20,7 @@ go build \
|
||||
-buildmode=c-archive \
|
||||
-ldflags="-s -w" \
|
||||
-o libmihomo.a \
|
||||
converter.go
|
||||
.
|
||||
|
||||
echo "==> Build完成!"
|
||||
echo "Generated files:"
|
||||
|
||||
@@ -6,41 +6,11 @@ package main
|
||||
import "C"
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/metacubex/mihomo/common/convert"
|
||||
)
|
||||
|
||||
// preprocessSubscription fixes URL encoding issues in subscription links
|
||||
// Decodes the entire URL line to ensure Mihomo parser receives properly unencoded links
|
||||
func preprocessSubscription(subscription string) string {
|
||||
lines := strings.Split(subscription, "\n")
|
||||
var result []string
|
||||
|
||||
for _, line := range lines {
|
||||
line = strings.TrimRight(line, " \r")
|
||||
if line == "" {
|
||||
result = append(result, line)
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode the entire URL line
|
||||
// This fixes issues like v2rayN's uuid%3Apassword encoding
|
||||
// Safe for all protocols: url.QueryUnescape only decodes %XX patterns
|
||||
// and leaves structural characters (://, @, ?, #) intact
|
||||
if decoded, err := url.QueryUnescape(line); err == nil {
|
||||
line = decoded
|
||||
}
|
||||
// If decoding fails (malformed %), keep original line
|
||||
|
||||
result = append(result, line)
|
||||
}
|
||||
|
||||
return strings.Join(result, "\n")
|
||||
}
|
||||
|
||||
// ConvertSubscription converts V2Ray subscription links to mihomo proxy configs
|
||||
//
|
||||
//export ConvertSubscription
|
||||
|
||||
@@ -2,7 +2,7 @@ module github.com/aethersailor/subconverter-extended/bridge
|
||||
|
||||
go 1.25.5
|
||||
|
||||
require github.com/metacubex/mihomo v1.19.21
|
||||
require github.com/metacubex/mihomo v1.19.26
|
||||
|
||||
require (
|
||||
github.com/RyuaNerin/go-krypto v1.3.0 // indirect
|
||||
@@ -19,12 +19,12 @@ require (
|
||||
github.com/metacubex/cpu v0.1.1 // indirect
|
||||
github.com/metacubex/hkdf v0.1.0 // indirect
|
||||
github.com/metacubex/hpke v0.1.0 // indirect
|
||||
github.com/metacubex/http v0.1.0 // indirect
|
||||
github.com/metacubex/http v0.1.6 // indirect
|
||||
github.com/metacubex/mlkem v0.1.0 // indirect
|
||||
github.com/metacubex/randv2 v0.2.0 // indirect
|
||||
github.com/metacubex/sing v0.5.7 // indirect
|
||||
github.com/metacubex/sing-shadowsocks v0.2.12 // indirect
|
||||
github.com/metacubex/tls v0.1.4 // indirect
|
||||
github.com/metacubex/tls v0.1.6 // indirect
|
||||
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
|
||||
github.com/samber/lo v1.53.0 // indirect
|
||||
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
|
||||
|
||||
172
bridge/go.sum
172
bridge/go.sum
@@ -4,11 +4,25 @@ github.com/RyuaNerin/testingutil v0.1.0 h1:IYT6JL57RV3U2ml3dLHZsVtPOP6yNK7WUVdzz
|
||||
github.com/RyuaNerin/testingutil v0.1.0/go.mod h1:yTqj6Ta/ycHMPJHRyO12Mz3VrvTloWOsy23WOZH19AA=
|
||||
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 h1:cDVUiFo+npB0ZASqnw4q90ylaVAbnYyx0JYqK4YcGok=
|
||||
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk=
|
||||
github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A=
|
||||
github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw=
|
||||
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
|
||||
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
||||
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
||||
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
|
||||
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
|
||||
github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc=
|
||||
github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dunglas/httpsfv v1.0.2 h1:iERDp/YAfnojSDJ7PW3dj1AReJz4MrwbECSSE59JWL0=
|
||||
github.com/dunglas/httpsfv v1.0.2/go.mod h1:zID2mqw9mFsnt7YC3vYQ9/cjq30q41W+1AnDwH8TiMg=
|
||||
github.com/enfein/mieru/v3 v3.33.0 h1:hv2jK8nqYHwpSG86U2rpZR2I8Aff1/J3ifRmd9NBbFc=
|
||||
github.com/enfein/mieru/v3 v3.33.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
|
||||
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo=
|
||||
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
|
||||
github.com/ericlagergren/polyval v0.0.0-20230805202542-18692a1b76f9 h1:NUmyvuwVoDsIFzOGFKW4zpCtQTbX2T4JpSn1jal64gM=
|
||||
@@ -21,40 +35,150 @@ github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 h1:fuGucgPk5d
|
||||
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010/go.mod h1:JtBcj7sBuTTRupn7c2bFspMDIObMJsVK8TeUvpShPok=
|
||||
github.com/ericlagergren/testutil v0.0.0-20220814024112-d21c9429edc2 h1:j9adob+s2qXdvdeJywrVifDfHAIq0XwoaK/0q4D1BGw=
|
||||
github.com/ericlagergren/testutil v0.0.0-20220814024112-d21c9429edc2/go.mod h1:E4aJHbNMb6zjyVd1Mrpf3FIJ6kAtnVUq2yl0T6DHZ/I=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
|
||||
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
|
||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
|
||||
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
|
||||
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg=
|
||||
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU=
|
||||
github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0=
|
||||
github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
||||
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I=
|
||||
github.com/google/tink/go v1.6.1/go.mod h1:IGW53kTgag+st5yPhKKwJ6u2l+SSp5/v9XF7spovjlY=
|
||||
github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=
|
||||
github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 h1:q3OEI9RaN/wwcx+qgGo6ZaoJkCiDYe/gjDLfq7lQQF4=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905/go.mod h1:VvGYjkZoJyKqlmT1yzakUs4mfKMNB0XdODP0+rdml6k=
|
||||
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I=
|
||||
github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E=
|
||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
||||
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/klauspost/reedsolomon v1.12.3 h1:tzUznbfc3OFwJaTebv/QdhnFf2Xvb7gZ24XaHLBPmdc=
|
||||
github.com/klauspost/reedsolomon v1.12.3/go.mod h1:3K5rXwABAvzGeR01r6pWZieUALXO/Tq7bFKGIb4m4WI=
|
||||
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
|
||||
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
||||
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
|
||||
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20251104174305-5a0e9f7e361d h1:vAJ0ZT4aO803F1uw2roIA9yH7Sxzox34tVVyye1bz6c=
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20251104174305-5a0e9f7e361d/go.mod h1:MsM/5czONyXMJ3PRr5DbQ4O/BxzAnJWOIcJdLzW6qHY=
|
||||
github.com/metacubex/ascon v0.1.0 h1:6ZWxmXYszT1XXtwkf6nxfFhc/OTtQ9R3Vyj1jN32lGM=
|
||||
github.com/metacubex/ascon v0.1.0/go.mod h1:eV5oim4cVPPdEL8/EYaTZ0iIKARH9pnhAK/fcT5Kacc=
|
||||
github.com/metacubex/bart v0.26.0 h1:d/bBTvVatfVWGfQbiDpYKI1bXUJgjaabB2KpK1Tnk6w=
|
||||
github.com/metacubex/bart v0.26.0/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI=
|
||||
github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b h1:j7dadXD8I2KTmMt8jg1JcaP1ANL3JEObJPdANKcSYPY=
|
||||
github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b/go.mod h1:+WmP0VJZDkDszvpa83HzfUp6QzARl/IKkMorH4+nODw=
|
||||
github.com/metacubex/blake3 v0.1.0 h1:KGnjh/56REO7U+cgZA8dnBhxdP7jByrG7hTP+bu6cqY=
|
||||
github.com/metacubex/blake3 v0.1.0/go.mod h1:CCkLdzFrqf7xmxCdhQFvJsRRV2mwOLDoSPg6vUTB9Uk=
|
||||
github.com/metacubex/chacha v0.1.5 h1:fKWMb/5c7ZrY8Uoqi79PPFxl+qwR7X/q0OrsAubyX2M=
|
||||
github.com/metacubex/chacha v0.1.5/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
|
||||
github.com/metacubex/connect-ip-go v0.0.0-20260412152424-e1625567920a h1:Ph5UfTWDsGruZ+v95Df1ycTflQFmpZBFg2LUvj2kx/M=
|
||||
github.com/metacubex/connect-ip-go v0.0.0-20260412152424-e1625567920a/go.mod h1:xYC8Ik7/rN6no+vTRuWMEziGwm3brA0wNM/zZP9qhOQ=
|
||||
github.com/metacubex/cpu v0.1.1 h1:rRV5HGmeuGzjiKI3hYbL0dCd0qGwM7VUtk4ICXD06mI=
|
||||
github.com/metacubex/cpu v0.1.1/go.mod h1:09VEt4dSRLR+bOA8l4w4NDuzGZ8n5dkMv7e8axgEeTU=
|
||||
github.com/metacubex/edwards25519 v1.2.0 h1:pIQZLBsjQgg3Nl/c86YYFEUAbL5qQRnPq4LrgIw0KK4=
|
||||
github.com/metacubex/edwards25519 v1.2.0/go.mod h1:NCQF3J/Ki7382FJuokwsywEIIEI/gro/3smyXgQJsx0=
|
||||
github.com/metacubex/fswatch v0.1.1 h1:jqU7C/v+g0qc2RUFgmAOPoVvfl2BXXUXEumn6oQuxhU=
|
||||
github.com/metacubex/fswatch v0.1.1/go.mod h1:czrTT7Zlbz7vWft8RQu9Qqh+JoX+Nnb+UabuyN1YsgI=
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
|
||||
github.com/metacubex/gvisor v0.0.0-20251227095601-261ec1326fe8 h1:hUL81H0Ic/XIDkvtn9M1pmfDdfid7JzYQToY4Ps1TvQ=
|
||||
github.com/metacubex/gvisor v0.0.0-20251227095601-261ec1326fe8/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU=
|
||||
github.com/metacubex/hkdf v0.1.0 h1:fPA6VzXK8cU1foc/TOmGCDmSa7pZbxlnqhl3RNsthaA=
|
||||
github.com/metacubex/hkdf v0.1.0/go.mod h1:3seEfds3smgTAXqUGn+tgEJH3uXdsUjOiduG/2EtvZ4=
|
||||
github.com/metacubex/hpke v0.1.0 h1:gu2jUNhraehWi0P/z5HX2md3d7L1FhPQE6/Q0E9r9xQ=
|
||||
github.com/metacubex/hpke v0.1.0/go.mod h1:vfDm6gfgrwlXUxKDkWbcE44hXtmc1uxLDm2BcR11b3U=
|
||||
github.com/metacubex/http v0.1.0 h1:Jcy0I9zKjYijSUaksZU34XEe2xNdoFkgUTB7z7K5q0o=
|
||||
github.com/metacubex/http v0.1.0/go.mod h1:Nxx0zZAo2AhRfanyL+fmmK6ACMtVsfpwIl1aFAik2Eg=
|
||||
github.com/metacubex/mihomo v1.19.21 h1:1VNlN124abEZQyRVc1u7vmFAxsm5p/u753EY2Gb/hac=
|
||||
github.com/metacubex/mihomo v1.19.21/go.mod h1:6MgQnQfKgHAnZ/ThGfD2qNO5b9Q0i4oxiARYaVo2syI=
|
||||
github.com/metacubex/http v0.1.6 h1:xvXuvXMCMxCWMF5nEJF4yiKvXL+p2atWMzs37e80m1I=
|
||||
github.com/metacubex/http v0.1.6/go.mod h1:Nxx0zZAo2AhRfanyL+fmmK6ACMtVsfpwIl1aFAik2Eg=
|
||||
github.com/metacubex/jsonv2 v0.0.0-20260518173308-f4597c22f1df h1:S0vBzqjXok24VopstOgPd1JdgglW9tXehrqvwpQWbQ8=
|
||||
github.com/metacubex/jsonv2 v0.0.0-20260518173308-f4597c22f1df/go.mod h1:F4sVXat6QjPXkNsKRDyyG3BhSkxPFFnRPEIwmmyCgbg=
|
||||
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604 h1:hJwCVlE3ojViC35MGHB+FBr8TuIf3BUFn2EQ1VIamsI=
|
||||
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604/go.mod h1:lpmN3m269b3V5jFCWtffqBLS4U3QQoIid9ugtO+OhVc=
|
||||
github.com/metacubex/mihomo v1.19.26 h1:zTOrwEzgji2N6jFZwe6411hCbKmV1VGxVYZFKriX6uw=
|
||||
github.com/metacubex/mihomo v1.19.26/go.mod h1:+L7tiesjMrF9I8lzTRsAFmHM9yh9RYdMEQP2tYnJqa8=
|
||||
github.com/metacubex/mlkem v0.1.0 h1:wFClitonSFcmipzzQvax75beLQU+D7JuC+VK1RzSL8I=
|
||||
github.com/metacubex/mlkem v0.1.0/go.mod h1:amhaXZVeYNShuy9BILcR7P0gbeo/QLZsnqCdL8U2PDQ=
|
||||
github.com/metacubex/qpack v0.6.0 h1:YqClGIMOpiRYLjV1qOs483Od08MdPgRnHjt90FuaAKw=
|
||||
github.com/metacubex/qpack v0.6.0/go.mod h1:lKGSi7Xk94IMvHGOmxS9eIei3bvIqpOAImEBsaOwTkA=
|
||||
github.com/metacubex/quic-go v0.59.1-0.20260520020949-fcd18c7b6ace h1:KXacx7dp1GYVMgxezwXRt5BMsEbvAYuA6rPFUmdAvcQ=
|
||||
github.com/metacubex/quic-go v0.59.1-0.20260520020949-fcd18c7b6ace/go.mod h1:2YEQEvFrZ5V76oynMBDTlN+4fdnSHCa2uNJxv3cm1HU=
|
||||
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
|
||||
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
|
||||
github.com/metacubex/restls-client-go v0.1.7 h1:eCwiXCTQb5WJu9IlgYvDBA1OgrINv58dEe7hcN5H15k=
|
||||
github.com/metacubex/restls-client-go v0.1.7/go.mod h1:BN/U52vPw7j8VTSh2vleD/MnmVKCov84mS5VcjVHH4g=
|
||||
github.com/metacubex/sing v0.5.7 h1:8OC+fhKFSv/l9ehEhJRaZZAOuthfZo68SteBVLe8QqM=
|
||||
github.com/metacubex/sing v0.5.7/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
|
||||
github.com/metacubex/sing-mux v0.3.9 h1:/aoBD2+sK2qsXDlNDe3hkR0GZuFDtwIZhOeGUx9W0Yk=
|
||||
github.com/metacubex/sing-mux v0.3.9/go.mod h1:8bT7ZKT3clRrJjYc/x5CRYibC1TX/bK73a3r3+2E+Fc=
|
||||
github.com/metacubex/sing-quic v0.0.0-20260527143057-68e10a6afdc3 h1:PnMby5+kZXTl/CFDHfxMbMTaSRD+uMKMsrDYVQyAmX8=
|
||||
github.com/metacubex/sing-quic v0.0.0-20260527143057-68e10a6afdc3/go.mod h1:6ayFGfzzBE85csgQkM3gf4neFq6s0losHlPRSxY+nuk=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.12 h1:Wqzo8bYXrK5aWqxu/TjlTnYZzAKtKsaFQBdr6IHFaBE=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.12/go.mod h1:2e5EIaw0rxKrm1YTRmiMnDulwbGxH9hAFlrwQLQMQkU=
|
||||
github.com/metacubex/tls v0.1.4 h1:Gm5GrkyMUh52gYOMIAQ1kHIym4v4M3Qb87Wsmd8Kpdc=
|
||||
github.com/metacubex/tls v0.1.4/go.mod h1:0XeVdL0cBw+8i5Hqy3lVeP9IyD/LFTq02ExvHM6rzEM=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.7 h1:hSuuc0YpsfiqYqt1o+fP4m34BQz4e6wVj3PPBVhor3A=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.7/go.mod h1:vOEbfKC60txi0ca+yUlqEwOGc3Obl6cnSgx9Gf45KjE=
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20260517015314-c11c36474edc h1:8wLoFfYQ88iGPL+krQ5tJsI8IAmkFjKpQL2q+y3pvss=
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20260517015314-c11c36474edc/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E=
|
||||
github.com/metacubex/sing-vmess v0.2.5 h1:m9Zt5I27lB9fmLMZfism9sH2LcnAfShZfwSkf6/KJoE=
|
||||
github.com/metacubex/sing-vmess v0.2.5/go.mod h1:AwtlzUgf8COe9tRYAKqWZ+leDH7p5U98a0ZUpYehl8Q=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20260520151737-7e7c7c1b854c h1:tH9FuQW357zp2xAGzkoZTGpNGMVmEFZov0iV5M2S5ew=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20260520151737-7e7c7c1b854c/go.mod h1:eQZDJTx+IH3k4mXqaOJ3VJ9h9ZqOl60F7TLi5wAU51Q=
|
||||
github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141 h1:DK2l6m2Fc85H2BhiAPgbJygiWhesPlfGmF+9Vw6ARdk=
|
||||
github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141/go.mod h1:/yI4OiGOSn0SURhZdJF3CbtPg3nwK700bG8TZLMBvAg=
|
||||
github.com/metacubex/ssh v0.1.0 h1:iGfr99qk/eMHzUnQ/0bTxXT8+8SWqLSHBWDHoAhngzw=
|
||||
github.com/metacubex/ssh v0.1.0/go.mod h1:NUtl0d+/f2cG9ECEpMM8iCVOpmggQlC13oLeDUONDlU=
|
||||
github.com/metacubex/tailscale v0.0.0-20260520011538-f23132fac4b7 h1:LoJR4NMyNKHeEJoeGDtcsao7sV0NRkzMeV5H/0J0MIE=
|
||||
github.com/metacubex/tailscale v0.0.0-20260520011538-f23132fac4b7/go.mod h1:MAo3HhE7968rIwmDvYTYE8xCsV4x+hLnkChdXeP3X4c=
|
||||
github.com/metacubex/tailscale-wireguard-go v0.0.0-20260521124654-e1bf77ef79af h1:c60IbBMUq2h1M2m7+grMJJmBmrObxL8SwvNtm6Ozbwk=
|
||||
github.com/metacubex/tailscale-wireguard-go v0.0.0-20260521124654-e1bf77ef79af/go.mod h1:i3zLKytWkOnyT1i9OmiLevWvrN5J5HE1+yjE7UYNfcQ=
|
||||
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 h1:H6TnfM12tOoTizYE/qBHH3nEuibIelmHI+BVSxVJr8o=
|
||||
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||
github.com/metacubex/tls v0.1.6 h1:t2ubLneYa4ceyIC++54a57BLqZFA/QYUrhdjLk2GPwo=
|
||||
github.com/metacubex/tls v0.1.6/go.mod h1:0XeVdL0cBw+8i5Hqy3lVeP9IyD/LFTq02ExvHM6rzEM=
|
||||
github.com/metacubex/utls v1.8.4 h1:HmL9nUApDdWSkgUyodfwF6hSjtiwCGGdyhaSpEejKpg=
|
||||
github.com/metacubex/utls v1.8.4/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko=
|
||||
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f h1:FGBPRb1zUabhPhDrlKEjQ9lgIwQ6cHL4x8M9lrERhbk=
|
||||
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f/go.mod h1:oPGcV994OGJedmmxrcK9+ni7jUEMGhR+uVQAdaduIP4=
|
||||
github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49 h1:lhlqpYHopuTLx9xQt22kSA9HtnyTDmk5XjjQVCGHe2E=
|
||||
github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49/go.mod h1:MBeEa9IVBphH7vc3LNtW6ZujVXFizotPo3OEiHQ+TNU=
|
||||
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
|
||||
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
|
||||
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
|
||||
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
|
||||
github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU=
|
||||
github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
|
||||
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4=
|
||||
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7/go.mod h1:UqoUn6cHESlliMhOnKLWr+CBH+e3bazUPvFj1XZwAjs=
|
||||
github.com/openacid/low v0.1.21 h1:Tr2GNu4N/+rGRYdOsEHOE89cxUIaDViZbVmKz29uKGo=
|
||||
github.com/openacid/low v0.1.21/go.mod h1:q+MsKI6Pz2xsCkzV4BLj7NR5M4EX0sGz5AqotpZDVh0=
|
||||
github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
|
||||
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pires/go-proxyproto v0.8.0 h1:5unRmEAPbHXHuLjDg01CxJWf91cw3lKHc/0xzKpXEe0=
|
||||
github.com/pires/go-proxyproto v0.8.0/go.mod h1:iknsfgnH8EkjrMeMyvfKByp9TiBZCKZM0jx2xmKqnVY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e h1:dCWirM5F3wMY+cmRda/B1BiPsFtmzXqV9b0hLWtVBMs=
|
||||
github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e/go.mod h1:9leZcVcItj6m9/CfHY5Em/iBrCz7js8LcRQGTKEEv2M=
|
||||
github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0=
|
||||
github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
|
||||
github.com/samber/lo v1.53.0 h1:t975lj2py4kJPQ6haz1QMgtId2gtmfktACxIXArw3HM=
|
||||
github.com/samber/lo v1.53.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b h1:rXHg9GrUEtWZhEkrykicdND3VPjlVbYiLdX9J7gimS8=
|
||||
@@ -69,20 +193,56 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tailscale/certstore v0.1.1-0.20260409135935-3638fb84b77d h1:JcGKBZAL7ePLwOhUdN8qGQZlP5GueEiIZwY7R62pejE=
|
||||
github.com/tailscale/certstore v0.1.1-0.20260409135935-3638fb84b77d/go.mod h1:XrBNfAFN+pwoWuksbFS9Ccxnopa15zJGgXRFN90l3K4=
|
||||
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4=
|
||||
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg=
|
||||
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw=
|
||||
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8=
|
||||
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+yfntqhI3oAu9i27nEojcQ4NuBQOo5ZFA=
|
||||
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc/go.mod h1:f93CXfllFsO9ZQVq+Zocb1Gp4G5Fz0b0rXHLOzt/Djc=
|
||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
|
||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
|
||||
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 h1:UNrDfkQqiEYzdMlNsVvBYOAJWZjdktqFE9tQh5BT2+4=
|
||||
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7/go.mod h1:E+rxHvJG9H6PUdzq9NRG6csuLN3XUx98BfGOVWNYnXs=
|
||||
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo=
|
||||
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ=
|
||||
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek=
|
||||
go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0=
|
||||
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
|
||||
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
||||
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
|
||||
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
|
||||
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
||||
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -87,7 +87,13 @@ typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
// ConvertSubscription converts V2Ray subscription links to mihomo proxy configs
|
||||
//
|
||||
extern char* ConvertSubscription(char* data);
|
||||
|
||||
// FreeString frees memory allocated by Go (must be called from C++ after using the result)
|
||||
//
|
||||
extern void FreeString(char* s);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
181
bridge/preprocess.go
Normal file
181
bridge/preprocess.go
Normal file
@@ -0,0 +1,181 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// preprocessSubscription fixes URL encoding issues and legacy share links before
|
||||
// the subscription is handed to mihomo's parser.
|
||||
func preprocessSubscription(subscription string) string {
|
||||
lines := strings.Split(subscription, "\n")
|
||||
result := make([]string, 0, len(lines))
|
||||
|
||||
for _, line := range lines {
|
||||
line = strings.TrimRight(line, " \r")
|
||||
if line == "" {
|
||||
result = append(result, line)
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode the entire URL line. This fixes inputs such as v2rayN's
|
||||
// uuid%3Apassword encoding and keeps malformed percent escapes unchanged.
|
||||
if decoded, err := url.QueryUnescape(line); err == nil {
|
||||
line = decoded
|
||||
}
|
||||
|
||||
line = normalizeLegacyShadowrocketVMess(line)
|
||||
result = append(result, line)
|
||||
}
|
||||
|
||||
return strings.Join(result, "\n")
|
||||
}
|
||||
|
||||
func normalizeLegacyShadowrocketVMess(line string) string {
|
||||
const prefix = "vmess://"
|
||||
if !strings.HasPrefix(line, prefix) {
|
||||
return line
|
||||
}
|
||||
|
||||
body := strings.TrimPrefix(line, prefix)
|
||||
queryStart := strings.IndexByte(body, '?')
|
||||
if queryStart < 0 {
|
||||
return line
|
||||
}
|
||||
|
||||
encoded := body[:queryStart]
|
||||
rawQuery := body[queryStart+1:]
|
||||
if encoded == "" || rawQuery == "" {
|
||||
return line
|
||||
}
|
||||
|
||||
decoded, ok := decodeLooseBase64(encoded)
|
||||
if !ok {
|
||||
return line
|
||||
}
|
||||
|
||||
cipher, remainder, ok := strings.Cut(decoded, ":")
|
||||
if !ok {
|
||||
return line
|
||||
}
|
||||
uuid, serverPort, ok := strings.Cut(remainder, "@")
|
||||
if !ok {
|
||||
return line
|
||||
}
|
||||
server, port, ok := splitHostPortLoose(serverPort)
|
||||
if !ok || uuid == "" || server == "" || port == "" {
|
||||
return line
|
||||
}
|
||||
if _, err := strconv.Atoi(port); err != nil {
|
||||
return line
|
||||
}
|
||||
|
||||
query, err := url.ParseQuery(rawQuery)
|
||||
if err != nil {
|
||||
return line
|
||||
}
|
||||
|
||||
aid := firstQueryValue(query, "aid", "alterId")
|
||||
if aid == "" {
|
||||
aid = "0"
|
||||
}
|
||||
|
||||
network := "tcp"
|
||||
headerType := "none"
|
||||
host := ""
|
||||
path := ""
|
||||
obfs := strings.ToLower(firstQueryValue(query, "obfs"))
|
||||
switch obfs {
|
||||
case "websocket", "ws":
|
||||
network = "ws"
|
||||
host = firstQueryValue(query, "obfsParam", "host", "wsHost")
|
||||
path = firstQueryValue(query, "path", "wspath")
|
||||
case "":
|
||||
if value := firstQueryValue(query, "network", "net", "type"); value != "" {
|
||||
network = strings.ToLower(value)
|
||||
}
|
||||
host = firstQueryValue(query, "wsHost", "host")
|
||||
path = firstQueryValue(query, "wspath", "path")
|
||||
if value := firstQueryValue(query, "headerType"); value != "" {
|
||||
headerType = value
|
||||
}
|
||||
case "none":
|
||||
headerType = "none"
|
||||
default:
|
||||
headerType = obfs
|
||||
}
|
||||
|
||||
tls := ""
|
||||
switch strings.ToLower(firstQueryValue(query, "tls", "security")) {
|
||||
case "1", "true", "tls":
|
||||
tls = "tls"
|
||||
}
|
||||
|
||||
remarks := firstQueryValue(query, "remarks", "remark", "name")
|
||||
if remarks == "" {
|
||||
remarks = server + ":" + port
|
||||
}
|
||||
|
||||
values := map[string]string{
|
||||
"v": "2",
|
||||
"ps": remarks,
|
||||
"add": server,
|
||||
"port": port,
|
||||
"id": uuid,
|
||||
"aid": aid,
|
||||
"scy": cipher,
|
||||
"net": network,
|
||||
"type": headerType,
|
||||
"host": host,
|
||||
"path": path,
|
||||
"tls": tls,
|
||||
}
|
||||
if sni := firstQueryValue(query, "sni", "peer"); sni != "" {
|
||||
values["sni"] = sni
|
||||
}
|
||||
if alpn := firstQueryValue(query, "alpn"); alpn != "" {
|
||||
values["alpn"] = alpn
|
||||
}
|
||||
|
||||
jsonBytes, err := json.Marshal(values)
|
||||
if err != nil {
|
||||
return line
|
||||
}
|
||||
return prefix + base64.StdEncoding.EncodeToString(jsonBytes)
|
||||
}
|
||||
|
||||
func decodeLooseBase64(value string) (string, bool) {
|
||||
encodings := []*base64.Encoding{
|
||||
base64.RawURLEncoding,
|
||||
base64.URLEncoding,
|
||||
base64.RawStdEncoding,
|
||||
base64.StdEncoding,
|
||||
}
|
||||
for _, encoding := range encodings {
|
||||
decoded, err := encoding.DecodeString(value)
|
||||
if err == nil {
|
||||
return string(decoded), true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func splitHostPortLoose(value string) (string, string, bool) {
|
||||
colon := strings.LastIndexByte(value, ':')
|
||||
if colon <= 0 || colon == len(value)-1 {
|
||||
return "", "", false
|
||||
}
|
||||
return strings.Trim(value[:colon], "[]"), value[colon+1:], true
|
||||
}
|
||||
|
||||
func firstQueryValue(values url.Values, keys ...string) string {
|
||||
for _, key := range keys {
|
||||
if value := values.Get(key); value != "" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
55
bridge/preprocess_test.go
Normal file
55
bridge/preprocess_test.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/metacubex/mihomo/common/convert"
|
||||
)
|
||||
|
||||
func TestPreprocessLegacyShadowrocketVmess(t *testing.T) {
|
||||
input := strings.Join([]string{
|
||||
"vmess://YXV0bzowNmNlNzU4Yy1iNTNkLTQ2NzQtOTdhNy01M2U4YmFhOGQwMjlAMjE2LjE0NC4yMjQuNjk6MzMwNg?remarks=%E7%BE%8E%E5%9B%BD%E8%87%AA%E5%BB%BA&path=/&obfs=none&alterId=0",
|
||||
"vmess://YXV0bzpmMmJiMmE4ZC02YWM0LTQ3NGYtYjJlYS1lMjJjNzhlYjkwMGZAMTI5LjE1MS4yNS4xOjE4MjU?remarks=US-O&udp=1&alterId=0",
|
||||
}, "\n")
|
||||
|
||||
proxies, err := convert.ConvertsV2Ray([]byte(preprocessSubscription(input)))
|
||||
if err != nil {
|
||||
t.Fatalf("convert error: %v", err)
|
||||
}
|
||||
if len(proxies) != 2 {
|
||||
t.Fatalf("got %d proxies: %#v", len(proxies), proxies)
|
||||
}
|
||||
|
||||
first := proxies[0]
|
||||
if first["type"] != "vmess" {
|
||||
t.Fatalf("unexpected first type: %#v", first["type"])
|
||||
}
|
||||
if first["name"] != "美国自建" {
|
||||
t.Fatalf("unexpected first name: %#v", first["name"])
|
||||
}
|
||||
if first["server"] != "216.144.224.69" {
|
||||
t.Fatalf("unexpected first server: %#v", first["server"])
|
||||
}
|
||||
if first["port"] != "3306" {
|
||||
t.Fatalf("unexpected first port: %#v", first["port"])
|
||||
}
|
||||
if first["uuid"] != "06ce758c-b53d-4674-97a7-53e8baa8d029" {
|
||||
t.Fatalf("unexpected first uuid: %#v", first["uuid"])
|
||||
}
|
||||
|
||||
second := proxies[1]
|
||||
if second["server"] != "129.151.25.1" {
|
||||
t.Fatalf("unexpected second server: %#v", second["server"])
|
||||
}
|
||||
if second["port"] != "1825" {
|
||||
t.Fatalf("unexpected second port: %#v", second["port"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreprocessKeepsStandardVmess(t *testing.T) {
|
||||
input := "vmess://uuid@example.com:443?encryption=auto#name"
|
||||
if got := preprocessSubscription(input); got != input {
|
||||
t.Fatalf("standard vmess changed:\nwant %q\n got %q", input, got)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
find_path(LIBCRON_INCLUDE_DIR libcron/Cron.h)
|
||||
find_path(DATE_INCLUDE_DIR date/date.h)
|
||||
find_path(LIBCRON_INCLUDE_DIR libcron/Cron.h
|
||||
PATHS "${CMAKE_SOURCE_DIR}/include")
|
||||
find_path(DATE_INCLUDE_DIR date/date.h
|
||||
PATHS "${CMAKE_SOURCE_DIR}/include")
|
||||
|
||||
find_library(LIBCRON_LIBRARY libcron)
|
||||
|
||||
|
||||
19
cmake/Findtoml11.cmake
Normal file
19
cmake/Findtoml11.cmake
Normal file
@@ -0,0 +1,19 @@
|
||||
find_path(TOML11_INCLUDE_DIR
|
||||
NAMES toml.hpp
|
||||
PATHS "${CMAKE_SOURCE_DIR}/include"
|
||||
)
|
||||
|
||||
set(TOML11_INCLUDE_DIRS "${TOML11_INCLUDE_DIR}")
|
||||
set(toml11_INCLUDE_DIRS "${TOML11_INCLUDE_DIRS}")
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(toml11 DEFAULT_MSG TOML11_INCLUDE_DIR)
|
||||
|
||||
if(toml11_FOUND AND NOT TARGET toml11::toml11)
|
||||
add_library(toml11::toml11 INTERFACE IMPORTED)
|
||||
set_target_properties(toml11::toml11 PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${TOML11_INCLUDE_DIR}"
|
||||
)
|
||||
endif()
|
||||
|
||||
mark_as_advanced(TOML11_INCLUDE_DIR)
|
||||
19
cmake/linux-armv7hf.cmake
Normal file
19
cmake/linux-armv7hf.cmake
Normal file
@@ -0,0 +1,19 @@
|
||||
set(CMAKE_SYSTEM_NAME Linux)
|
||||
set(CMAKE_SYSTEM_PROCESSOR armv7)
|
||||
|
||||
set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc)
|
||||
set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++)
|
||||
set(CMAKE_AR arm-linux-gnueabihf-ar)
|
||||
set(CMAKE_RANLIB arm-linux-gnueabihf-ranlib)
|
||||
set(CMAKE_STRIP arm-linux-gnueabihf-strip)
|
||||
|
||||
set(CMAKE_LIBRARY_ARCHITECTURE arm-linux-gnueabihf)
|
||||
set(CMAKE_C_FLAGS_INIT "-march=armv7-a -mfpu=vfpv3-d16 -mfloat-abi=hard")
|
||||
set(CMAKE_CXX_FLAGS_INIT "-march=armv7-a -mfpu=vfpv3-d16 -mfloat-abi=hard")
|
||||
set(CMAKE_EXE_LINKER_FLAGS_INIT "-latomic -Wl,-rpath-link,/lib/arm-linux-gnueabihf -Wl,-rpath-link,/usr/lib/arm-linux-gnueabihf -Wl,-rpath-link,/usr/arm-linux-gnueabihf/lib")
|
||||
|
||||
set(CMAKE_FIND_ROOT_PATH /opt/armv7 /usr/arm-linux-gnueabihf)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH)
|
||||
23
design/favicon-dark-proposal.svg
Normal file
23
design/favicon-dark-proposal.svg
Normal file
@@ -0,0 +1,23 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="SubConverter-Extended icon">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="10" y1="8" x2="54" y2="56" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#0f172a"/>
|
||||
<stop offset="1" stop-color="#1e293b"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="cyan" x1="18" y1="20" x2="46" y2="20" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#38bdf8"/>
|
||||
<stop offset="1" stop-color="#22d3ee"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="green" x1="46" y1="44" x2="18" y2="44" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#34d399"/>
|
||||
<stop offset="1" stop-color="#84cc16"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="64" height="64" rx="14" fill="url(#bg)"/>
|
||||
<path d="M18 20h25" fill="none" stroke="url(#cyan)" stroke-width="6" stroke-linecap="round"/>
|
||||
<path d="M40 11l10 9-10 9" fill="none" stroke="#67e8f9" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M46 44H21" fill="none" stroke="url(#green)" stroke-width="6" stroke-linecap="round"/>
|
||||
<path d="M24 35l-10 9 10 9" fill="none" stroke="#bef264" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M24 31c0-6 4-10 9-10 3 0 6 1 8 3" fill="none" stroke="#f8fafc" stroke-width="5" stroke-linecap="round"/>
|
||||
<path d="M40 33c0 6-4 10-9 10-3 0-6-1-8-3" fill="none" stroke="#f8fafc" stroke-width="5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
24
design/favicon-light-proposal.svg
Normal file
24
design/favicon-light-proposal.svg
Normal file
@@ -0,0 +1,24 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="SubConverter-Extended icon">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="10" y1="8" x2="54" y2="56" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#ffffff"/>
|
||||
<stop offset="1" stop-color="#e8eef7"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="cyan" x1="18" y1="20" x2="46" y2="20" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#0284c7"/>
|
||||
<stop offset="1" stop-color="#0891b2"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="green" x1="46" y1="44" x2="18" y2="44" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#059669"/>
|
||||
<stop offset="1" stop-color="#65a30d"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="64" height="64" rx="14" fill="url(#bg)"/>
|
||||
<rect x="1.5" y="1.5" width="61" height="61" rx="12.5" fill="none" stroke="#cbd5e1" stroke-width="3"/>
|
||||
<path d="M18 20h25" fill="none" stroke="url(#cyan)" stroke-width="6" stroke-linecap="round"/>
|
||||
<path d="M40 11l10 9-10 9" fill="none" stroke="#06b6d4" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M46 44H21" fill="none" stroke="url(#green)" stroke-width="6" stroke-linecap="round"/>
|
||||
<path d="M24 35l-10 9 10 9" fill="none" stroke="#84cc16" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M24 31c0-6 4-10 9-10 3 0 6 1 8 3" fill="none" stroke="#172033" stroke-width="5" stroke-linecap="round"/>
|
||||
<path d="M40 33c0 6-4 10-9 10-3 0-6-1-8-3" fill="none" stroke="#172033" stroke-width="5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
117
design/favicon-preview.html
Normal file
117
design/favicon-preview.html
Normal file
@@ -0,0 +1,117 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Favicon proposal preview</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background: #f5f7fa;
|
||||
color: #172033;
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
}
|
||||
main {
|
||||
display: grid;
|
||||
gap: 24px;
|
||||
padding: 32px;
|
||||
}
|
||||
h1 {
|
||||
margin: 0 0 4px;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.group {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 18px;
|
||||
}
|
||||
.sample {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 96px;
|
||||
height: 72px;
|
||||
border: 1px solid #d8dee8;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
}
|
||||
.tab {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 260px;
|
||||
height: 40px;
|
||||
padding: 0 12px;
|
||||
border-radius: 8px 8px 0 0;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);
|
||||
font-size: 14px;
|
||||
}
|
||||
.dark {
|
||||
background: #0f172a;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
.label {
|
||||
min-width: 70px;
|
||||
color: #64748b;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="group">
|
||||
<h1>Light mode</h1>
|
||||
<div class="row">
|
||||
<span class="label">16px</span>
|
||||
<div class="sample"><img src="favicon-light-proposal.svg" width="16" height="16" alt=""></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="label">32px</span>
|
||||
<div class="sample"><img src="favicon-light-proposal.svg" width="32" height="32" alt=""></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="label">64px</span>
|
||||
<div class="sample"><img src="favicon-light-proposal.svg" width="64" height="64" alt=""></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="label">标签页</span>
|
||||
<div class="tab">
|
||||
<img src="favicon-light-proposal.svg" width="16" height="16" alt="">
|
||||
<span>SubConverter-Extended</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="group">
|
||||
<h1>Dark mode</h1>
|
||||
<div class="row">
|
||||
<span class="label">16px</span>
|
||||
<div class="sample dark"><img src="favicon-dark-proposal.svg" width="16" height="16" alt=""></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="label">32px</span>
|
||||
<div class="sample dark"><img src="favicon-dark-proposal.svg" width="32" height="32" alt=""></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="label">64px</span>
|
||||
<div class="sample dark"><img src="favicon-dark-proposal.svg" width="64" height="64" alt=""></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="label">标签页</span>
|
||||
<div class="tab dark">
|
||||
<img src="favicon-dark-proposal.svg" width="16" height="16" alt="">
|
||||
<span>SubConverter-Extended</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -6,13 +6,18 @@ services:
|
||||
container_name: "SubConverter-Extended"
|
||||
image: "aethersailor/subconverter-extended:latest"
|
||||
ports:
|
||||
# 自定义映射端口号
|
||||
- "25500:25500/tcp"
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
TZ: "Asia/Shanghai"
|
||||
# 可选:覆盖 [managed_config].managed_config_prefix / Optional: override [managed_config].managed_config_prefix
|
||||
MANAGED_CONFIG_PREFIX: "http://your-domain-or-ip:25500"
|
||||
volumes:
|
||||
- "./base/pref.toml:/base/pref.toml:ro"
|
||||
# 可选:持久化 /dashboard 统计数据;删除该目录即可清空统计。
|
||||
# Optional: persist /dashboard statistics; delete this directory to reset.
|
||||
- "./stats:/base/stats"
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
|
||||
246
docker/Dockerfile.armv7-cross
Normal file
246
docker/Dockerfile.armv7-cross
Normal file
@@ -0,0 +1,246 @@
|
||||
# ========== GO BUILD STAGE ==========
|
||||
FROM --platform=$BUILDPLATFORM mirror.gcr.io/library/golang:latest AS go-builder
|
||||
|
||||
ARG TARGETARCH
|
||||
ARG TARGETVARIANT
|
||||
ARG MIHOMO_REF="Meta"
|
||||
ARG MIHOMO_CACHE_BUST=1
|
||||
ARG REFRESH_GO_DEPS=false
|
||||
|
||||
WORKDIR /build/bridge
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
git build-essential crossbuild-essential-armhf ca-certificates && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY bridge/go.mod bridge/go.sum ./
|
||||
COPY bridge/converter.go ./
|
||||
COPY bridge/preprocess.go ./
|
||||
|
||||
RUN set -xe && \
|
||||
if [ "${REFRESH_GO_DEPS}" = "true" ]; then \
|
||||
echo "MIHOMO_CACHE_BUST=$MIHOMO_CACHE_BUST" && \
|
||||
go get github.com/metacubex/mihomo@${MIHOMO_REF} && \
|
||||
go get -u all && \
|
||||
go mod tidy; \
|
||||
else \
|
||||
go mod download; \
|
||||
fi
|
||||
|
||||
COPY scripts/ ../scripts/
|
||||
RUN go run ../scripts/generate_schemes.go mihomo_schemes.h
|
||||
RUN go run ../scripts/generate_param_compat.go -o param_compat.h
|
||||
|
||||
RUN echo "==> Cross-compiling Go bridge for linux/arm/v7" && \
|
||||
CGO_ENABLED=1 \
|
||||
GOOS=linux \
|
||||
GOARCH=arm \
|
||||
GOARM=7 \
|
||||
CC=arm-linux-gnueabihf-gcc \
|
||||
go build \
|
||||
-trimpath \
|
||||
-buildmode=c-archive \
|
||||
-o libmihomo.a \
|
||||
.
|
||||
|
||||
RUN ls -lh libmihomo.a libmihomo.h
|
||||
|
||||
# ========== C++ CROSS BUILD STAGE ==========
|
||||
FROM --platform=$BUILDPLATFORM mirror.gcr.io/library/debian:trixie AS builder
|
||||
|
||||
ARG THREADS="4"
|
||||
ARG SHA=""
|
||||
ARG VERSION="dev"
|
||||
ARG BUILD_DATE=""
|
||||
ARG REFRESH_HEADERS=false
|
||||
ARG SOURCE_DEPS_CACHE_BUST=stable
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
ENV PKG_CONFIG_ALLOW_CROSS=1
|
||||
ENV PKG_CONFIG_LIBDIR=/usr/lib/arm-linux-gnueabihf/pkgconfig:/usr/share/pkgconfig:/opt/armv7/lib/pkgconfig
|
||||
ENV PKG_CONFIG_PATH=/opt/armv7/lib/pkgconfig
|
||||
|
||||
WORKDIR /
|
||||
|
||||
RUN dpkg --add-architecture armhf && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
git build-essential crossbuild-essential-armhf \
|
||||
cmake ninja-build ccache pkg-config curl ca-certificates file \
|
||||
tzdata \
|
||||
libcurl4-openssl-dev:armhf \
|
||||
libpcre2-dev:armhf \
|
||||
libyaml-cpp-dev:armhf \
|
||||
libatomic1:armhf \
|
||||
rapidjson-dev && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY cmake/linux-armv7hf.cmake /opt/armv7-toolchain.cmake
|
||||
|
||||
RUN set -xe && \
|
||||
echo "SOURCE_DEPS_CACHE_BUST=${SOURCE_DEPS_CACHE_BUST}" && \
|
||||
git clone --depth=1 --recurse-submodules --shallow-submodules https://github.com/ftk/quickjspp.git quickjspp && \
|
||||
cd quickjspp && \
|
||||
cmake -DCMAKE_TOOLCHAIN_FILE=/opt/armv7-toolchain.cmake -DCMAKE_BUILD_TYPE=Release . && \
|
||||
make quickjs -j ${THREADS} && \
|
||||
install -d /opt/armv7/lib/quickjs/ && \
|
||||
install -m644 quickjs/libquickjs.a /opt/armv7/lib/quickjs/ && \
|
||||
install -d /opt/armv7/include/quickjs/ && \
|
||||
install -m644 quickjs/quickjs.h quickjs/quickjs-libc.h /opt/armv7/include/quickjs/ && \
|
||||
install -m644 quickjspp.hpp /opt/armv7/include/
|
||||
|
||||
RUN set -xe && \
|
||||
echo "SOURCE_DEPS_CACHE_BUST=${SOURCE_DEPS_CACHE_BUST}" && \
|
||||
git clone --depth=1 --recurse-submodules --shallow-submodules https://github.com/PerMalmberg/libcron.git libcron && \
|
||||
cd libcron && \
|
||||
cmake -DCMAKE_TOOLCHAIN_FILE=/opt/armv7-toolchain.cmake -DCMAKE_BUILD_TYPE=Release . && \
|
||||
make libcron -j ${THREADS} && \
|
||||
install -d /opt/armv7/lib/ && \
|
||||
install -m644 libcron/out/Release/liblibcron.a /opt/armv7/lib/ && \
|
||||
install -d /opt/armv7/include/libcron/ && \
|
||||
install -m644 libcron/include/libcron/* /opt/armv7/include/libcron/ && \
|
||||
install -d /opt/armv7/include/date/ && \
|
||||
install -m644 libcron/externals/date/include/date/* /opt/armv7/include/date/
|
||||
|
||||
RUN set -xe && \
|
||||
echo "SOURCE_DEPS_CACHE_BUST=${SOURCE_DEPS_CACHE_BUST}" && \
|
||||
git clone --depth=1 https://github.com/ToruNiina/toml11.git toml11 && \
|
||||
cd toml11 && \
|
||||
install -d /opt/armv7/include && \
|
||||
cp -a include/. /opt/armv7/include/
|
||||
|
||||
COPY --from=go-builder /build/bridge/libmihomo.a /usr/lib/
|
||||
COPY --from=go-builder /build/bridge/libmihomo.h /usr/include/
|
||||
COPY --from=go-builder /build/bridge/go.mod /src/bridge/go.mod
|
||||
COPY --from=go-builder /build/bridge/go.sum /src/bridge/go.sum
|
||||
|
||||
WORKDIR /src
|
||||
COPY . /src
|
||||
COPY --from=go-builder /build/bridge/mihomo_schemes.h /src/src/parser/mihomo_schemes.h
|
||||
COPY --from=go-builder /build/bridge/param_compat.h /src/src/parser/param_compat.h
|
||||
|
||||
RUN set -xe && \
|
||||
if [ "${REFRESH_HEADERS}" = "true" ]; then \
|
||||
echo "Downloading latest cpp-httplib..." && \
|
||||
curl -fsSL https://raw.githubusercontent.com/yhirose/cpp-httplib/master/httplib.h -o include/httplib.h && \
|
||||
echo "Downloading latest nlohmann/json..." && \
|
||||
curl -fsSL https://github.com/nlohmann/json/releases/latest/download/json.hpp -o include/nlohmann/json.hpp && \
|
||||
echo "Downloading latest inja..." && \
|
||||
curl -fsSL https://raw.githubusercontent.com/pantor/inja/master/single_include/inja/inja.hpp -o include/inja.hpp && \
|
||||
echo "Downloading latest jpcre2..." && \
|
||||
curl -fsSL https://raw.githubusercontent.com/jpcre2/jpcre2/master/src/jpcre2.hpp -o include/jpcre2.hpp && \
|
||||
echo "Copying latest quickjspp from compiled source..." && \
|
||||
cp /opt/armv7/include/quickjspp.hpp include/quickjspp.hpp && \
|
||||
echo "Copying latest libcron headers from compiled source..." && \
|
||||
rm -rf include/libcron include/date && \
|
||||
cp -a /libcron/libcron/include/libcron include/libcron && \
|
||||
cp -a /libcron/libcron/externals/date/include/date include/date && \
|
||||
echo "Copying latest toml11 headers from compiled source..." && \
|
||||
rm -rf include/toml11 && \
|
||||
cp /toml11/include/toml.hpp include/toml.hpp && \
|
||||
cp -a /toml11/include/toml11 include/toml11; \
|
||||
else \
|
||||
echo "Using committed header libraries"; \
|
||||
fi
|
||||
|
||||
RUN set -xe && \
|
||||
[ -n "${SHA}" ] && sed -i "s/#define BUILD_ID \"\"/#define BUILD_ID \"${SHA}\"/ " src/version.h || true && \
|
||||
[ -n "${VERSION}" ] && sed -i "s/#define VERSION \"dev\"/#define VERSION \"${VERSION}\"/" src/version.h || true && \
|
||||
[ -n "${BUILD_DATE}" ] && sed -i "s/#define BUILD_DATE \"\"/#define BUILD_DATE \"${BUILD_DATE}\"/" src/version.h || true && \
|
||||
mkdir -p bridge && \
|
||||
cp /usr/lib/libmihomo.a bridge/ && \
|
||||
cp /usr/include/libmihomo.h bridge/ && \
|
||||
export PATH="/usr/lib/ccache:$PATH" && \
|
||||
export CCACHE_DIR=/tmp/ccache && \
|
||||
export CCACHE_COMPILERCHECK=content && \
|
||||
cmake -GNinja \
|
||||
-DCMAKE_TOOLCHAIN_FILE=/opt/armv7-toolchain.cmake \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_PREFIX_PATH=/opt/armv7 \
|
||||
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=OFF \
|
||||
-DCMAKE_POSITION_INDEPENDENT_CODE=OFF \
|
||||
-DCURL_INCLUDE_DIR=/usr/include \
|
||||
-DCURL_LIBRARY=/usr/lib/arm-linux-gnueabihf/libcurl.so \
|
||||
-DPCRE2_INCLUDE_DIR=/usr/include \
|
||||
-DPCRE2_LIBRARY=/usr/lib/arm-linux-gnueabihf/libpcre2-8.so \
|
||||
-DQUICKJS_INCLUDE_DIRS=/opt/armv7/include \
|
||||
-DQUICKJS_LIBRARY=/opt/armv7/lib/quickjs/libquickjs.a \
|
||||
-DLIBCRON_INCLUDE_DIR=/opt/armv7/include \
|
||||
-DDATE_INCLUDE_DIR=/opt/armv7/include \
|
||||
-DLIBCRON_LIBRARY=/opt/armv7/lib/liblibcron.a \
|
||||
-DRAPIDJSON_INCLUDEDIR=/usr/include \
|
||||
. && \
|
||||
ninja -j ${THREADS} && \
|
||||
file /src/subconverter && \
|
||||
arm-linux-gnueabihf-readelf -h /src/subconverter && \
|
||||
arm-linux-gnueabihf-readelf -d /src/subconverter
|
||||
|
||||
RUN set -xe && \
|
||||
mkdir -p /runtime-root/etc/ssl/certs /runtime-root/etc /runtime-root/tmp /runtime-root/usr/lib/arm-linux-gnueabihf /runtime-root/usr/share/zoneinfo && \
|
||||
cp -aL /etc/ssl/certs/ca-certificates.crt /runtime-root/etc/ssl/certs/ && \
|
||||
cp -aL /usr/share/zoneinfo/. /runtime-root/usr/share/zoneinfo/ && \
|
||||
ln -snf /usr/share/zoneinfo/Asia/Shanghai /runtime-root/etc/localtime && \
|
||||
echo Asia/Shanghai > /runtime-root/etc/timezone && \
|
||||
if [ -f /etc/nsswitch.conf ]; then cp -aL /etc/nsswitch.conf /runtime-root/etc/nsswitch.conf; fi && \
|
||||
READELF=arm-linux-gnueabihf-readelf \
|
||||
ELF_LIBRARY_PATH="/usr/lib/arm-linux-gnueabihf:/lib/arm-linux-gnueabihf:/usr/arm-linux-gnueabihf/lib:/usr/lib:/lib" \
|
||||
bash /src/scripts/ci/copy-elf-runtime-deps.sh /runtime-root \
|
||||
/src/subconverter \
|
||||
libnss_dns.so.2 \
|
||||
libnss_files.so.2 \
|
||||
libnss_compat.so.2 \
|
||||
libresolv.so.2 && \
|
||||
if [ -d /runtime-root/lib ]; then \
|
||||
install -d /runtime-root/usr/lib/arm-linux-gnueabihf; \
|
||||
find /runtime-root/lib -maxdepth 1 -type f -name '*.so*' -exec mv -f -t /runtime-root/usr/lib/arm-linux-gnueabihf {} +; \
|
||||
if [ -d /runtime-root/lib/arm-linux-gnueabihf ]; then \
|
||||
cp -a /runtime-root/lib/arm-linux-gnueabihf/. /runtime-root/usr/lib/arm-linux-gnueabihf/; \
|
||||
rm -rf /runtime-root/lib/arm-linux-gnueabihf; \
|
||||
fi; \
|
||||
rmdir /runtime-root/lib 2>/dev/null || true; \
|
||||
fi
|
||||
|
||||
# ========== FINAL STAGE ==========
|
||||
FROM --platform=$TARGETPLATFORM mirror.gcr.io/library/debian:trixie-slim
|
||||
|
||||
ARG VERSION="dev"
|
||||
ARG SHA=""
|
||||
ARG BUILD_DATE=""
|
||||
LABEL \
|
||||
org.opencontainers.image.title="SubConverter-Extended" \
|
||||
org.opencontainers.image.description="A Modern Evolution of subconverter; an enhanced implementation aligned with Mihomo configuration" \
|
||||
org.opencontainers.image.url="https://github.com/Aethersailor/SubConverter-Extended" \
|
||||
org.opencontainers.image.source="https://github.com/Aethersailor/SubConverter-Extended" \
|
||||
org.opencontainers.image.licenses="GPL-3.0" \
|
||||
org.opencontainers.image.version="${VERSION}" \
|
||||
org.opencontainers.image.revision="${SHA}" \
|
||||
org.opencontainers.image.created="${BUILD_DATE}" \
|
||||
maintainer="Aethersailor"
|
||||
|
||||
COPY --from=builder /runtime-root/ /
|
||||
COPY --from=builder --chmod=0755 /src/subconverter /usr/bin/subconverter
|
||||
COPY --from=builder /src/base /base/
|
||||
|
||||
ENV TZ=Asia/Shanghai
|
||||
ENV LD_LIBRARY_PATH="/usr/lib/arm-linux-gnueabihf:/usr/lib"
|
||||
WORKDIR /base
|
||||
RUN set -e && \
|
||||
ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && \
|
||||
echo $TZ > /etc/timezone && \
|
||||
printf '%s\n' \
|
||||
'#!/bin/sh' \
|
||||
'set -e' \
|
||||
'export LD_LIBRARY_PATH="/usr/lib/arm-linux-gnueabihf:/usr/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"' \
|
||||
'CONF="${PREF_PATH:-/base/pref.toml}"' \
|
||||
'CONF_DIR="$(dirname "$CONF")"' \
|
||||
'mkdir -p "$CONF_DIR"' \
|
||||
'if [ ! -f "$CONF" ] && [ -f /base/pref.example.toml ]; then' \
|
||||
' cp /base/pref.example.toml "$CONF"' \
|
||||
'fi' \
|
||||
'exec /usr/bin/subconverter -f "$CONF"' \
|
||||
> /usr/local/bin/start-subconverter && \
|
||||
chmod +x /usr/local/bin/start-subconverter
|
||||
CMD ["/usr/local/bin/start-subconverter"]
|
||||
EXPOSE 25500/tcp
|
||||
@@ -1,36 +1,38 @@
|
||||
# ========== GO BUILD STAGE ==========
|
||||
FROM golang:latest AS go-builder
|
||||
FROM mirror.gcr.io/library/golang:latest AS go-builder
|
||||
|
||||
# Docker Buildx 自动注入目标架构信息
|
||||
ARG TARGETARCH
|
||||
ARG TARGETVARIANT
|
||||
ARG MIHOMO_REF="Meta"
|
||||
ARG MIHOMO_CACHE_BUST=1
|
||||
ARG REFRESH_GO_DEPS=false
|
||||
|
||||
WORKDIR /build/bridge
|
||||
|
||||
# Install build dependencies (gcc required for CGO)
|
||||
# 包含 ARMv7 交叉编译工具链
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends git build-essential \
|
||||
gcc-arm-linux-gnueabihf && \
|
||||
apt-get install -y --no-install-recommends git build-essential && \
|
||||
if [ "$(dpkg --print-architecture)" != "armhf" ]; then \
|
||||
apt-get install -y --no-install-recommends gcc-arm-linux-gnueabihf; \
|
||||
fi && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy Go source code FIRST (needed for dependency analysis)
|
||||
# Copy committed Go module files and source.
|
||||
COPY bridge/go.mod bridge/go.sum ./
|
||||
COPY bridge/converter.go ./
|
||||
COPY bridge/preprocess.go ./
|
||||
|
||||
# Initialize new go.mod dynamically
|
||||
RUN go mod init github.com/aethersailor/subconverter-extended/bridge
|
||||
|
||||
# Get latest Mihomo and resolve all dependencies
|
||||
RUN echo "MIHOMO_CACHE_BUST=$MIHOMO_CACHE_BUST" && \
|
||||
go get github.com/metacubex/mihomo@${MIHOMO_REF}
|
||||
|
||||
# Upgrade all dependencies to latest versions (security fix)
|
||||
RUN go get -u all
|
||||
|
||||
# Tidy dependencies (auto-resolves transitive deps)
|
||||
RUN go mod tidy
|
||||
RUN set -xe && \
|
||||
if [ "${REFRESH_GO_DEPS}" = "true" ]; then \
|
||||
echo "MIHOMO_CACHE_BUST=$MIHOMO_CACHE_BUST" && \
|
||||
go get github.com/metacubex/mihomo@${MIHOMO_REF} && \
|
||||
go get -u all && \
|
||||
go mod tidy; \
|
||||
else \
|
||||
go mod download; \
|
||||
fi
|
||||
|
||||
# Copy scripts for scheme generation
|
||||
COPY scripts/ ../scripts/
|
||||
@@ -41,12 +43,14 @@ RUN go run ../scripts/generate_param_compat.go -o param_compat.h
|
||||
# 根据目标架构自动配置交叉编译环境
|
||||
# 注意:不使用 -ldflags="-s -w",因为移除符号表会导致 CGO 程序 Segfault
|
||||
RUN if [ "$TARGETARCH" = "arm" ] && [ "$TARGETVARIANT" = "v7" ]; then \
|
||||
echo "==> Cross-compiling for ARMv7 (native ARM64 build)" && \
|
||||
echo "==> Building for ARMv7" && \
|
||||
export CGO_ENABLED=1 \
|
||||
CC=arm-linux-gnueabihf-gcc \
|
||||
GOOS=linux \
|
||||
GOARCH=arm \
|
||||
GOARM=7; \
|
||||
if [ "$(dpkg --print-architecture)" != "armhf" ]; then \
|
||||
export CC=arm-linux-gnueabihf-gcc; \
|
||||
fi; \
|
||||
else \
|
||||
echo "==> Native build for $TARGETARCH" && \
|
||||
export CGO_ENABLED=1; \
|
||||
@@ -60,11 +64,15 @@ RUN if [ "$TARGETARCH" = "arm" ] && [ "$TARGETVARIANT" = "v7" ]; then \
|
||||
RUN ls -lh libmihomo.a libmihomo.h
|
||||
|
||||
# ========== C++ BUILD STAGE ==========
|
||||
FROM debian:latest AS builder
|
||||
FROM mirror.gcr.io/library/debian:latest AS builder
|
||||
ARG THREADS="4"
|
||||
ARG SHA=""
|
||||
ARG VERSION="dev"
|
||||
ARG BUILD_DATE=""
|
||||
ARG REFRESH_HEADERS=false
|
||||
ARG QUICKJSPP_REF="01cdd3047ced48265b127790848a0ca88204f2c7"
|
||||
ARG LIBCRON_REF="v1.3.3"
|
||||
ARG TOML11_REF="v4.4.0"
|
||||
|
||||
WORKDIR /
|
||||
|
||||
@@ -79,8 +87,11 @@ RUN apt-get update && \
|
||||
|
||||
# quickjspp
|
||||
RUN set -xe && \
|
||||
git clone --depth=1 https://github.com/ftk/quickjspp.git && \
|
||||
git init quickjspp && \
|
||||
cd quickjspp && \
|
||||
git remote add origin https://github.com/ftk/quickjspp.git && \
|
||||
git fetch --depth=1 origin "${QUICKJSPP_REF}" && \
|
||||
git checkout --detach FETCH_HEAD && \
|
||||
git submodule update --init && \
|
||||
cmake -DCMAKE_BUILD_TYPE=Release . && \
|
||||
make quickjs -j ${THREADS} && \
|
||||
@@ -92,8 +103,11 @@ RUN set -xe && \
|
||||
|
||||
# libcron
|
||||
RUN set -xe && \
|
||||
git clone https://github.com/PerMalmberg/libcron --depth=1 && \
|
||||
git init libcron && \
|
||||
cd libcron && \
|
||||
git remote add origin https://github.com/PerMalmberg/libcron && \
|
||||
git fetch --depth=1 origin "${LIBCRON_REF}" && \
|
||||
git checkout --detach FETCH_HEAD && \
|
||||
git submodule update --init && \
|
||||
cmake -DCMAKE_BUILD_TYPE=Release . && \
|
||||
make libcron -j ${THREADS} && \
|
||||
@@ -103,10 +117,12 @@ RUN set -xe && \
|
||||
install -d /usr/include/date/ && \
|
||||
install -m644 libcron/externals/date/include/date/* /usr/include/date/
|
||||
|
||||
# toml11 (跟随默认分支最新版本)
|
||||
RUN set -xe && \
|
||||
git clone https://github.com/ToruNiina/toml11 --depth=1 && \
|
||||
git init toml11 && \
|
||||
cd toml11 && \
|
||||
git remote add origin https://github.com/ToruNiina/toml11 && \
|
||||
git fetch --depth=1 origin "${TOML11_REF}" && \
|
||||
git checkout --detach FETCH_HEAD && \
|
||||
cmake -DCMAKE_CXX_STANDARD=11 . && \
|
||||
make install -j ${THREADS}
|
||||
|
||||
@@ -116,25 +132,27 @@ COPY --from=go-builder /build/bridge/libmihomo.h /usr/include/
|
||||
COPY --from=go-builder /build/bridge/go.mod /src/bridge/go.mod
|
||||
COPY --from=go-builder /build/bridge/go.sum /src/bridge/go.sum
|
||||
|
||||
# build subconverter from THIS repository source (provided by build context)
|
||||
# build SubConverter-Extended from THIS repository source (provided by build context)
|
||||
WORKDIR /src
|
||||
COPY . /src
|
||||
COPY --from=go-builder /build/bridge/mihomo_schemes.h /src/src/parser/mihomo_schemes.h
|
||||
COPY --from=go-builder /build/bridge/param_compat.h /src/src/parser/param_compat.h
|
||||
|
||||
# Download latest header-only libraries (override old versions in repo)
|
||||
RUN set -xe && \
|
||||
echo "Downloading latest cpp-httplib..." && \
|
||||
curl -fsSL https://raw.githubusercontent.com/yhirose/cpp-httplib/master/httplib.h -o include/httplib.h && \
|
||||
echo "Downloading latest nlohmann/json..." && \
|
||||
curl -fsSL https://github.com/nlohmann/json/releases/latest/download/json.hpp -o include/nlohmann/json.hpp && \
|
||||
echo "Downloading latest inja..." && \
|
||||
curl -fsSL https://raw.githubusercontent.com/pantor/inja/master/single_include/inja/inja.hpp -o include/inja.hpp && \
|
||||
echo "Downloading latest jpcre2..." && \
|
||||
curl -fsSL https://raw.githubusercontent.com/jpcre2/jpcre2/master/src/jpcre2.hpp -o include/jpcre2.hpp && \
|
||||
echo "Copying latest quickjspp from compiled source..." && \
|
||||
cp /usr/include/quickjspp.hpp include/quickjspp.hpp && \
|
||||
echo "All header libraries updated to latest versions"
|
||||
if [ "${REFRESH_HEADERS}" = "true" ]; then \
|
||||
echo "Downloading latest cpp-httplib..." && \
|
||||
curl -fsSL https://raw.githubusercontent.com/yhirose/cpp-httplib/master/httplib.h -o include/httplib.h && \
|
||||
echo "Downloading latest nlohmann/json..." && \
|
||||
curl -fsSL https://github.com/nlohmann/json/releases/latest/download/json.hpp -o include/nlohmann/json.hpp && \
|
||||
echo "Downloading latest inja..." && \
|
||||
curl -fsSL https://raw.githubusercontent.com/pantor/inja/master/single_include/inja/inja.hpp -o include/inja.hpp && \
|
||||
echo "Downloading latest jpcre2..." && \
|
||||
curl -fsSL https://raw.githubusercontent.com/jpcre2/jpcre2/master/src/jpcre2.hpp -o include/jpcre2.hpp && \
|
||||
echo "Copying pinned quickjspp from compiled source..." && \
|
||||
cp /usr/include/quickjspp.hpp include/quickjspp.hpp; \
|
||||
else \
|
||||
echo "Using committed header-only libraries"; \
|
||||
fi
|
||||
|
||||
RUN set -xe && \
|
||||
[ -n "${SHA}" ] && sed -i "s/#define BUILD_ID \"\"/#define BUILD_ID \"${SHA}\"/ " src/version.h || true && \
|
||||
@@ -150,11 +168,11 @@ RUN set -xe && \
|
||||
export CCACHE_COMPILERCHECK=content && \
|
||||
# Use Ninja generator and enable ccache
|
||||
cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER_LAUNCHER=ccache . && \
|
||||
# Parallel build (reduced from ${THREADS} to avoid OOM in Docker)
|
||||
ninja -j 4
|
||||
# Parallelism is controlled by the workflow to avoid OOM on smaller targets.
|
||||
ninja -j ${THREADS}
|
||||
|
||||
# ========== FINAL STAGE ==========
|
||||
FROM debian:latest
|
||||
FROM mirror.gcr.io/library/debian:latest
|
||||
|
||||
ARG VERSION="dev"
|
||||
ARG SHA=""
|
||||
@@ -171,9 +189,17 @@ LABEL \
|
||||
maintainer="Aethersailor"
|
||||
|
||||
# Install runtime dependencies
|
||||
ENV TZ=Asia/Shanghai
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
libpcre2-8-0 libcurl4 libyaml-cpp-dev ca-certificates && \
|
||||
if apt-cache policy libcurl4 | grep -q 'Candidate: (none)'; then \
|
||||
curl_runtime=libcurl4t64; \
|
||||
else \
|
||||
curl_runtime=libcurl4; \
|
||||
fi && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
libpcre2-8-0 "$curl_runtime" libyaml-cpp-dev ca-certificates tzdata && \
|
||||
ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && \
|
||||
echo $TZ > /etc/timezone && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --from=builder /src/subconverter /usr/bin/subconverter
|
||||
@@ -182,9 +208,6 @@ COPY --from=builder /src/base /base/
|
||||
# 确保二进制可执行
|
||||
RUN chmod +x /usr/bin/subconverter
|
||||
|
||||
ENV TZ=Asia/Shanghai
|
||||
RUN ln -sf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||
|
||||
WORKDIR /base
|
||||
CMD ["/usr/bin/subconverter"]
|
||||
EXPOSE 25500/tcp
|
||||
109
docs/images/readme-flow-extended.svg
Normal file
109
docs/images/readme-flow-extended.svg
Normal file
@@ -0,0 +1,109 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="900" height="360" viewBox="0 0 900 360" role="img" aria-labelledby="title desc">
|
||||
<title id="title">SubConverter-Extended 远程订阅链接处理流程</title>
|
||||
<desc id="desc">SubConverter-Extended 只返回包含 proxy-provider 的配置,客户端中的 Mihomo 内核再按订阅链接向远程订阅服务商请求节点信息。</desc>
|
||||
<defs>
|
||||
<marker id="arrow" markerWidth="10" markerHeight="10" refX="8.5" refY="5" orient="auto" markerUnits="strokeWidth">
|
||||
<path class="arrow-head" d="M 0 0 L 10 5 L 0 10 z"/>
|
||||
</marker>
|
||||
<style>
|
||||
svg {
|
||||
color-scheme: light dark;
|
||||
--canvas: #f8fafc;
|
||||
--canvas-stroke: #d7dde8;
|
||||
--client-wrap: #eef8f4;
|
||||
--client-wrap-stroke: #75a98f;
|
||||
--node: #ffffff;
|
||||
--node-stroke: #708199;
|
||||
--backend: #f0f7ff;
|
||||
--backend-stroke: #5f86bd;
|
||||
--kernel: #effcf6;
|
||||
--kernel-stroke: #5d9b7e;
|
||||
--provider: #f4f7ff;
|
||||
--provider-stroke: #738cc7;
|
||||
--text: #1f2937;
|
||||
--label: #4b5563;
|
||||
--small: #5b6573;
|
||||
--line: #596273;
|
||||
--no-line: #c24141;
|
||||
--no-text: #b42323;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
svg {
|
||||
--canvas: #0f172a;
|
||||
--canvas-stroke: #334155;
|
||||
--client-wrap: #12231d;
|
||||
--client-wrap-stroke: #58ad88;
|
||||
--node: #111827;
|
||||
--node-stroke: #94a3b8;
|
||||
--backend: #142238;
|
||||
--backend-stroke: #75a7e8;
|
||||
--kernel: #12281f;
|
||||
--kernel-stroke: #60c494;
|
||||
--provider: #172033;
|
||||
--provider-stroke: #8aa4e8;
|
||||
--text: #e5e7eb;
|
||||
--label: #cbd5e1;
|
||||
--small: #b7c2d1;
|
||||
--line: #cbd5e1;
|
||||
--no-line: #f87171;
|
||||
--no-text: #fca5a5;
|
||||
}
|
||||
}
|
||||
|
||||
.canvas { fill: var(--canvas); stroke: var(--canvas-stroke); stroke-width: 1.5; }
|
||||
.client-wrap { fill: var(--client-wrap); stroke: var(--client-wrap-stroke); stroke-width: 1.6; }
|
||||
.node { fill: var(--node); stroke: var(--node-stroke); stroke-width: 1.6; }
|
||||
.backend { fill: var(--backend); stroke: var(--backend-stroke); }
|
||||
.kernel { fill: var(--kernel); stroke: var(--kernel-stroke); }
|
||||
.provider { fill: var(--provider); stroke: var(--provider-stroke); }
|
||||
.title { fill: var(--text); font: 700 22px "Segoe UI", "Microsoft YaHei", Arial, sans-serif; }
|
||||
.text { fill: var(--text); font: 600 16px "Segoe UI", "Microsoft YaHei", Arial, sans-serif; }
|
||||
.label { fill: var(--label); font: 14px "Segoe UI", "Microsoft YaHei", Arial, sans-serif; }
|
||||
.small { fill: var(--small); font: 13px "Segoe UI", "Microsoft YaHei", Arial, sans-serif; }
|
||||
.line { fill: none; stroke: var(--line); stroke-width: 2; marker-end: url(#arrow); }
|
||||
.no-line { fill: none; stroke: var(--no-line); stroke-width: 2; stroke-dasharray: 7 7; }
|
||||
.no-mark { fill: none; stroke: var(--no-line); stroke-width: 3; stroke-linecap: round; }
|
||||
.no-text { fill: var(--no-text); font: 600 13px "Segoe UI", "Microsoft YaHei", Arial, sans-serif; }
|
||||
.arrow-head { fill: var(--line); }
|
||||
</style>
|
||||
</defs>
|
||||
|
||||
<rect class="canvas" x="1" y="1" width="898" height="358" rx="16"/>
|
||||
<text class="title" x="450" y="42" text-anchor="middle">SubConverter-Extended:后端只生成配置</text>
|
||||
|
||||
<rect class="client-wrap" x="48" y="76" width="330" height="250" rx="14"/>
|
||||
<text class="small" x="74" y="104">客户端</text>
|
||||
|
||||
<rect class="node" x="82" y="122" width="130" height="62" rx="10"/>
|
||||
<text class="text" x="147" y="159" text-anchor="middle">客户端界面</text>
|
||||
|
||||
<rect class="node kernel" x="220" y="235" width="130" height="62" rx="10"/>
|
||||
<text class="text" x="285" y="261" text-anchor="middle">Mihomo</text>
|
||||
<text class="text" x="285" y="282" text-anchor="middle">内核</text>
|
||||
|
||||
<rect class="node backend" x="455" y="122" width="150" height="62" rx="10"/>
|
||||
<text class="text" x="530" y="159" text-anchor="middle">新型后端</text>
|
||||
|
||||
<rect class="node provider" x="710" y="235" width="140" height="62" rx="10"/>
|
||||
<text class="text" x="780" y="272" text-anchor="middle">远程订阅服务商</text>
|
||||
|
||||
<path class="line" d="M 212 137 H 455"/>
|
||||
<text class="label" x="334" y="121" text-anchor="middle">发送订阅链接</text>
|
||||
|
||||
<path class="line" d="M 455 174 H 212"/>
|
||||
<text class="label" x="334" y="205" text-anchor="middle">返回 proxy-provider 配置</text>
|
||||
|
||||
<path class="line" d="M 176 184 C 190 215, 207 241, 220 250"/>
|
||||
<text class="label" x="156" y="224" text-anchor="middle">应用配置</text>
|
||||
|
||||
<path class="line" d="M 350 250 H 710"/>
|
||||
<text class="label" x="530" y="228" text-anchor="middle">按订阅链接请求节点信息</text>
|
||||
|
||||
<path class="line" d="M 710 282 H 350"/>
|
||||
<text class="label" x="530" y="318" text-anchor="middle">返回节点信息</text>
|
||||
|
||||
<path class="no-line" d="M 600 178 C 650 194, 685 216, 722 240"/>
|
||||
<path class="no-mark" d="M 649 197 L 665 213 M 665 197 L 649 213"/>
|
||||
<text class="no-text" x="654" y="192" text-anchor="middle">后端不直连远程订阅服务商</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.0 KiB |
75
docs/images/readme-flow-legacy.svg
Normal file
75
docs/images/readme-flow-legacy.svg
Normal file
@@ -0,0 +1,75 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="900" height="270" viewBox="0 0 900 270" role="img" aria-labelledby="title desc">
|
||||
<title id="title">传统 subconverter 远程订阅链接处理流程</title>
|
||||
<desc id="desc">客户端把订阅链接发给原始后端,原始后端向远程订阅服务商请求节点信息,再把整合了节点信息的配置文件返回客户端。</desc>
|
||||
<defs>
|
||||
<marker id="arrow" markerWidth="10" markerHeight="10" refX="8.5" refY="5" orient="auto" markerUnits="strokeWidth">
|
||||
<path class="arrow-head" d="M 0 0 L 10 5 L 0 10 z"/>
|
||||
</marker>
|
||||
<style>
|
||||
svg {
|
||||
color-scheme: light dark;
|
||||
--canvas: #f8fafc;
|
||||
--canvas-stroke: #d7dde8;
|
||||
--node: #ffffff;
|
||||
--node-stroke: #708199;
|
||||
--backend: #fff8ec;
|
||||
--backend-stroke: #b7843d;
|
||||
--provider: #f4f7ff;
|
||||
--provider-stroke: #738cc7;
|
||||
--text: #1f2937;
|
||||
--label: #4b5563;
|
||||
--line: #596273;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
svg {
|
||||
--canvas: #0f172a;
|
||||
--canvas-stroke: #334155;
|
||||
--node: #111827;
|
||||
--node-stroke: #94a3b8;
|
||||
--backend: #2a2113;
|
||||
--backend-stroke: #d08b40;
|
||||
--provider: #172033;
|
||||
--provider-stroke: #8aa4e8;
|
||||
--text: #e5e7eb;
|
||||
--label: #cbd5e1;
|
||||
--line: #cbd5e1;
|
||||
}
|
||||
}
|
||||
|
||||
.canvas { fill: var(--canvas); stroke: var(--canvas-stroke); stroke-width: 1.5; }
|
||||
.node { fill: var(--node); stroke: var(--node-stroke); stroke-width: 1.6; }
|
||||
.backend { fill: var(--backend); stroke: var(--backend-stroke); }
|
||||
.provider { fill: var(--provider); stroke: var(--provider-stroke); }
|
||||
.title { fill: var(--text); font: 700 22px "Segoe UI", "Microsoft YaHei", Arial, sans-serif; }
|
||||
.text { fill: var(--text); font: 600 16px "Segoe UI", "Microsoft YaHei", Arial, sans-serif; }
|
||||
.label { fill: var(--label); font: 14px "Segoe UI", "Microsoft YaHei", Arial, sans-serif; }
|
||||
.line { fill: none; stroke: var(--line); stroke-width: 2; marker-end: url(#arrow); }
|
||||
.arrow-head { fill: var(--line); }
|
||||
</style>
|
||||
</defs>
|
||||
|
||||
<rect class="canvas" x="1" y="1" width="898" height="268" rx="16"/>
|
||||
<text class="title" x="450" y="42" text-anchor="middle">传统 subconverter:后端作为中转站</text>
|
||||
|
||||
<rect class="node" x="60" y="108" width="150" height="72" rx="10"/>
|
||||
<text class="text" x="135" y="150" text-anchor="middle">客户端</text>
|
||||
|
||||
<rect class="node backend" x="375" y="108" width="150" height="72" rx="10"/>
|
||||
<text class="text" x="450" y="150" text-anchor="middle">原始后端</text>
|
||||
|
||||
<rect class="node provider" x="690" y="108" width="150" height="72" rx="10"/>
|
||||
<text class="text" x="765" y="150" text-anchor="middle">远程订阅服务商</text>
|
||||
|
||||
<path class="line" d="M 210 128 H 375"/>
|
||||
<text class="label" x="292" y="112" text-anchor="middle">发送订阅链接</text>
|
||||
|
||||
<path class="line" d="M 375 162 H 210"/>
|
||||
<text class="label" x="292" y="199" text-anchor="middle">返回整合节点的配置文件</text>
|
||||
|
||||
<path class="line" d="M 525 128 H 690"/>
|
||||
<text class="label" x="607" y="112" text-anchor="middle">请求节点信息</text>
|
||||
|
||||
<path class="line" d="M 690 162 H 525"/>
|
||||
<text class="label" x="607" y="199" text-anchor="middle">返回节点信息</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
53
docs/security-profiles.zh-CN.md
Normal file
53
docs/security-profiles.zh-CN.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# 安全档位与平滑迁移
|
||||
|
||||
## 推荐默认方案
|
||||
|
||||
现有部署默认保持 `lan` 档位。该档位保留历史行为,适合家庭内网、NAS、软路由、旁路由、Docker 内网等自用部署,用户仍可通过项目访问本地资源、私有网段资源和 fake-ip 资源。
|
||||
|
||||
公网部署建议显式切到 `public`:
|
||||
|
||||
```ini
|
||||
[security]
|
||||
profile=public
|
||||
```
|
||||
|
||||
或使用环境变量:
|
||||
|
||||
```bash
|
||||
SUBCONVERTER_SECURITY_PROFILE=public
|
||||
```
|
||||
|
||||
## 三个档位
|
||||
|
||||
- `lan`:默认值,兼容旧行为。公开请求、外部配置、规则集、订阅链接仍可访问本地、私有网段和 fake-ip 资源。
|
||||
- `public`:公网推荐值。仅限制由公开请求控制的不可信拉取目标,例如 `/sub?url=...`、`/sub?config=...`、`/getruleset?url=...`、公开外部配置里的远程 import/fetch。项目自带本地模板、部署者配置的默认模板和本地 base 文件继续可用。
|
||||
- `strict`:在 `public` 的拉取限制基础上,始终禁用公开请求触发的 Gist 上传。
|
||||
|
||||
## 公网模式不会阻止什么
|
||||
|
||||
`public` 不会阻止项目读取自带的 `base/` 模板、部署者在配置文件里指定的本地模板,以及受信任默认配置里的本地资源。限制只挂在“请求方可控”的来源上。
|
||||
|
||||
## 上传开关
|
||||
|
||||
`lan` 保持旧上传行为。`public` 默认禁用公开请求触发的上传,如确实需要可显式开启:
|
||||
|
||||
```ini
|
||||
[security]
|
||||
profile=public
|
||||
allow_public_upload=true
|
||||
```
|
||||
|
||||
也可以使用:
|
||||
|
||||
```bash
|
||||
SUBCONVERTER_ALLOW_PUBLIC_UPLOAD=true
|
||||
```
|
||||
|
||||
`strict` 下即使设置 `allow_public_upload=true` 也不会允许公开上传。
|
||||
|
||||
## 推荐迁移步骤
|
||||
|
||||
1. 内网自用部署不需要改配置,继续使用默认 `lan`。
|
||||
2. 对公网暴露的实例,先更新镜像但保持 `lan`,确认业务正常。
|
||||
3. 将公网实例切到 `public`,观察日志里是否有被阻止的私有地址访问。
|
||||
4. 如果被阻止的是业务必须访问的内网资源,说明该实例更适合作为内网服务运行;不要直接暴露到公网。
|
||||
34
include/date/chrono_io.h
Normal file
34
include/date/chrono_io.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#ifndef CHRONO_IO_H
|
||||
#define CHRONO_IO_H
|
||||
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) 2016, 2017 Howard Hinnant
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
// Our apologies. When the previous paragraph was written, lowercase had not yet
|
||||
// been invented (that would involve another several millennia of evolution).
|
||||
// We did not mean to shout.
|
||||
|
||||
// This functionality has moved to "date.h"
|
||||
|
||||
#include "date.h"
|
||||
|
||||
#endif // CHRONO_IO_H
|
||||
8356
include/date/date.h
Normal file
8356
include/date/date.h
Normal file
File diff suppressed because it is too large
Load Diff
50
include/date/ios.h
Normal file
50
include/date/ios.h
Normal file
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// ios.h
|
||||
// DateTimeLib
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) 2016 Alexander Kormanovsky
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
#ifndef ios_hpp
|
||||
#define ios_hpp
|
||||
|
||||
#if __APPLE__
|
||||
# include <TargetConditionals.h>
|
||||
# if TARGET_OS_IPHONE
|
||||
# include <string>
|
||||
|
||||
namespace date
|
||||
{
|
||||
namespace iOSUtils
|
||||
{
|
||||
|
||||
std::string get_tzdata_path();
|
||||
std::string get_current_timezone();
|
||||
|
||||
} // namespace iOSUtils
|
||||
} // namespace date
|
||||
|
||||
# endif // TARGET_OS_IPHONE
|
||||
#else // !__APPLE__
|
||||
# define TARGET_OS_IPHONE 0
|
||||
#endif // !__APPLE__
|
||||
#endif // ios_hpp
|
||||
3031
include/date/islamic.h
Normal file
3031
include/date/islamic.h
Normal file
File diff suppressed because it is too large
Load Diff
1761
include/date/iso_week.h
Normal file
1761
include/date/iso_week.h
Normal file
File diff suppressed because it is too large
Load Diff
3052
include/date/julian.h
Normal file
3052
include/date/julian.h
Normal file
File diff suppressed because it is too large
Load Diff
952
include/date/ptz.h
Normal file
952
include/date/ptz.h
Normal file
@@ -0,0 +1,952 @@
|
||||
#ifndef PTZ_H
|
||||
#define PTZ_H
|
||||
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) 2017 Howard Hinnant
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
// This header allows Posix-style time zones as specified for TZ here:
|
||||
// http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03
|
||||
//
|
||||
// Posix::time_zone can be constructed with a posix-style string and then used in
|
||||
// a zoned_time like so:
|
||||
//
|
||||
// zoned_time<system_clock::duration, Posix::time_zone> zt{"EST5EDT,M3.2.0,M11.1.0",
|
||||
// system_clock::now()};
|
||||
// or:
|
||||
//
|
||||
// Posix::time_zone tz{"EST5EDT,M3.2.0,M11.1.0"};
|
||||
// zoned_time<system_clock::duration, Posix::time_zone> zt{tz, system_clock::now()};
|
||||
//
|
||||
// In C++17 CTAD simplifies this to:
|
||||
//
|
||||
// Posix::time_zone tz{"EST5EDT,M3.2.0,M11.1.0"};
|
||||
// zoned_time zt{tz, system_clock::now()};
|
||||
//
|
||||
// Extension to the Posix rules to allow a constant daylight saving offset:
|
||||
//
|
||||
// If the rule set is missing (everything starting with ','), then
|
||||
// there must be exactly one abbreviation (std or daylight) with
|
||||
// length 3 or greater, and that will be used as the constant offset. If
|
||||
// there are two, the std abbreviation is silently set to "", and the
|
||||
// result is constant daylight saving. If there are zero abbreviations
|
||||
// with no rule set, an exception is thrown.
|
||||
//
|
||||
// Example:
|
||||
// "EST5" yields a constant offset of -5h with 0h save and "EST abbreviation.
|
||||
// "5EDT" yields a constant offset of -4h with 1h save and "EDT" abbreviation.
|
||||
// "EST5EDT" and "5EDT4" are both equal to "5EDT".
|
||||
//
|
||||
// Note, Posix-style time zones are not recommended for all of the reasons described here:
|
||||
// https://stackoverflow.com/tags/timezone/info
|
||||
//
|
||||
// They are provided here as a non-trivial custom time zone example, and if you really
|
||||
// have to have Posix time zones, you're welcome to use this one.
|
||||
|
||||
#include "date/tz.h"
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
namespace Posix
|
||||
{
|
||||
|
||||
namespace detail
|
||||
{
|
||||
|
||||
#if HAS_STRING_VIEW
|
||||
|
||||
using string_t = std::string_view;
|
||||
|
||||
#else // !HAS_STRING_VIEW
|
||||
|
||||
using string_t = std::string;
|
||||
|
||||
#endif // !HAS_STRING_VIEW
|
||||
|
||||
class rule;
|
||||
|
||||
void throw_invalid(const string_t& s, unsigned i, const string_t& message);
|
||||
unsigned read_date(const string_t& s, unsigned i, rule& r);
|
||||
unsigned read_name(const string_t& s, unsigned i, std::string& name);
|
||||
unsigned read_signed_time(const string_t& s, unsigned i, std::chrono::seconds& t);
|
||||
unsigned read_unsigned_time(const string_t& s, unsigned i, std::chrono::seconds& t);
|
||||
unsigned read_unsigned(const string_t& s, unsigned i, unsigned limit, unsigned& u,
|
||||
const string_t& message = string_t{});
|
||||
|
||||
class rule
|
||||
{
|
||||
enum {off, J, M, N};
|
||||
|
||||
date::month m_;
|
||||
date::weekday wd_;
|
||||
unsigned short n_ : 14;
|
||||
unsigned short mode_ : 2;
|
||||
std::chrono::duration<std::int32_t> time_ = std::chrono::hours{2};
|
||||
|
||||
public:
|
||||
rule() : mode_(off) {}
|
||||
|
||||
bool ok() const {return mode_ != off;}
|
||||
date::local_seconds operator()(date::year y) const;
|
||||
std::string to_string() const;
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, const rule& r);
|
||||
friend unsigned read_date(const string_t& s, unsigned i, rule& r);
|
||||
friend bool operator==(const rule& x, const rule& y);
|
||||
};
|
||||
|
||||
inline
|
||||
bool
|
||||
operator==(const rule& x, const rule& y)
|
||||
{
|
||||
if (x.mode_ != y.mode_)
|
||||
return false;
|
||||
switch (x.mode_)
|
||||
{
|
||||
case rule::J:
|
||||
case rule::N:
|
||||
return x.n_ == y.n_;
|
||||
case rule::M:
|
||||
return x.m_ == y.m_ && x.n_ == y.n_ && x.wd_ == y.wd_;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
inline
|
||||
bool
|
||||
operator!=(const rule& x, const rule& y)
|
||||
{
|
||||
return !(x == y);
|
||||
}
|
||||
|
||||
inline
|
||||
date::local_seconds
|
||||
rule::operator()(date::year y) const
|
||||
{
|
||||
using date::local_days;
|
||||
using date::January;
|
||||
using date::days;
|
||||
using date::last;
|
||||
using sec = std::chrono::seconds;
|
||||
date::local_seconds t;
|
||||
switch (mode_)
|
||||
{
|
||||
case J:
|
||||
t = local_days{y/January/0} + days{n_ + (y.is_leap() && n_ > 59)} + sec{time_};
|
||||
break;
|
||||
case M:
|
||||
t = (n_ == 5 ? local_days{y/m_/wd_[last]} : local_days{y/m_/wd_[n_]}) + sec{time_};
|
||||
break;
|
||||
case N:
|
||||
t = local_days{y/January/1} + days{n_} + sec{time_};
|
||||
break;
|
||||
default:
|
||||
assert(!"rule called with bad mode");
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
inline
|
||||
std::string
|
||||
rule::to_string() const
|
||||
{
|
||||
using namespace std::chrono;
|
||||
auto print_offset = [](seconds off)
|
||||
{
|
||||
std::string nm;
|
||||
if (off != hours{2})
|
||||
{
|
||||
date::hh_mm_ss<seconds> offset{off};
|
||||
nm = '/';
|
||||
nm += std::to_string(offset.hours().count());
|
||||
if (offset.minutes() != minutes{0} || offset.seconds() != seconds{0})
|
||||
{
|
||||
nm += ':';
|
||||
if (offset.minutes() < minutes{10})
|
||||
nm += '0';
|
||||
nm += std::to_string(offset.minutes().count());
|
||||
if (offset.seconds() != seconds{0})
|
||||
{
|
||||
nm += ':';
|
||||
if (offset.seconds() < seconds{10})
|
||||
nm += '0';
|
||||
nm += std::to_string(offset.seconds().count());
|
||||
}
|
||||
}
|
||||
}
|
||||
return nm;
|
||||
};
|
||||
|
||||
std::string nm;
|
||||
switch (mode_)
|
||||
{
|
||||
case rule::J:
|
||||
nm = 'J';
|
||||
nm += std::to_string(n_);
|
||||
break;
|
||||
case rule::M:
|
||||
nm = 'M';
|
||||
nm += std::to_string(static_cast<unsigned>(m_));
|
||||
nm += '.';
|
||||
nm += std::to_string(n_);
|
||||
nm += '.';
|
||||
nm += std::to_string(wd_.c_encoding());
|
||||
break;
|
||||
case rule::N:
|
||||
nm = std::to_string(n_);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
nm += print_offset(time_);
|
||||
return nm;
|
||||
}
|
||||
|
||||
inline
|
||||
std::ostream&
|
||||
operator<<(std::ostream& os, const rule& r)
|
||||
{
|
||||
switch (r.mode_)
|
||||
{
|
||||
case rule::J:
|
||||
os << 'J' << r.n_ << date::format(" %T", r.time_);
|
||||
break;
|
||||
case rule::M:
|
||||
if (r.n_ == 5)
|
||||
os << r.m_/r.wd_[date::last];
|
||||
else
|
||||
os << r.m_/r.wd_[r.n_];
|
||||
os << date::format(" %T", r.time_);
|
||||
break;
|
||||
case rule::N:
|
||||
os << r.n_ << date::format(" %T", r.time_);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
class time_zone
|
||||
{
|
||||
std::string std_abbrev_;
|
||||
std::string dst_abbrev_ = {};
|
||||
std::chrono::seconds offset_;
|
||||
std::chrono::seconds save_ = std::chrono::hours{1};
|
||||
detail::rule start_rule_;
|
||||
detail::rule end_rule_;
|
||||
|
||||
public:
|
||||
explicit time_zone(const detail::string_t& name);
|
||||
|
||||
template <class Duration>
|
||||
date::sys_info get_info(date::sys_time<Duration> st) const;
|
||||
template <class Duration>
|
||||
date::local_info get_info(date::local_time<Duration> tp) const;
|
||||
|
||||
template <class Duration>
|
||||
date::sys_time<typename std::common_type<Duration, std::chrono::seconds>::type>
|
||||
to_sys(date::local_time<Duration> tp) const;
|
||||
|
||||
template <class Duration>
|
||||
date::sys_time<typename std::common_type<Duration, std::chrono::seconds>::type>
|
||||
to_sys(date::local_time<Duration> tp, date::choose z) const;
|
||||
|
||||
template <class Duration>
|
||||
date::local_time<typename std::common_type<Duration, std::chrono::seconds>::type>
|
||||
to_local(date::sys_time<Duration> tp) const;
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, const time_zone& z);
|
||||
|
||||
const time_zone* operator->() const {return this;}
|
||||
|
||||
std::string name() const;
|
||||
|
||||
friend bool operator==(const time_zone& x, const time_zone& y);
|
||||
|
||||
private:
|
||||
date::sys_seconds get_start(date::year y) const;
|
||||
date::sys_seconds get_prev_start(date::year y) const;
|
||||
date::sys_seconds get_next_start(date::year y) const;
|
||||
date::sys_seconds get_end(date::year y) const;
|
||||
date::sys_seconds get_prev_end(date::year y) const;
|
||||
date::sys_seconds get_next_end(date::year y) const;
|
||||
date::sys_info contant_offset() const;
|
||||
};
|
||||
|
||||
inline
|
||||
date::sys_seconds
|
||||
time_zone::get_start(date::year y) const
|
||||
{
|
||||
return date::sys_seconds{(start_rule_(y) - offset_).time_since_epoch()};
|
||||
}
|
||||
|
||||
inline
|
||||
date::sys_seconds
|
||||
time_zone::get_prev_start(date::year y) const
|
||||
{
|
||||
return date::sys_seconds{(start_rule_(--y) - offset_).time_since_epoch()};
|
||||
}
|
||||
|
||||
inline
|
||||
date::sys_seconds
|
||||
time_zone::get_next_start(date::year y) const
|
||||
{
|
||||
return date::sys_seconds{(start_rule_(++y) - offset_).time_since_epoch()};
|
||||
}
|
||||
|
||||
inline
|
||||
date::sys_seconds
|
||||
time_zone::get_end(date::year y) const
|
||||
{
|
||||
return date::sys_seconds{(end_rule_(y) - (offset_ + save_)).time_since_epoch()};
|
||||
}
|
||||
|
||||
inline
|
||||
date::sys_seconds
|
||||
time_zone::get_prev_end(date::year y) const
|
||||
{
|
||||
return date::sys_seconds{(end_rule_(--y) - (offset_ + save_)).time_since_epoch()};
|
||||
}
|
||||
|
||||
inline
|
||||
date::sys_seconds
|
||||
time_zone::get_next_end(date::year y) const
|
||||
{
|
||||
return date::sys_seconds{(end_rule_(++y) - (offset_ + save_)).time_since_epoch()};
|
||||
}
|
||||
|
||||
inline
|
||||
date::sys_info
|
||||
time_zone::contant_offset() const
|
||||
{
|
||||
using date::year;
|
||||
using date::sys_info;
|
||||
using date::sys_days;
|
||||
using date::January;
|
||||
using date::December;
|
||||
using date::last;
|
||||
using date::days;
|
||||
using std::chrono::minutes;
|
||||
sys_info r;
|
||||
r.begin = sys_days{year::min()/January/1};
|
||||
r.end = sys_days{year::max()/December/last} + days{1} - std::chrono::seconds{1};
|
||||
if (std_abbrev_.size() > 0)
|
||||
{
|
||||
r.abbrev = std_abbrev_;
|
||||
r.offset = offset_;
|
||||
r.save = {};
|
||||
}
|
||||
else
|
||||
{
|
||||
r.abbrev = dst_abbrev_;
|
||||
r.offset = offset_ + save_;
|
||||
r.save = date::ceil<minutes>(save_);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
inline
|
||||
time_zone::time_zone(const detail::string_t& s)
|
||||
{
|
||||
using detail::read_name;
|
||||
using detail::read_signed_time;
|
||||
using detail::throw_invalid;
|
||||
auto i = read_name(s, 0, std_abbrev_);
|
||||
auto std_name_i = i;
|
||||
auto abbrev_name_i = i;
|
||||
i = read_signed_time(s, i, offset_);
|
||||
offset_ = -offset_;
|
||||
if (i != s.size())
|
||||
{
|
||||
i = read_name(s, i, dst_abbrev_);
|
||||
abbrev_name_i = i;
|
||||
if (i != s.size())
|
||||
{
|
||||
if (s[i] != ',')
|
||||
{
|
||||
i = read_signed_time(s, i, save_);
|
||||
save_ = -save_ - offset_;
|
||||
}
|
||||
if (i != s.size())
|
||||
{
|
||||
if (s[i] != ',')
|
||||
throw_invalid(s, i, "Expecting end of string or ',' to start rule");
|
||||
++i;
|
||||
i = read_date(s, i, start_rule_);
|
||||
if (i == s.size() || s[i] != ',')
|
||||
throw_invalid(s, i, "Expecting ',' and then the ending rule");
|
||||
++i;
|
||||
i = read_date(s, i, end_rule_);
|
||||
if (i != s.size())
|
||||
throw_invalid(s, i, "Found unexpected trailing characters");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (start_rule_.ok())
|
||||
{
|
||||
if (std_abbrev_.size() < 3)
|
||||
throw_invalid(s, std_name_i, "Zone with rules must have a std"
|
||||
" abbreviation of length 3 or greater");
|
||||
if (dst_abbrev_.size() < 3)
|
||||
throw_invalid(s, abbrev_name_i, "Zone with rules must have a daylight"
|
||||
" abbreviation of length 3 or greater");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dst_abbrev_.size() >= 3)
|
||||
{
|
||||
std_abbrev_.clear();
|
||||
}
|
||||
else if (std_abbrev_.size() < 3)
|
||||
{
|
||||
throw_invalid(s, std_name_i, "Zone must have at least one abbreviation"
|
||||
" of length 3 or greater");
|
||||
}
|
||||
else
|
||||
{
|
||||
dst_abbrev_.clear();
|
||||
save_ = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class Duration>
|
||||
date::sys_info
|
||||
time_zone::get_info(date::sys_time<Duration> st) const
|
||||
{
|
||||
using date::sys_info;
|
||||
using date::year_month_day;
|
||||
using date::sys_days;
|
||||
using date::floor;
|
||||
using date::ceil;
|
||||
using date::days;
|
||||
using date::year;
|
||||
using date::January;
|
||||
using date::December;
|
||||
using date::last;
|
||||
using std::chrono::minutes;
|
||||
sys_info r{};
|
||||
r.offset = offset_;
|
||||
if (start_rule_.ok())
|
||||
{
|
||||
auto y = year_month_day{floor<days>(st)}.year();
|
||||
if (st >= get_next_start(y))
|
||||
++y;
|
||||
else if (st < get_prev_end(y))
|
||||
--y;
|
||||
auto start = get_start(y);
|
||||
auto end = get_end(y);
|
||||
if (start <= end) // (northern hemisphere)
|
||||
{
|
||||
if (start <= st && st < end)
|
||||
{
|
||||
r.begin = start;
|
||||
r.end = end;
|
||||
r.offset += save_;
|
||||
r.save = ceil<minutes>(save_);
|
||||
r.abbrev = dst_abbrev_;
|
||||
}
|
||||
else if (st < start)
|
||||
{
|
||||
r.begin = get_prev_end(y);
|
||||
r.end = start;
|
||||
r.abbrev = std_abbrev_;
|
||||
}
|
||||
else // st >= end
|
||||
{
|
||||
r.begin = end;
|
||||
r.end = get_next_start(y);
|
||||
r.abbrev = std_abbrev_;
|
||||
}
|
||||
}
|
||||
else // end < start (southern hemisphere)
|
||||
{
|
||||
if (end <= st && st < start)
|
||||
{
|
||||
r.begin = end;
|
||||
r.end = start;
|
||||
r.abbrev = std_abbrev_;
|
||||
}
|
||||
else if (st < end)
|
||||
{
|
||||
r.begin = get_prev_start(y);
|
||||
r.end = end;
|
||||
r.offset += save_;
|
||||
r.save = ceil<minutes>(save_);
|
||||
r.abbrev = dst_abbrev_;
|
||||
}
|
||||
else // st >= start
|
||||
{
|
||||
r.begin = start;
|
||||
r.end = get_next_end(y);
|
||||
r.offset += save_;
|
||||
r.save = ceil<minutes>(save_);
|
||||
r.abbrev = dst_abbrev_;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
r = contant_offset();
|
||||
using seconds = std::chrono::seconds;
|
||||
assert(r.begin <= floor<seconds>(st) && floor<seconds>(st) <= r.end);
|
||||
return r;
|
||||
}
|
||||
|
||||
template <class Duration>
|
||||
date::local_info
|
||||
time_zone::get_info(date::local_time<Duration> tp) const
|
||||
{
|
||||
using date::local_info;
|
||||
using date::year_month_day;
|
||||
using date::days;
|
||||
using date::sys_days;
|
||||
using date::sys_seconds;
|
||||
using date::year;
|
||||
using date::ceil;
|
||||
using date::January;
|
||||
using date::December;
|
||||
using date::last;
|
||||
using std::chrono::seconds;
|
||||
using std::chrono::minutes;
|
||||
local_info r{};
|
||||
using date::floor;
|
||||
if (start_rule_.ok())
|
||||
{
|
||||
auto y = year_month_day{floor<days>(tp)}.year();
|
||||
auto start = get_start(y);
|
||||
auto end = get_end(y);
|
||||
auto utcs = sys_seconds{floor<seconds>(tp - offset_).time_since_epoch()};
|
||||
auto utcd = sys_seconds{floor<seconds>(tp - (offset_ + save_)).time_since_epoch()};
|
||||
auto northern = start <= end;
|
||||
if ((utcs < start) != (utcd < start))
|
||||
{
|
||||
if (northern)
|
||||
r.first.begin = get_prev_end(y);
|
||||
else
|
||||
r.first.begin = end;
|
||||
r.first.end = start;
|
||||
r.first.offset = offset_;
|
||||
r.first.abbrev = std_abbrev_;
|
||||
r.second.begin = start;
|
||||
if (northern)
|
||||
r.second.end = end;
|
||||
else
|
||||
r.second.end = get_next_end(y);
|
||||
r.second.abbrev = dst_abbrev_;
|
||||
r.second.offset = offset_ + save_;
|
||||
r.second.save = ceil<minutes>(save_);
|
||||
r.result = save_ > seconds{0} ? local_info::nonexistent
|
||||
: local_info::ambiguous;
|
||||
}
|
||||
else if ((utcs < end) != (utcd < end))
|
||||
{
|
||||
if (northern)
|
||||
r.first.begin = start;
|
||||
else
|
||||
r.first.begin = get_prev_start(y);
|
||||
r.first.end = end;
|
||||
r.first.offset = offset_ + save_;
|
||||
r.first.save = ceil<minutes>(save_);
|
||||
r.first.abbrev = dst_abbrev_;
|
||||
r.second.begin = end;
|
||||
if (northern)
|
||||
r.second.end = get_next_start(y);
|
||||
else
|
||||
r.second.end = start;
|
||||
r.second.abbrev = std_abbrev_;
|
||||
r.second.offset = offset_;
|
||||
r.result = save_ > seconds{0} ? local_info::ambiguous
|
||||
: local_info::nonexistent;
|
||||
}
|
||||
else
|
||||
r.first = get_info(utcs);
|
||||
}
|
||||
else
|
||||
r.first = contant_offset();
|
||||
return r;
|
||||
}
|
||||
|
||||
template <class Duration>
|
||||
date::sys_time<typename std::common_type<Duration, std::chrono::seconds>::type>
|
||||
time_zone::to_sys(date::local_time<Duration> tp) const
|
||||
{
|
||||
using date::local_info;
|
||||
using date::sys_time;
|
||||
using date::ambiguous_local_time;
|
||||
using date::nonexistent_local_time;
|
||||
auto i = get_info(tp);
|
||||
if (i.result == local_info::nonexistent)
|
||||
throw nonexistent_local_time(tp, i);
|
||||
else if (i.result == local_info::ambiguous)
|
||||
throw ambiguous_local_time(tp, i);
|
||||
return sys_time<Duration>{tp.time_since_epoch()} - i.first.offset;
|
||||
}
|
||||
|
||||
template <class Duration>
|
||||
date::sys_time<typename std::common_type<Duration, std::chrono::seconds>::type>
|
||||
time_zone::to_sys(date::local_time<Duration> tp, date::choose z) const
|
||||
{
|
||||
using date::local_info;
|
||||
using date::sys_time;
|
||||
using date::choose;
|
||||
auto i = get_info(tp);
|
||||
if (i.result == local_info::nonexistent)
|
||||
{
|
||||
return i.first.end;
|
||||
}
|
||||
else if (i.result == local_info::ambiguous)
|
||||
{
|
||||
if (z == choose::latest)
|
||||
return sys_time<Duration>{tp.time_since_epoch()} - i.second.offset;
|
||||
}
|
||||
return sys_time<Duration>{tp.time_since_epoch()} - i.first.offset;
|
||||
}
|
||||
|
||||
template <class Duration>
|
||||
date::local_time<typename std::common_type<Duration, std::chrono::seconds>::type>
|
||||
time_zone::to_local(date::sys_time<Duration> tp) const
|
||||
{
|
||||
using date::local_time;
|
||||
using std::chrono::seconds;
|
||||
using LT = local_time<typename std::common_type<Duration, seconds>::type>;
|
||||
auto i = get_info(tp);
|
||||
return LT{(tp + i.offset).time_since_epoch()};
|
||||
}
|
||||
|
||||
inline
|
||||
std::ostream&
|
||||
operator<<(std::ostream& os, const time_zone& z)
|
||||
{
|
||||
using date::operator<<;
|
||||
os << '{';
|
||||
os << z.std_abbrev_ << ", " << z.dst_abbrev_ << date::format(", %T, ", z.offset_)
|
||||
<< date::format("%T, [", z.save_) << z.start_rule_ << ", " << z.end_rule_ << ")}";
|
||||
return os;
|
||||
}
|
||||
|
||||
inline
|
||||
std::string
|
||||
time_zone::name() const
|
||||
{
|
||||
using namespace date;
|
||||
using namespace std::chrono;
|
||||
auto print_abbrev = [](std::string const& nm)
|
||||
{
|
||||
if (std::any_of(nm.begin(), nm.end(),
|
||||
[](char c)
|
||||
{
|
||||
return !std::isalpha(c);
|
||||
}))
|
||||
{
|
||||
return '<' + nm + '>';
|
||||
}
|
||||
return nm;
|
||||
};
|
||||
auto print_offset = [](seconds off)
|
||||
{
|
||||
std::string nm;
|
||||
date::hh_mm_ss<seconds> offset{-off};
|
||||
if (offset.is_negative())
|
||||
nm += '-';
|
||||
nm += std::to_string(offset.hours().count());
|
||||
if (offset.minutes() != minutes{0} || offset.seconds() != seconds{0})
|
||||
{
|
||||
nm += ':';
|
||||
if (offset.minutes() < minutes{10})
|
||||
nm += '0';
|
||||
nm += std::to_string(offset.minutes().count());
|
||||
if (offset.seconds() != seconds{0})
|
||||
{
|
||||
nm += ':';
|
||||
if (offset.seconds() < seconds{10})
|
||||
nm += '0';
|
||||
nm += std::to_string(offset.seconds().count());
|
||||
}
|
||||
}
|
||||
return nm;
|
||||
};
|
||||
auto nm = print_abbrev(std_abbrev_);
|
||||
nm += print_offset(offset_);
|
||||
if (!dst_abbrev_.empty())
|
||||
{
|
||||
nm += print_abbrev(dst_abbrev_);
|
||||
if (save_ != hours{1})
|
||||
nm += print_offset(offset_+save_);
|
||||
if (start_rule_.ok())
|
||||
{
|
||||
nm += ',';
|
||||
nm += start_rule_.to_string();
|
||||
nm += ',';
|
||||
nm += end_rule_.to_string();
|
||||
}
|
||||
}
|
||||
return nm;
|
||||
}
|
||||
|
||||
inline
|
||||
bool
|
||||
operator==(const time_zone& x, const time_zone& y)
|
||||
{
|
||||
return x.std_abbrev_ == y.std_abbrev_ &&
|
||||
x.dst_abbrev_ == y. dst_abbrev_ &&
|
||||
x.offset_ == y.offset_ &&
|
||||
x.save_ == y.save_ &&
|
||||
x.start_rule_ == y.start_rule_ &&
|
||||
x.end_rule_ == y.end_rule_;
|
||||
}
|
||||
|
||||
inline
|
||||
bool
|
||||
operator!=(const time_zone& x, const time_zone& y)
|
||||
{
|
||||
return !(x == y);
|
||||
}
|
||||
|
||||
namespace detail
|
||||
{
|
||||
|
||||
inline
|
||||
void
|
||||
throw_invalid(const string_t& s, unsigned i, const string_t& message)
|
||||
{
|
||||
throw std::runtime_error(std::string("Invalid time_zone initializer.\n") +
|
||||
std::string(message) + ":\n" +
|
||||
std::string(s) + '\n' +
|
||||
"\x1b[1;32m" +
|
||||
std::string(i, '~') + '^' +
|
||||
std::string(i < s.size() ? s.size()-i-1 : 0, '~') +
|
||||
"\x1b[0m");
|
||||
}
|
||||
|
||||
inline
|
||||
unsigned
|
||||
read_date(const string_t& s, unsigned i, rule& r)
|
||||
{
|
||||
using date::month;
|
||||
using date::weekday;
|
||||
if (i == s.size())
|
||||
throw_invalid(s, i, "Expected rule but found end of string");
|
||||
if (s[i] == 'J')
|
||||
{
|
||||
++i;
|
||||
unsigned n;
|
||||
i = read_unsigned(s, i, 3, n, "Expected to find the Julian day [1, 365]");
|
||||
if (!(1 <= n && n <= 365))
|
||||
throw_invalid(s, i-1, "Expected Julian day to be in the range [1, 365]");
|
||||
r.mode_ = rule::J;
|
||||
r.n_ = n;
|
||||
}
|
||||
else if (s[i] == 'M')
|
||||
{
|
||||
++i;
|
||||
unsigned m;
|
||||
i = read_unsigned(s, i, 2, m, "Expected to find month [1, 12]");
|
||||
if (!(1 <= m && m <= 12))
|
||||
throw_invalid(s, i-1, "Expected month to be in the range [1, 12]");
|
||||
if (i == s.size() || s[i] != '.')
|
||||
throw_invalid(s, i, "Expected '.' after month");
|
||||
++i;
|
||||
unsigned n;
|
||||
i = read_unsigned(s, i, 1, n, "Expected to find week number [1, 5]");
|
||||
if (!(1 <= n && n <= 5))
|
||||
throw_invalid(s, i-1, "Expected week number to be in the range [1, 5]");
|
||||
if (i == s.size() || s[i] != '.')
|
||||
throw_invalid(s, i, "Expected '.' after weekday index");
|
||||
++i;
|
||||
unsigned wd;
|
||||
i = read_unsigned(s, i, 1, wd, "Expected to find day of week [0, 6]");
|
||||
if (wd > 6)
|
||||
throw_invalid(s, i-1, "Expected day of week to be in the range [0, 6]");
|
||||
r.mode_ = rule::M;
|
||||
r.m_ = month{m};
|
||||
r.wd_ = weekday{wd};
|
||||
r.n_ = n;
|
||||
}
|
||||
else if (std::isdigit(s[i]))
|
||||
{
|
||||
unsigned n;
|
||||
i = read_unsigned(s, i, 3, n);
|
||||
if (n > 365)
|
||||
throw_invalid(s, i-1, "Expected Julian day to be in the range [0, 365]");
|
||||
r.mode_ = rule::N;
|
||||
r.n_ = n;
|
||||
}
|
||||
else
|
||||
throw_invalid(s, i, "Expected 'J', 'M', or a digit to start rule");
|
||||
if (i != s.size() && s[i] == '/')
|
||||
{
|
||||
++i;
|
||||
std::chrono::seconds t;
|
||||
i = read_unsigned_time(s, i, t);
|
||||
r.time_ = t;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
inline
|
||||
unsigned
|
||||
read_name(const string_t& s, unsigned i, std::string& name)
|
||||
{
|
||||
if (i == s.size())
|
||||
throw_invalid(s, i, "Expected a name but found end of string");
|
||||
if (s[i] == '<')
|
||||
{
|
||||
++i;
|
||||
while (true)
|
||||
{
|
||||
if (i == s.size())
|
||||
throw_invalid(s, i,
|
||||
"Expected to find closing '>', but found end of string");
|
||||
if (s[i] == '>')
|
||||
break;
|
||||
name.push_back(s[i]);
|
||||
++i;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
else
|
||||
{
|
||||
while (i != s.size() && std::isalpha(s[i]))
|
||||
{
|
||||
name.push_back(s[i]);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
inline
|
||||
unsigned
|
||||
read_signed_time(const string_t& s, unsigned i,
|
||||
std::chrono::seconds& t)
|
||||
{
|
||||
if (i == s.size())
|
||||
throw_invalid(s, i, "Expected to read signed time, but found end of string");
|
||||
bool negative = false;
|
||||
if (s[i] == '-')
|
||||
{
|
||||
negative = true;
|
||||
++i;
|
||||
}
|
||||
else if (s[i] == '+')
|
||||
++i;
|
||||
i = read_unsigned_time(s, i, t);
|
||||
if (negative)
|
||||
t = -t;
|
||||
return i;
|
||||
}
|
||||
|
||||
inline
|
||||
unsigned
|
||||
read_unsigned_time(const string_t& s, unsigned i, std::chrono::seconds& t)
|
||||
{
|
||||
using std::chrono::seconds;
|
||||
using std::chrono::minutes;
|
||||
using std::chrono::hours;
|
||||
if (i == s.size())
|
||||
throw_invalid(s, i, "Expected to read unsigned time, but found end of string");
|
||||
unsigned x;
|
||||
i = read_unsigned(s, i, 2, x, "Expected to find hours [0, 24]");
|
||||
if (x > 24)
|
||||
throw_invalid(s, i-1, "Expected hours to be in the range [0, 24]");
|
||||
t = hours{x};
|
||||
if (i != s.size() && s[i] == ':')
|
||||
{
|
||||
++i;
|
||||
i = read_unsigned(s, i, 2, x, "Expected to find minutes [0, 59]");
|
||||
if (x > 59)
|
||||
throw_invalid(s, i-1, "Expected minutes to be in the range [0, 59]");
|
||||
t += minutes{x};
|
||||
if (i != s.size() && s[i] == ':')
|
||||
{
|
||||
++i;
|
||||
i = read_unsigned(s, i, 2, x, "Expected to find seconds [0, 59]");
|
||||
if (x > 59)
|
||||
throw_invalid(s, i-1, "Expected seconds to be in the range [0, 59]");
|
||||
t += seconds{x};
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
inline
|
||||
unsigned
|
||||
read_unsigned(const string_t& s, unsigned i, unsigned limit, unsigned& u,
|
||||
const string_t& message)
|
||||
{
|
||||
if (i == s.size() || !std::isdigit(s[i]))
|
||||
throw_invalid(s, i, message);
|
||||
u = static_cast<unsigned>(s[i] - '0');
|
||||
unsigned count = 1;
|
||||
for (++i; count < limit && i != s.size() && std::isdigit(s[i]); ++i, ++count)
|
||||
u = u * 10 + static_cast<unsigned>(s[i] - '0');
|
||||
return i;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
} // namespace Posix
|
||||
|
||||
namespace date
|
||||
{
|
||||
|
||||
template <>
|
||||
struct zoned_traits<Posix::time_zone>
|
||||
{
|
||||
|
||||
#if HAS_STRING_VIEW
|
||||
|
||||
static
|
||||
Posix::time_zone
|
||||
locate_zone(std::string_view name)
|
||||
{
|
||||
return Posix::time_zone{name};
|
||||
}
|
||||
|
||||
#else // !HAS_STRING_VIEW
|
||||
|
||||
static
|
||||
Posix::time_zone
|
||||
locate_zone(const std::string& name)
|
||||
{
|
||||
return Posix::time_zone{name};
|
||||
}
|
||||
|
||||
static
|
||||
Posix::time_zone
|
||||
locate_zone(const char* name)
|
||||
{
|
||||
return Posix::time_zone{name};
|
||||
}
|
||||
|
||||
#endif // !HAS_STRING_VIEW
|
||||
|
||||
};
|
||||
|
||||
} // namespace date
|
||||
|
||||
#endif // PTZ_H
|
||||
3151
include/date/solar_hijri.h
Normal file
3151
include/date/solar_hijri.h
Normal file
File diff suppressed because it is too large
Load Diff
2808
include/date/tz.h
Normal file
2808
include/date/tz.h
Normal file
File diff suppressed because it is too large
Load Diff
315
include/date/tz_private.h
Normal file
315
include/date/tz_private.h
Normal file
@@ -0,0 +1,315 @@
|
||||
#ifndef TZ_PRIVATE_H
|
||||
#define TZ_PRIVATE_H
|
||||
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) 2015, 2016 Howard Hinnant
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
// Our apologies. When the previous paragraph was written, lowercase had not yet
|
||||
// been invented (that would involve another several millennia of evolution).
|
||||
// We did not mean to shout.
|
||||
|
||||
#if !defined(_MSC_VER) || (_MSC_VER >= 1900)
|
||||
#include "tz.h"
|
||||
#else
|
||||
#include "date.h"
|
||||
#include <vector>
|
||||
#endif
|
||||
|
||||
namespace date
|
||||
{
|
||||
|
||||
namespace detail
|
||||
{
|
||||
|
||||
#if !USE_OS_TZDB
|
||||
|
||||
enum class tz {utc, local, standard};
|
||||
|
||||
//forward declare to avoid warnings in gcc 6.2
|
||||
class MonthDayTime;
|
||||
std::istream& operator>>(std::istream& is, MonthDayTime& x);
|
||||
std::ostream& operator<<(std::ostream& os, const MonthDayTime& x);
|
||||
|
||||
|
||||
class MonthDayTime
|
||||
{
|
||||
private:
|
||||
struct pair
|
||||
{
|
||||
#if defined(_MSC_VER) && (_MSC_VER < 1900)
|
||||
pair() : month_day_(date::jan / 1), weekday_(0U) {}
|
||||
|
||||
pair(const date::month_day& month_day, const date::weekday& weekday)
|
||||
: month_day_(month_day), weekday_(weekday) {}
|
||||
#endif
|
||||
|
||||
date::month_day month_day_;
|
||||
date::weekday weekday_;
|
||||
};
|
||||
|
||||
enum Type {month_day, month_last_dow, lteq, gteq};
|
||||
|
||||
Type type_{month_day};
|
||||
|
||||
#if !defined(_MSC_VER) || (_MSC_VER >= 1900)
|
||||
union U
|
||||
#else
|
||||
struct U
|
||||
#endif
|
||||
{
|
||||
date::month_day month_day_;
|
||||
date::month_weekday_last month_weekday_last_;
|
||||
pair month_day_weekday_;
|
||||
|
||||
#if !defined(_MSC_VER) || (_MSC_VER >= 1900)
|
||||
U() : month_day_{date::jan/1} {}
|
||||
#else
|
||||
U() :
|
||||
month_day_(date::jan/1),
|
||||
month_weekday_last_(date::month(0U), date::weekday_last(date::weekday(0U)))
|
||||
{}
|
||||
|
||||
#endif // !defined(_MSC_VER) || (_MSC_VER >= 1900)
|
||||
|
||||
U& operator=(const date::month_day& x);
|
||||
U& operator=(const date::month_weekday_last& x);
|
||||
U& operator=(const pair& x);
|
||||
} u;
|
||||
|
||||
std::chrono::hours h_{0};
|
||||
std::chrono::minutes m_{0};
|
||||
std::chrono::seconds s_{0};
|
||||
tz zone_{tz::local};
|
||||
|
||||
public:
|
||||
MonthDayTime() = default;
|
||||
MonthDayTime(local_seconds tp, tz timezone);
|
||||
MonthDayTime(const date::month_day& md, tz timezone);
|
||||
|
||||
date::day day() const;
|
||||
date::month month() const;
|
||||
tz zone() const {return zone_;}
|
||||
|
||||
void canonicalize(date::year y);
|
||||
|
||||
sys_seconds
|
||||
to_sys(date::year y, std::chrono::seconds offset, std::chrono::seconds save) const;
|
||||
sys_days to_sys_days(date::year y) const;
|
||||
|
||||
sys_seconds to_time_point(date::year y) const;
|
||||
int compare(date::year y, const MonthDayTime& x, date::year yx,
|
||||
std::chrono::seconds offset, std::chrono::minutes prev_save) const;
|
||||
|
||||
friend std::istream& operator>>(std::istream& is, MonthDayTime& x);
|
||||
friend std::ostream& operator<<(std::ostream& os, const MonthDayTime& x);
|
||||
};
|
||||
|
||||
// A Rule specifies one or more set of datetimes without using an offset.
|
||||
// Multiple dates are specified with multiple years. The years in effect
|
||||
// go from starting_year_ to ending_year_, inclusive. starting_year_ <=
|
||||
// ending_year_. save_ is in effect for times from the specified time
|
||||
// onward, including the specified time. When the specified time is
|
||||
// local, it uses the save_ from the chronologically previous Rule, or if
|
||||
// there is none, 0.
|
||||
|
||||
//forward declare to avoid warnings in gcc 6.2
|
||||
class Rule;
|
||||
bool operator==(const Rule& x, const Rule& y);
|
||||
bool operator<(const Rule& x, const Rule& y);
|
||||
bool operator==(const Rule& x, const date::year& y);
|
||||
bool operator<(const Rule& x, const date::year& y);
|
||||
bool operator==(const date::year& x, const Rule& y);
|
||||
bool operator<(const date::year& x, const Rule& y);
|
||||
bool operator==(const Rule& x, const std::string& y);
|
||||
bool operator<(const Rule& x, const std::string& y);
|
||||
bool operator==(const std::string& x, const Rule& y);
|
||||
bool operator<(const std::string& x, const Rule& y);
|
||||
std::ostream& operator<<(std::ostream& os, const Rule& r);
|
||||
|
||||
class Rule
|
||||
{
|
||||
private:
|
||||
std::string name_;
|
||||
date::year starting_year_{0};
|
||||
date::year ending_year_{0};
|
||||
MonthDayTime starting_at_;
|
||||
std::chrono::minutes save_{0};
|
||||
std::string abbrev_;
|
||||
|
||||
public:
|
||||
Rule() = default;
|
||||
explicit Rule(const std::string& s);
|
||||
Rule(const Rule& r, date::year starting_year, date::year ending_year);
|
||||
|
||||
const std::string& name() const {return name_;}
|
||||
const std::string& abbrev() const {return abbrev_;}
|
||||
|
||||
const MonthDayTime& mdt() const {return starting_at_;}
|
||||
const date::year& starting_year() const {return starting_year_;}
|
||||
const date::year& ending_year() const {return ending_year_;}
|
||||
const std::chrono::minutes& save() const {return save_;}
|
||||
|
||||
static void split_overlaps(std::vector<Rule>& rules);
|
||||
|
||||
friend bool operator==(const Rule& x, const Rule& y);
|
||||
friend bool operator<(const Rule& x, const Rule& y);
|
||||
friend bool operator==(const Rule& x, const date::year& y);
|
||||
friend bool operator<(const Rule& x, const date::year& y);
|
||||
friend bool operator==(const date::year& x, const Rule& y);
|
||||
friend bool operator<(const date::year& x, const Rule& y);
|
||||
friend bool operator==(const Rule& x, const std::string& y);
|
||||
friend bool operator<(const Rule& x, const std::string& y);
|
||||
friend bool operator==(const std::string& x, const Rule& y);
|
||||
friend bool operator<(const std::string& x, const Rule& y);
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, const Rule& r);
|
||||
|
||||
private:
|
||||
date::day day() const;
|
||||
date::month month() const;
|
||||
static void split_overlaps(std::vector<Rule>& rules, std::size_t i, std::size_t& e);
|
||||
static bool overlaps(const Rule& x, const Rule& y);
|
||||
static void split(std::vector<Rule>& rules, std::size_t i, std::size_t k,
|
||||
std::size_t& e);
|
||||
};
|
||||
|
||||
inline bool operator!=(const Rule& x, const Rule& y) {return !(x == y);}
|
||||
inline bool operator> (const Rule& x, const Rule& y) {return y < x;}
|
||||
inline bool operator<=(const Rule& x, const Rule& y) {return !(y < x);}
|
||||
inline bool operator>=(const Rule& x, const Rule& y) {return !(x < y);}
|
||||
|
||||
inline bool operator!=(const Rule& x, const date::year& y) {return !(x == y);}
|
||||
inline bool operator> (const Rule& x, const date::year& y) {return y < x;}
|
||||
inline bool operator<=(const Rule& x, const date::year& y) {return !(y < x);}
|
||||
inline bool operator>=(const Rule& x, const date::year& y) {return !(x < y);}
|
||||
|
||||
inline bool operator!=(const date::year& x, const Rule& y) {return !(x == y);}
|
||||
inline bool operator> (const date::year& x, const Rule& y) {return y < x;}
|
||||
inline bool operator<=(const date::year& x, const Rule& y) {return !(y < x);}
|
||||
inline bool operator>=(const date::year& x, const Rule& y) {return !(x < y);}
|
||||
|
||||
inline bool operator!=(const Rule& x, const std::string& y) {return !(x == y);}
|
||||
inline bool operator> (const Rule& x, const std::string& y) {return y < x;}
|
||||
inline bool operator<=(const Rule& x, const std::string& y) {return !(y < x);}
|
||||
inline bool operator>=(const Rule& x, const std::string& y) {return !(x < y);}
|
||||
|
||||
inline bool operator!=(const std::string& x, const Rule& y) {return !(x == y);}
|
||||
inline bool operator> (const std::string& x, const Rule& y) {return y < x;}
|
||||
inline bool operator<=(const std::string& x, const Rule& y) {return !(y < x);}
|
||||
inline bool operator>=(const std::string& x, const Rule& y) {return !(x < y);}
|
||||
|
||||
struct zonelet
|
||||
{
|
||||
enum tag {has_rule, has_save, is_empty};
|
||||
|
||||
std::chrono::seconds gmtoff_;
|
||||
tag tag_ = has_rule;
|
||||
|
||||
#if !defined(_MSC_VER) || (_MSC_VER >= 1900)
|
||||
union U
|
||||
#else
|
||||
struct U
|
||||
#endif
|
||||
{
|
||||
std::string rule_;
|
||||
std::chrono::minutes save_;
|
||||
|
||||
~U() {}
|
||||
U() {}
|
||||
U(const U&) {}
|
||||
U& operator=(const U&) = delete;
|
||||
} u;
|
||||
|
||||
std::string format_;
|
||||
date::year until_year_{0};
|
||||
MonthDayTime until_date_;
|
||||
sys_seconds until_utc_;
|
||||
local_seconds until_std_;
|
||||
local_seconds until_loc_;
|
||||
std::chrono::minutes initial_save_{0};
|
||||
std::string initial_abbrev_;
|
||||
std::pair<const Rule*, date::year> first_rule_{nullptr, date::year::min()};
|
||||
std::pair<const Rule*, date::year> last_rule_{nullptr, date::year::max()};
|
||||
|
||||
~zonelet();
|
||||
zonelet();
|
||||
zonelet(const zonelet& i);
|
||||
zonelet& operator=(const zonelet&) = delete;
|
||||
};
|
||||
|
||||
#else // USE_OS_TZDB
|
||||
|
||||
struct ttinfo
|
||||
{
|
||||
std::int32_t tt_gmtoff;
|
||||
unsigned char tt_isdst;
|
||||
unsigned char tt_abbrind;
|
||||
unsigned char pad[2];
|
||||
};
|
||||
|
||||
static_assert(sizeof(ttinfo) == 8, "");
|
||||
|
||||
struct expanded_ttinfo
|
||||
{
|
||||
std::chrono::seconds offset;
|
||||
std::string abbrev;
|
||||
bool is_dst;
|
||||
};
|
||||
|
||||
struct transition
|
||||
{
|
||||
sys_seconds timepoint;
|
||||
const expanded_ttinfo* info;
|
||||
|
||||
transition(sys_seconds tp, const expanded_ttinfo* i = nullptr)
|
||||
: timepoint(tp)
|
||||
, info(i)
|
||||
{}
|
||||
|
||||
friend
|
||||
std::ostream&
|
||||
operator<<(std::ostream& os, const transition& t)
|
||||
{
|
||||
date::operator<<(os, t.timepoint) << "Z ";
|
||||
if (t.info->offset >= std::chrono::seconds{0})
|
||||
os << '+';
|
||||
os << make_time(t.info->offset);
|
||||
if (t.info->is_dst > 0)
|
||||
os << " daylight ";
|
||||
else
|
||||
os << " standard ";
|
||||
os << t.info->abbrev;
|
||||
return os;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // USE_OS_TZDB
|
||||
|
||||
} // namespace detail
|
||||
|
||||
} // namespace date
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER < 1900)
|
||||
#include "tz.h"
|
||||
#endif
|
||||
|
||||
#endif // TZ_PRIVATE_H
|
||||
1990
include/httplib.h
1990
include/httplib.h
File diff suppressed because it is too large
Load Diff
300
include/libcron/Cron.h
Normal file
300
include/libcron/Cron.h
Normal file
@@ -0,0 +1,300 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include "Task.h"
|
||||
#include "CronClock.h"
|
||||
#include "TaskQueue.h"
|
||||
|
||||
namespace libcron
|
||||
{
|
||||
class NullLock
|
||||
{
|
||||
public:
|
||||
void lock() {}
|
||||
void unlock() {}
|
||||
};
|
||||
|
||||
class Locker
|
||||
{
|
||||
public:
|
||||
void lock() { m.lock(); }
|
||||
void unlock() { m.unlock(); }
|
||||
private:
|
||||
std::recursive_mutex m{};
|
||||
};
|
||||
|
||||
template<typename ClockType, typename LockType>
|
||||
class Cron;
|
||||
|
||||
template<typename ClockType, typename LockType>
|
||||
std::ostream& operator<<(std::ostream& stream, const Cron<ClockType, LockType>& c);
|
||||
|
||||
template<typename ClockType = libcron::LocalClock,
|
||||
typename LockType = libcron::NullLock>
|
||||
class Cron
|
||||
{
|
||||
public:
|
||||
bool add_schedule(std::string name, const std::string& schedule, Task::TaskFunction work);
|
||||
|
||||
template<typename Schedules = std::map<std::string, std::string>>
|
||||
std::tuple<bool, std::string, std::string>
|
||||
add_schedule(const Schedules& name_schedule_map, Task::TaskFunction work);
|
||||
void clear_schedules();
|
||||
void remove_schedule(const std::string& name);
|
||||
|
||||
size_t count() const
|
||||
{
|
||||
return tasks.size();
|
||||
}
|
||||
|
||||
// Tick is expected to be called at least once a second to prevent missing schedules.
|
||||
size_t
|
||||
tick()
|
||||
{
|
||||
return tick(clock.now());
|
||||
}
|
||||
|
||||
size_t
|
||||
tick(std::chrono::system_clock::time_point now);
|
||||
|
||||
std::chrono::system_clock::duration
|
||||
time_until_next() const;
|
||||
|
||||
ClockType& get_clock()
|
||||
{
|
||||
return clock;
|
||||
}
|
||||
|
||||
void recalculate_schedule()
|
||||
{
|
||||
for (auto& t : tasks.get_tasks())
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
// Ensure that next schedule is in the future
|
||||
t.calculate_next(clock.now() + 1s);
|
||||
}
|
||||
}
|
||||
|
||||
void get_time_until_expiry_for_tasks(
|
||||
std::vector<std::tuple<std::string, std::chrono::system_clock::duration>>& status) const;
|
||||
|
||||
friend std::ostream& operator<<<>(std::ostream& stream, const Cron<ClockType, LockType>& c);
|
||||
|
||||
private:
|
||||
TaskQueue<LockType> tasks{};
|
||||
ClockType clock{};
|
||||
bool first_tick = true;
|
||||
std::chrono::system_clock::time_point last_tick{};
|
||||
};
|
||||
|
||||
template<typename ClockType, typename LockType>
|
||||
bool Cron<ClockType, LockType>::add_schedule(std::string name, const std::string& schedule, Task::TaskFunction work)
|
||||
{
|
||||
auto cron = CronData::create(schedule);
|
||||
bool res = cron.is_valid();
|
||||
if (res)
|
||||
{
|
||||
tasks.lock_queue();
|
||||
Task t{std::move(name), CronSchedule{cron}, work };
|
||||
if (t.calculate_next(clock.now()))
|
||||
{
|
||||
tasks.push(t);
|
||||
tasks.sort();
|
||||
}
|
||||
tasks.release_queue();
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
template<typename ClockType, typename LockType>
|
||||
template<typename Schedules>
|
||||
std::tuple<bool, std::string, std::string>
|
||||
Cron<ClockType, LockType>::add_schedule(const Schedules& name_schedule_map, Task::TaskFunction work)
|
||||
{
|
||||
bool is_valid = true;
|
||||
std::tuple<bool, std::string, std::string> res{false, "", ""};
|
||||
|
||||
std::vector<Task> tasks_to_add;
|
||||
tasks_to_add.reserve(name_schedule_map.size());
|
||||
|
||||
for (auto it = name_schedule_map.begin(); is_valid && it != name_schedule_map.end(); ++it)
|
||||
{
|
||||
const auto& [name, schedule] = *it;
|
||||
auto cron = CronData::create(schedule);
|
||||
is_valid = cron.is_valid();
|
||||
if (is_valid)
|
||||
{
|
||||
Task t{std::move(name), CronSchedule{cron}, work };
|
||||
if (t.calculate_next(clock.now()))
|
||||
{
|
||||
tasks_to_add.push_back(std::move(t));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::get<1>(res) = name;
|
||||
std::get<2>(res) = schedule;
|
||||
}
|
||||
}
|
||||
|
||||
// Only add tasks and sort once if all elements in the map where valid
|
||||
if (is_valid && tasks_to_add.size() > 0)
|
||||
{
|
||||
tasks.lock_queue();
|
||||
tasks.push(tasks_to_add);
|
||||
tasks.sort();
|
||||
tasks.release_queue();
|
||||
}
|
||||
|
||||
std::get<0>(res) = is_valid;
|
||||
return res;
|
||||
}
|
||||
|
||||
template<typename ClockType, typename LockType>
|
||||
void Cron<ClockType, LockType>::clear_schedules()
|
||||
{
|
||||
tasks.clear();
|
||||
}
|
||||
|
||||
template<typename ClockType, typename LockType>
|
||||
void Cron<ClockType, LockType>::remove_schedule(const std::string& name)
|
||||
{
|
||||
tasks.remove(name);
|
||||
}
|
||||
|
||||
template<typename ClockType, typename LockType>
|
||||
std::chrono::system_clock::duration Cron<ClockType, LockType>::time_until_next() const
|
||||
{
|
||||
std::chrono::system_clock::duration d{};
|
||||
if (tasks.empty())
|
||||
{
|
||||
d = std::numeric_limits<std::chrono::minutes>::max();
|
||||
}
|
||||
else
|
||||
{
|
||||
d = tasks.top().time_until_expiry(clock.now());
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
template<typename ClockType, typename LockType>
|
||||
size_t Cron<ClockType, LockType>::tick(std::chrono::system_clock::time_point now)
|
||||
{
|
||||
tasks.lock_queue();
|
||||
size_t res = 0;
|
||||
|
||||
if(!first_tick)
|
||||
{
|
||||
// Only allow time to flow if at least one second has passed since the last tick,
|
||||
// either forward or backward.
|
||||
auto diff = now - last_tick;
|
||||
|
||||
constexpr auto one_second = std::chrono::seconds{1};
|
||||
|
||||
if(diff < one_second && diff > -one_second)
|
||||
{
|
||||
now = last_tick;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (first_tick)
|
||||
{
|
||||
first_tick = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// https://linux.die.net/man/8/cron
|
||||
|
||||
constexpr auto three_hours = std::chrono::hours{3};
|
||||
auto diff = now - last_tick;
|
||||
auto absolute_diff = diff > diff.zero() ? diff : -diff;
|
||||
|
||||
if(absolute_diff >= three_hours)
|
||||
{
|
||||
// Time changes of more than 3 hours are considered to be corrections to the
|
||||
// clock or timezone, and the new time is used immediately.
|
||||
for (auto& t : tasks.get_tasks())
|
||||
{
|
||||
t.calculate_next(now);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Change of less than three hours
|
||||
|
||||
// If time has moved backwards: Since tasks are not rescheduled, they won't run before
|
||||
// we're back at least the original point in time which prevents running tasks twice.
|
||||
|
||||
// If time has moved forward, tasks that would have run since last tick will be run.
|
||||
}
|
||||
}
|
||||
|
||||
last_tick = now;
|
||||
|
||||
if (!tasks.empty())
|
||||
{
|
||||
for (size_t i = 0; i < tasks.size(); i++)
|
||||
{
|
||||
if (tasks.at(i).is_expired(now))
|
||||
{
|
||||
auto& t = tasks.at(i);
|
||||
t.execute(now);
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
if (!t.calculate_next(now + 1s))
|
||||
{
|
||||
tasks.remove(t);
|
||||
}
|
||||
|
||||
res++;
|
||||
}
|
||||
}
|
||||
|
||||
// Only sort if at least one task was executed
|
||||
if (res > 0)
|
||||
{
|
||||
tasks.sort();
|
||||
}
|
||||
}
|
||||
|
||||
tasks.release_queue();
|
||||
return res;
|
||||
}
|
||||
|
||||
template<typename ClockType, typename LockType>
|
||||
void Cron<ClockType, LockType>::get_time_until_expiry_for_tasks(std::vector<std::tuple<std::string,
|
||||
std::chrono::system_clock::duration>>& status) const
|
||||
{
|
||||
auto now = clock.now();
|
||||
status.clear();
|
||||
|
||||
std::for_each(tasks.get_tasks().cbegin(), tasks.get_tasks().cend(),
|
||||
[&status, &now](const Task& t)
|
||||
{
|
||||
status.emplace_back(t.get_name(), t.time_until_expiry(now));
|
||||
});
|
||||
}
|
||||
|
||||
template<typename ClockType, typename LockType>
|
||||
std::ostream& operator<<(std::ostream& stream, const Cron<ClockType, LockType>& c)
|
||||
{
|
||||
std::for_each(c.tasks.get_tasks().cbegin(), c.tasks.get_tasks().cend(),
|
||||
[&stream, &c](const Task& t)
|
||||
{
|
||||
stream << t.get_status(c.clock.now()) << '\n';
|
||||
});
|
||||
|
||||
return stream;
|
||||
}
|
||||
}
|
||||
42
include/libcron/CronClock.h
Normal file
42
include/libcron/CronClock.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace libcron
|
||||
{
|
||||
class ICronClock
|
||||
{
|
||||
public:
|
||||
virtual std::chrono::system_clock::time_point now() const = 0;
|
||||
virtual std::chrono::seconds utc_offset(std::chrono::system_clock::time_point now) const = 0;
|
||||
};
|
||||
|
||||
class UTCClock
|
||||
: public ICronClock
|
||||
{
|
||||
public:
|
||||
std::chrono::system_clock::time_point now() const override
|
||||
{
|
||||
return std::chrono::system_clock::now();
|
||||
}
|
||||
|
||||
std::chrono::seconds utc_offset(std::chrono::system_clock::time_point) const override
|
||||
{
|
||||
using namespace std::chrono;
|
||||
return 0s;
|
||||
}
|
||||
};
|
||||
|
||||
class LocalClock
|
||||
: public ICronClock
|
||||
{
|
||||
public:
|
||||
std::chrono::system_clock::time_point now() const override
|
||||
{
|
||||
auto now = std::chrono::system_clock::now();
|
||||
return now + utc_offset(now);
|
||||
}
|
||||
|
||||
std::chrono::seconds utc_offset(std::chrono::system_clock::time_point now) const override;
|
||||
};
|
||||
}
|
||||
389
include/libcron/CronData.h
Normal file
389
include/libcron/CronData.h
Normal file
@@ -0,0 +1,389 @@
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
#include <regex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <libcron/TimeTypes.h>
|
||||
|
||||
namespace libcron
|
||||
{
|
||||
class CronData
|
||||
{
|
||||
public:
|
||||
static const int NUMBER_OF_LONG_MONTHS = 7;
|
||||
static const libcron::Months months_with_31[NUMBER_OF_LONG_MONTHS];
|
||||
|
||||
static CronData create(const std::string& cron_expression);
|
||||
|
||||
CronData() = default;
|
||||
|
||||
CronData(const CronData&) = default;
|
||||
|
||||
CronData& operator=(const CronData&) = default;
|
||||
|
||||
bool is_valid() const
|
||||
{
|
||||
return valid;
|
||||
}
|
||||
|
||||
const std::set<Seconds>& get_seconds() const
|
||||
{
|
||||
return seconds;
|
||||
}
|
||||
|
||||
const std::set<Minutes>& get_minutes() const
|
||||
{
|
||||
return minutes;
|
||||
}
|
||||
|
||||
const std::set<Hours>& get_hours() const
|
||||
{
|
||||
return hours;
|
||||
}
|
||||
|
||||
const std::set<DayOfMonth>& get_day_of_month() const
|
||||
{
|
||||
return day_of_month;
|
||||
}
|
||||
|
||||
const std::set<Months>& get_months() const
|
||||
{
|
||||
return months;
|
||||
}
|
||||
|
||||
const std::set<DayOfWeek>& get_day_of_week() const
|
||||
{
|
||||
return day_of_week;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static uint8_t value_of(T t)
|
||||
{
|
||||
return static_cast<uint8_t>(t);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static bool has_any_in_range(const std::set<T>& set, uint8_t low, uint8_t high)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
for (auto i = low; !found && i <= high; ++i)
|
||||
{
|
||||
found |= set.find(static_cast<T>(i)) != set.end();
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool convert_from_string_range_to_number_range(const std::string& range, std::set<T>& numbers);
|
||||
|
||||
template<typename T>
|
||||
static std::string& replace_string_name_with_numeric(std::string& s);
|
||||
|
||||
private:
|
||||
void parse(const std::string& cron_expression);
|
||||
|
||||
template<typename T>
|
||||
bool validate_numeric(const std::string& s, std::set<T>& numbers);
|
||||
|
||||
template<typename T>
|
||||
bool validate_literal(const std::string& s,
|
||||
std::set<T>& numbers,
|
||||
const std::vector<std::string>& names);
|
||||
|
||||
template<typename T>
|
||||
bool process_parts(const std::vector<std::string>& parts, std::set<T>& numbers);
|
||||
|
||||
template<typename T>
|
||||
bool add_number(std::set<T>& set, int32_t number);
|
||||
|
||||
template<typename T>
|
||||
bool is_within_limits(int32_t low, int32_t high);
|
||||
|
||||
template<typename T>
|
||||
bool get_range(const std::string& s, T& low, T& high);
|
||||
|
||||
template<typename T>
|
||||
bool get_step(const std::string& s, uint8_t& start, uint8_t& step);
|
||||
|
||||
std::vector<std::string> split(const std::string& s, char token);
|
||||
|
||||
bool is_number(const std::string& s);
|
||||
|
||||
bool is_between(int32_t value, int32_t low_limit, int32_t high_limit);
|
||||
|
||||
bool validate_date_vs_months() const;
|
||||
|
||||
bool check_dom_vs_dow(const std::string& dom, const std::string& dow) const;
|
||||
|
||||
std::set<Seconds> seconds{};
|
||||
std::set<Minutes> minutes{};
|
||||
std::set<Hours> hours{};
|
||||
std::set<DayOfMonth> day_of_month{};
|
||||
std::set<Months> months{};
|
||||
std::set<DayOfWeek> day_of_week{};
|
||||
bool valid = false;
|
||||
|
||||
static const std::vector<std::string> month_names;
|
||||
static const std::vector<std::string> day_names;
|
||||
static std::unordered_map<std::string, CronData> cache;
|
||||
|
||||
template<typename T>
|
||||
void add_full_range(std::set<T>& set);
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
bool CronData::validate_numeric(const std::string& s, std::set<T>& numbers)
|
||||
{
|
||||
std::vector<std::string> parts = split(s, ',');
|
||||
|
||||
return process_parts(parts, numbers);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool CronData::validate_literal(const std::string& s,
|
||||
std::set<T>& numbers,
|
||||
const std::vector<std::string>& names)
|
||||
{
|
||||
std::vector<std::string> parts = split(s, ',');
|
||||
|
||||
auto value_of_first_name = value_of(T::First);
|
||||
|
||||
// Replace each found name with the corresponding value.
|
||||
for (const auto& name : names)
|
||||
{
|
||||
std::regex m(name, std::regex_constants::ECMAScript | std::regex_constants::icase);
|
||||
|
||||
for (auto& part : parts)
|
||||
{
|
||||
std::string replaced;
|
||||
std::regex_replace(std::back_inserter(replaced), part.begin(), part.end(), m,
|
||||
std::to_string(value_of_first_name));
|
||||
|
||||
part = replaced;
|
||||
}
|
||||
|
||||
value_of_first_name++;
|
||||
}
|
||||
|
||||
return process_parts(parts, numbers);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool CronData::process_parts(const std::vector<std::string>& parts, std::set<T>& numbers)
|
||||
{
|
||||
bool res = true;
|
||||
|
||||
for (const auto& p : parts)
|
||||
{
|
||||
res &= convert_from_string_range_to_number_range(p, numbers);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool CronData::get_range(const std::string& s, T& low, T& high)
|
||||
{
|
||||
bool res = false;
|
||||
|
||||
auto value_range = R"#((\d+)-(\d+))#";
|
||||
|
||||
std::regex range(value_range, std::regex_constants::ECMAScript);
|
||||
|
||||
std::smatch match;
|
||||
|
||||
if (std::regex_match(s.begin(), s.end(), match, range))
|
||||
{
|
||||
auto left = std::stoi(match[1].str());
|
||||
auto right = std::stoi(match[2].str());
|
||||
|
||||
if (is_within_limits<T>(left, right))
|
||||
{
|
||||
low = static_cast<T>(left);
|
||||
high = static_cast<T>(right);
|
||||
res = true;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool CronData::get_step(const std::string& s, uint8_t& start, uint8_t& step)
|
||||
{
|
||||
bool res = false;
|
||||
|
||||
auto value_range = R"#((\d+|\*)/(\d+))#";
|
||||
|
||||
std::regex range(value_range, std::regex_constants::ECMAScript);
|
||||
|
||||
std::smatch match;
|
||||
|
||||
if (std::regex_match(s.begin(), s.end(), match, range))
|
||||
{
|
||||
int raw_start;
|
||||
|
||||
if (match[1].str() == "*")
|
||||
{
|
||||
raw_start = value_of(T::First);
|
||||
}
|
||||
else
|
||||
{
|
||||
raw_start = std::stoi(match[1].str());
|
||||
}
|
||||
|
||||
auto raw_step = std::stoi(match[2].str());
|
||||
|
||||
if (is_within_limits<T>(raw_start, raw_start) && raw_step > 0)
|
||||
{
|
||||
start = static_cast<uint8_t>(raw_start);
|
||||
step = static_cast<uint8_t>(raw_step);
|
||||
res = true;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void CronData::add_full_range(std::set<T>& set)
|
||||
{
|
||||
for (auto v = value_of(T::First); v <= value_of(T::Last); ++v)
|
||||
{
|
||||
if (set.find(static_cast<T>(v)) == set.end())
|
||||
{
|
||||
set.emplace(static_cast<T>(v));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool CronData::add_number(std::set<T>& set, int32_t number)
|
||||
{
|
||||
bool res = true;
|
||||
|
||||
// Don't add if already there
|
||||
if (set.find(static_cast<T>(number)) == set.end())
|
||||
{
|
||||
// Check range
|
||||
if (is_within_limits<T>(number, number))
|
||||
{
|
||||
set.emplace(static_cast<T>(number));
|
||||
}
|
||||
else
|
||||
{
|
||||
res = false;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool CronData::is_within_limits(int32_t low, int32_t high)
|
||||
{
|
||||
return is_between(low, value_of(T::First), value_of(T::Last))
|
||||
&& is_between(high, value_of(T::First), value_of(T::Last));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool CronData::convert_from_string_range_to_number_range(const std::string& range, std::set<T>& numbers)
|
||||
{
|
||||
T left;
|
||||
T right;
|
||||
uint8_t step_start;
|
||||
uint8_t step;
|
||||
|
||||
bool res = true;
|
||||
|
||||
if (range == "*" || range == "?")
|
||||
{
|
||||
// We treat the ignore-character '?' the same as the full range being allowed.
|
||||
add_full_range<T>(numbers);
|
||||
}
|
||||
else if (is_number(range))
|
||||
{
|
||||
res = add_number<T>(numbers, std::stoi(range));
|
||||
}
|
||||
else if (get_range<T>(range, left, right))
|
||||
{
|
||||
// A range can be written as both 1-22 or 22-1, meaning totally different ranges.
|
||||
// First case is 1...22 while 22-1 is only four hours: 22, 23, 0, 1.
|
||||
if (left <= right)
|
||||
{
|
||||
for (auto v = value_of(left); v <= value_of(right); ++v)
|
||||
{
|
||||
res &= add_number(numbers, v);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 'left' and 'right' are not in value order. First, get values between 'left' and T::Last, inclusive
|
||||
for (auto v = value_of(left); v <= value_of(T::Last); ++v)
|
||||
{
|
||||
res = add_number(numbers, v);
|
||||
}
|
||||
|
||||
// Next, get values between T::First and 'right', inclusive.
|
||||
for (auto v = value_of(T::First); v <= value_of(right); ++v)
|
||||
{
|
||||
res = add_number(numbers, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (get_step<T>(range, step_start, step))
|
||||
{
|
||||
// Add from step_start to T::Last with a step of 'step'
|
||||
for (auto v = step_start; v <= value_of(T::Last); v += step)
|
||||
{
|
||||
res = add_number(numbers, v);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
res = false;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::string & CronData::replace_string_name_with_numeric(std::string& s)
|
||||
{
|
||||
auto value = static_cast<int>(T::First);
|
||||
|
||||
const std::vector<std::string>* name_source{};
|
||||
|
||||
static_assert(std::is_same<T, libcron::Months>()
|
||||
|| std::is_same<T, libcron::DayOfWeek>(),
|
||||
"T must be either Months or DayOfWeek");
|
||||
|
||||
if constexpr (std::is_same<T, libcron::Months>())
|
||||
{
|
||||
name_source = &month_names;
|
||||
}
|
||||
else
|
||||
{
|
||||
name_source = &day_names;
|
||||
}
|
||||
|
||||
for (const auto& name : *name_source)
|
||||
{
|
||||
std::regex m(name, std::regex_constants::ECMAScript | std::regex_constants::icase);
|
||||
|
||||
std::string replaced;
|
||||
|
||||
std::regex_replace(std::back_inserter(replaced), s.begin(), s.end(), m, std::to_string(value));
|
||||
|
||||
s = replaced;
|
||||
|
||||
++value;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
}
|
||||
100
include/libcron/CronRandomization.h
Normal file
100
include/libcron/CronRandomization.h
Normal file
@@ -0,0 +1,100 @@
|
||||
#pragma once
|
||||
|
||||
#include <tuple>
|
||||
#include <random>
|
||||
#include <regex>
|
||||
#include <functional>
|
||||
#include "CronData.h"
|
||||
|
||||
namespace libcron
|
||||
{
|
||||
class CronRandomization
|
||||
{
|
||||
public:
|
||||
std::tuple<bool, std::string> parse(const std::string& cron_schedule);
|
||||
|
||||
CronRandomization();
|
||||
|
||||
CronRandomization(const CronRandomization&) = delete;
|
||||
|
||||
CronRandomization & operator=(const CronRandomization &) = delete;
|
||||
|
||||
private:
|
||||
template<typename T>
|
||||
std::pair<bool, std::string> get_random_in_range(const std::string& section,
|
||||
int& selected_value,
|
||||
std::pair<int, int> limit = std::make_pair(-1, -1));
|
||||
|
||||
std::pair<int, int> day_limiter(const std::set<Months>& month);
|
||||
|
||||
int cap(int value, int lower, int upper);
|
||||
|
||||
std::regex const rand_expression{ R"#([rR]\((\d+)\-(\d+)\))#", std::regex_constants::ECMAScript };
|
||||
std::random_device rd{};
|
||||
std::mt19937 twister;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
std::pair<bool, std::string> CronRandomization::get_random_in_range(const std::string& section,
|
||||
int& selected_value,
|
||||
std::pair<int, int> limit)
|
||||
{
|
||||
auto res = std::make_pair(true, std::string{});
|
||||
selected_value = -1;
|
||||
|
||||
std::smatch random_match;
|
||||
|
||||
if (std::regex_match(section.cbegin(), section.cend(), random_match, rand_expression))
|
||||
{
|
||||
// Random range, get left and right numbers.
|
||||
auto left = std::stoi(random_match[1].str());
|
||||
auto right = std::stoi(random_match[2].str());
|
||||
|
||||
if (limit.first != -1 && limit.second != -1)
|
||||
{
|
||||
left = cap(left, limit.first, limit.second);
|
||||
right = cap(right, limit.first, limit.second);
|
||||
}
|
||||
|
||||
libcron::CronData cd;
|
||||
std::set<T> numbers;
|
||||
res.first = cd.convert_from_string_range_to_number_range<T>(
|
||||
std::to_string(left) + "-" + std::to_string(right), numbers);
|
||||
|
||||
// Remove items outside limits.
|
||||
if (limit.first != -1 && limit.second != -1)
|
||||
{
|
||||
for (auto it = numbers.begin(); it != numbers.end(); )
|
||||
{
|
||||
if (CronData::value_of(*it) < limit.first || CronData::value_of(*it) > limit.second)
|
||||
{
|
||||
it = numbers.erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (res.first)
|
||||
{
|
||||
// Generate random indexes to select one of the numbers in the range.
|
||||
std::uniform_int_distribution<> dis(0, static_cast<int>(numbers.size() - 1));
|
||||
|
||||
// Select the random number to use as the schedule
|
||||
auto it = numbers.begin();
|
||||
std::advance(it, dis(twister));
|
||||
selected_value = CronData::value_of(*it);
|
||||
res.second = std::to_string(selected_value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not random, just append input to output.
|
||||
res.second = section;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
56
include/libcron/CronSchedule.h
Normal file
56
include/libcron/CronSchedule.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include "libcron/CronData.h"
|
||||
#include <chrono>
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable:4244)
|
||||
#endif
|
||||
#include <date/date.h>
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#include "libcron/DateTime.h"
|
||||
|
||||
namespace libcron
|
||||
{
|
||||
class CronSchedule
|
||||
{
|
||||
public:
|
||||
explicit CronSchedule(CronData& data)
|
||||
: data(data)
|
||||
{
|
||||
}
|
||||
|
||||
CronSchedule(const CronSchedule&) = default;
|
||||
|
||||
CronSchedule& operator=(const CronSchedule&) = default;
|
||||
|
||||
std::tuple<bool, std::chrono::system_clock::time_point>
|
||||
calculate_from(const std::chrono::system_clock::time_point& from) const;
|
||||
|
||||
// https://github.com/HowardHinnant/date/wiki/Examples-and-Recipes#obtaining-ymd-hms-components-from-a-time_point
|
||||
static DateTime to_calendar_time(std::chrono::system_clock::time_point time)
|
||||
{
|
||||
auto daypoint = date::floor<date::days>(time);
|
||||
auto ymd = date::year_month_day(daypoint); // calendar date
|
||||
auto time_of_day = date::make_time(time - daypoint); // Yields time_of_day type
|
||||
|
||||
// Obtain individual components as integers
|
||||
DateTime dt{
|
||||
int(ymd.year()),
|
||||
unsigned(ymd.month()),
|
||||
unsigned(ymd.day()),
|
||||
static_cast<uint8_t>(time_of_day.hours().count()),
|
||||
static_cast<uint8_t>(time_of_day.minutes().count()),
|
||||
static_cast<uint8_t>(time_of_day.seconds().count())};
|
||||
|
||||
return dt;
|
||||
}
|
||||
|
||||
private:
|
||||
CronData data;
|
||||
};
|
||||
|
||||
}
|
||||
16
include/libcron/DateTime.h
Normal file
16
include/libcron/DateTime.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace libcron
|
||||
{
|
||||
struct DateTime
|
||||
{
|
||||
int year = 0;
|
||||
unsigned month = 0;
|
||||
unsigned day = 0;
|
||||
uint8_t hour = 0;
|
||||
uint8_t min = 0;
|
||||
uint8_t sec = 0;
|
||||
};
|
||||
}
|
||||
100
include/libcron/Task.h
Normal file
100
include/libcron/Task.h
Normal file
@@ -0,0 +1,100 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <chrono>
|
||||
#include <utility>
|
||||
#include "CronData.h"
|
||||
#include "CronSchedule.h"
|
||||
|
||||
namespace libcron
|
||||
{
|
||||
class TaskInformation
|
||||
{
|
||||
public:
|
||||
virtual ~TaskInformation() = default;
|
||||
virtual std::chrono::system_clock::duration get_delay() const = 0;
|
||||
virtual std::string get_name() const = 0;
|
||||
};
|
||||
|
||||
class Task : public TaskInformation
|
||||
{
|
||||
public:
|
||||
using TaskFunction = std::function<void(const TaskInformation&)>;
|
||||
|
||||
Task(std::string name, const CronSchedule schedule, TaskFunction task)
|
||||
: name(std::move(name)), schedule(std::move(schedule)), task(std::move(task))
|
||||
{
|
||||
}
|
||||
|
||||
void execute(std::chrono::system_clock::time_point now)
|
||||
{
|
||||
// Next Schedule is still the current schedule, calculate delay (actual execution - planned execution)
|
||||
delay = now - next_schedule;
|
||||
|
||||
last_run = now;
|
||||
task(*this);
|
||||
}
|
||||
|
||||
std::chrono::system_clock::duration get_delay() const override
|
||||
{
|
||||
return delay;
|
||||
}
|
||||
|
||||
Task(const Task& other) = default;
|
||||
|
||||
Task& operator=(const Task&) = default;
|
||||
|
||||
bool calculate_next(std::chrono::system_clock::time_point from);
|
||||
|
||||
bool operator>(const Task& other) const
|
||||
{
|
||||
return next_schedule > other.next_schedule;
|
||||
}
|
||||
|
||||
bool operator<(const Task& other) const
|
||||
{
|
||||
return next_schedule < other.next_schedule;
|
||||
}
|
||||
|
||||
bool is_expired(std::chrono::system_clock::time_point now) const;
|
||||
|
||||
std::chrono::system_clock::duration
|
||||
time_until_expiry(std::chrono::system_clock::time_point now) const;
|
||||
|
||||
std::string get_name() const override
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
std::string get_status(std::chrono::system_clock::time_point now) const;
|
||||
|
||||
private:
|
||||
std::string name;
|
||||
CronSchedule schedule;
|
||||
std::chrono::system_clock::time_point next_schedule;
|
||||
std::chrono::system_clock::duration delay = std::chrono::seconds(-1);
|
||||
TaskFunction task;
|
||||
bool valid = false;
|
||||
std::chrono::system_clock::time_point last_run = std::numeric_limits<std::chrono::system_clock::time_point>::min();
|
||||
};
|
||||
}
|
||||
|
||||
inline bool operator==(const std::string &lhs, const libcron::Task &rhs)
|
||||
{
|
||||
return lhs == rhs.get_name();
|
||||
}
|
||||
|
||||
inline bool operator==(const libcron::Task &lhs, const std::string &rhs)
|
||||
{
|
||||
return lhs.get_name() == rhs;
|
||||
}
|
||||
|
||||
inline bool operator!=(const std::string &lhs, const libcron::Task &rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
inline bool operator!=(const libcron::Task &lhs, const std::string &rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
115
include/libcron/TaskQueue.h
Normal file
115
include/libcron/TaskQueue.h
Normal file
@@ -0,0 +1,115 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include "Task.h"
|
||||
|
||||
namespace libcron
|
||||
{
|
||||
template<typename LockType>
|
||||
class TaskQueue
|
||||
{
|
||||
public:
|
||||
const std::vector<Task>& get_tasks() const
|
||||
{
|
||||
return c;
|
||||
}
|
||||
|
||||
std::vector<Task>& get_tasks()
|
||||
{
|
||||
return c;
|
||||
}
|
||||
|
||||
size_t size() const noexcept
|
||||
{
|
||||
return c.size();
|
||||
}
|
||||
|
||||
bool empty() const noexcept
|
||||
{
|
||||
return c.empty();
|
||||
}
|
||||
|
||||
void push(Task& t)
|
||||
{
|
||||
c.push_back(std::move(t));
|
||||
}
|
||||
|
||||
void push(Task&& t)
|
||||
{
|
||||
c.push_back(std::move(t));
|
||||
}
|
||||
|
||||
void push(std::vector<Task>& tasks_to_insert)
|
||||
{
|
||||
c.reserve(c.size() + tasks_to_insert.size());
|
||||
c.insert(c.end(), std::make_move_iterator(tasks_to_insert.begin()), std::make_move_iterator(tasks_to_insert.end()));
|
||||
}
|
||||
|
||||
const Task& top() const
|
||||
{
|
||||
return c[0];
|
||||
}
|
||||
|
||||
Task& at(const size_t i)
|
||||
{
|
||||
return c[i];
|
||||
}
|
||||
|
||||
void sort()
|
||||
{
|
||||
std::sort(c.begin(), c.end(), std::less<>());
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
lock.lock();
|
||||
c.clear();
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
void remove(Task& to_remove)
|
||||
{
|
||||
auto it = std::find_if(c.begin(), c.end(), [&to_remove] (const Task& to_compare) {
|
||||
return to_remove.get_name() == to_compare;
|
||||
});
|
||||
|
||||
if (it != c.end())
|
||||
{
|
||||
c.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void remove(std::string to_remove)
|
||||
{
|
||||
lock.lock();
|
||||
auto it = std::find_if(c.begin(), c.end(), [&to_remove] (const Task& to_compare) {
|
||||
return to_remove == to_compare;
|
||||
});
|
||||
if (it != c.end())
|
||||
{
|
||||
c.erase(it);
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
void lock_queue()
|
||||
{
|
||||
/* Do not allow to manipulate the Queue */
|
||||
lock.lock();
|
||||
}
|
||||
|
||||
void release_queue()
|
||||
{
|
||||
/* Allow Access to the Queue Manipulating-Functions */
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
private:
|
||||
LockType lock;
|
||||
std::vector<Task> c;
|
||||
};
|
||||
}
|
||||
55
include/libcron/TimeTypes.h
Normal file
55
include/libcron/TimeTypes.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace libcron
|
||||
{
|
||||
enum class Seconds : int8_t
|
||||
{
|
||||
First = 0,
|
||||
Last = 59
|
||||
};
|
||||
|
||||
enum class Minutes : int8_t
|
||||
{
|
||||
First = 0,
|
||||
Last = 59
|
||||
};
|
||||
|
||||
enum class Hours : int8_t
|
||||
{
|
||||
First = 0,
|
||||
Last = 23
|
||||
};
|
||||
|
||||
enum class DayOfMonth : uint8_t
|
||||
{
|
||||
First = 1,
|
||||
Last = 31
|
||||
};
|
||||
|
||||
enum class Months : uint8_t
|
||||
{
|
||||
First = 1,
|
||||
January = First,
|
||||
February,
|
||||
March,
|
||||
April,
|
||||
May,
|
||||
June,
|
||||
July,
|
||||
August,
|
||||
September,
|
||||
October,
|
||||
November,
|
||||
December = 12,
|
||||
Last = December
|
||||
};
|
||||
|
||||
enum class DayOfWeek : uint8_t
|
||||
{
|
||||
// Sunday = 0 ... Saturday = 6
|
||||
First = 0,
|
||||
Last = 6,
|
||||
};
|
||||
}
|
||||
62
include/toml.hpp
Normal file
62
include/toml.hpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#ifndef TOML11_TOML_HPP
|
||||
#define TOML11_TOML_HPP
|
||||
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) 2017-now Toru Niina
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
// IWYU pragma: begin_exports
|
||||
#include "toml11/color.hpp"
|
||||
#include "toml11/comments.hpp"
|
||||
#include "toml11/compat.hpp"
|
||||
#include "toml11/context.hpp"
|
||||
#include "toml11/conversion.hpp"
|
||||
#include "toml11/datetime.hpp"
|
||||
#include "toml11/error_info.hpp"
|
||||
#include "toml11/exception.hpp"
|
||||
#include "toml11/find.hpp"
|
||||
#include "toml11/format.hpp"
|
||||
#include "toml11/from.hpp"
|
||||
#include "toml11/get.hpp"
|
||||
#include "toml11/into.hpp"
|
||||
#include "toml11/literal.hpp"
|
||||
#include "toml11/location.hpp"
|
||||
#include "toml11/ordered_map.hpp"
|
||||
#include "toml11/parser.hpp"
|
||||
#include "toml11/region.hpp"
|
||||
#include "toml11/result.hpp"
|
||||
#include "toml11/scanner.hpp"
|
||||
#include "toml11/serializer.hpp"
|
||||
#include "toml11/skip.hpp"
|
||||
#include "toml11/source_location.hpp"
|
||||
#include "toml11/spec.hpp"
|
||||
#include "toml11/storage.hpp"
|
||||
#include "toml11/syntax.hpp"
|
||||
#include "toml11/traits.hpp"
|
||||
#include "toml11/types.hpp"
|
||||
#include "toml11/utility.hpp"
|
||||
#include "toml11/value.hpp"
|
||||
#include "toml11/value_t.hpp"
|
||||
#include "toml11/version.hpp"
|
||||
#include "toml11/visit.hpp"
|
||||
// IWYU pragma: end_exports
|
||||
|
||||
#endif// TOML11_TOML_HPP
|
||||
10
include/toml11/color.hpp
Normal file
10
include/toml11/color.hpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#ifndef TOML11_COLOR_HPP
|
||||
#define TOML11_COLOR_HPP
|
||||
|
||||
#include "fwd/color_fwd.hpp" // IWYU pragma: export
|
||||
|
||||
#if ! defined(TOML11_COMPILE_SOURCES)
|
||||
#include "impl/color_impl.hpp" // IWYU pragma: export
|
||||
#endif
|
||||
|
||||
#endif // TOML11_COLOR_HPP
|
||||
10
include/toml11/comments.hpp
Normal file
10
include/toml11/comments.hpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#ifndef TOML11_COMMENTS_HPP
|
||||
#define TOML11_COMMENTS_HPP
|
||||
|
||||
#include "fwd/comments_fwd.hpp" // IWYU pragma: export
|
||||
|
||||
#if ! defined(TOML11_COMPILE_SOURCES)
|
||||
#include "impl/comments_impl.hpp" // IWYU pragma: export
|
||||
#endif
|
||||
|
||||
#endif // TOML11_COMMENTS_HPP
|
||||
858
include/toml11/compat.hpp
Normal file
858
include/toml11/compat.hpp
Normal file
@@ -0,0 +1,858 @@
|
||||
#ifndef TOML11_COMPAT_HPP
|
||||
#define TOML11_COMPAT_HPP
|
||||
|
||||
#include "version.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX20_VALUE
|
||||
# if __has_include(<bit>)
|
||||
# include <bit>
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#include <cstring>
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX14_VALUE
|
||||
# if __has_cpp_attribute(deprecated)
|
||||
# define TOML11_HAS_ATTR_DEPRECATED 1
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if defined(TOML11_HAS_ATTR_DEPRECATED)
|
||||
# define TOML11_DEPRECATED(msg) [[deprecated(msg)]]
|
||||
#elif defined(__GNUC__)
|
||||
# define TOML11_DEPRECATED(msg) __attribute__((deprecated(msg)))
|
||||
#elif defined(_MSC_VER)
|
||||
# define TOML11_DEPRECATED(msg) __declspec(deprecated(msg))
|
||||
#else
|
||||
# define TOML11_DEPRECATED(msg)
|
||||
#endif
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#if defined(__cpp_if_constexpr)
|
||||
# if __cpp_if_constexpr >= 201606L
|
||||
# define TOML11_HAS_CONSTEXPR_IF 1
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if defined(TOML11_HAS_CONSTEXPR_IF)
|
||||
# define TOML11_CONSTEXPR_IF if constexpr
|
||||
#else
|
||||
# define TOML11_CONSTEXPR_IF if
|
||||
#endif
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX14_VALUE
|
||||
# if defined(__cpp_lib_make_unique)
|
||||
# if __cpp_lib_make_unique >= 201304L
|
||||
# define TOML11_HAS_STD_MAKE_UNIQUE 1
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
namespace cxx
|
||||
{
|
||||
|
||||
#if defined(TOML11_HAS_STD_MAKE_UNIQUE)
|
||||
|
||||
using std::make_unique;
|
||||
|
||||
#else
|
||||
|
||||
template<typename T, typename ... Ts>
|
||||
std::unique_ptr<T> make_unique(Ts&& ... args)
|
||||
{
|
||||
return std::unique_ptr<T>(new T(std::forward<Ts>(args)...));
|
||||
}
|
||||
|
||||
#endif // TOML11_HAS_STD_MAKE_UNIQUE
|
||||
|
||||
} // cxx
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX14_VALUE
|
||||
# if defined(__cpp_lib_make_reverse_iterator)
|
||||
# if __cpp_lib_make_reverse_iterator >= 201402L
|
||||
# define TOML11_HAS_STD_MAKE_REVERSE_ITERATOR 1
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
namespace cxx
|
||||
{
|
||||
# if defined(TOML11_HAS_STD_MAKE_REVERSE_ITERATOR)
|
||||
|
||||
using std::make_reverse_iterator;
|
||||
|
||||
#else
|
||||
|
||||
template<typename Iterator>
|
||||
std::reverse_iterator<Iterator> make_reverse_iterator(Iterator iter)
|
||||
{
|
||||
return std::reverse_iterator<Iterator>(iter);
|
||||
}
|
||||
|
||||
#endif // TOML11_HAS_STD_MAKE_REVERSE_ITERATOR
|
||||
|
||||
} // cxx
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX20_VALUE
|
||||
# if defined(__cpp_lib_clamp)
|
||||
# if __cpp_lib_clamp >= 201603L
|
||||
# define TOML11_HAS_STD_CLAMP 1
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
namespace cxx
|
||||
{
|
||||
#if defined(TOML11_HAS_STD_CLAMP)
|
||||
|
||||
using std::clamp;
|
||||
|
||||
#else
|
||||
|
||||
template<typename T>
|
||||
T clamp(const T& x, const T& low, const T& high) noexcept
|
||||
{
|
||||
assert(low <= high);
|
||||
return (std::min)((std::max)(x, low), high);
|
||||
}
|
||||
|
||||
#endif // TOML11_HAS_STD_CLAMP
|
||||
|
||||
} // cxx
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX20_VALUE
|
||||
# if defined(__cpp_lib_bit_cast)
|
||||
# if __cpp_lib_bit_cast >= 201806L
|
||||
# define TOML11_HAS_STD_BIT_CAST 1
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
namespace cxx
|
||||
{
|
||||
#if defined(TOML11_HAS_STD_BIT_CAST)
|
||||
|
||||
using std::bit_cast;
|
||||
|
||||
#else
|
||||
|
||||
template<typename U, typename T>
|
||||
U bit_cast(const T& x) noexcept
|
||||
{
|
||||
static_assert(sizeof(T) == sizeof(U), "");
|
||||
static_assert(std::is_default_constructible<T>::value, "");
|
||||
|
||||
U z;
|
||||
std::memcpy(reinterpret_cast<char*>(std::addressof(z)),
|
||||
reinterpret_cast<const char*>(std::addressof(x)),
|
||||
sizeof(T));
|
||||
|
||||
return z;
|
||||
}
|
||||
|
||||
#endif // TOML11_HAS_STD_BIT_CAST
|
||||
|
||||
} // cxx
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// C++20 remove_cvref_t
|
||||
|
||||
#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX20_VALUE
|
||||
# if defined(__cpp_lib_remove_cvref)
|
||||
# if __cpp_lib_remove_cvref >= 201711L
|
||||
# define TOML11_HAS_STD_REMOVE_CVREF 1
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
namespace cxx
|
||||
{
|
||||
#if defined(TOML11_HAS_STD_REMOVE_CVREF)
|
||||
|
||||
using std::remove_cvref;
|
||||
using std::remove_cvref_t;
|
||||
|
||||
#else
|
||||
|
||||
template<typename T>
|
||||
struct remove_cvref
|
||||
{
|
||||
using type = typename std::remove_cv<
|
||||
typename std::remove_reference<T>::type>::type;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
using remove_cvref_t = typename remove_cvref<T>::type;
|
||||
|
||||
#endif // TOML11_HAS_STD_REMOVE_CVREF
|
||||
|
||||
} // cxx
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// C++17 and/or/not
|
||||
|
||||
#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX17_VALUE
|
||||
# if defined(__cpp_lib_logical_traits)
|
||||
# if __cpp_lib_logical_traits >= 201510L
|
||||
# define TOML11_HAS_STD_CONJUNCTION 1
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
namespace cxx
|
||||
{
|
||||
#if defined(TOML11_HAS_STD_CONJUNCTION)
|
||||
|
||||
using std::conjunction;
|
||||
using std::disjunction;
|
||||
using std::negation;
|
||||
|
||||
#else
|
||||
|
||||
template<typename ...> struct conjunction : std::true_type{};
|
||||
template<typename T> struct conjunction<T> : T{};
|
||||
template<typename T, typename ... Ts>
|
||||
struct conjunction<T, Ts...> :
|
||||
std::conditional<static_cast<bool>(T::value), conjunction<Ts...>, T>::type
|
||||
{};
|
||||
|
||||
template<typename ...> struct disjunction : std::false_type{};
|
||||
template<typename T> struct disjunction<T> : T {};
|
||||
template<typename T, typename ... Ts>
|
||||
struct disjunction<T, Ts...> :
|
||||
std::conditional<static_cast<bool>(T::value), T, disjunction<Ts...>>::type
|
||||
{};
|
||||
|
||||
template<typename T>
|
||||
struct negation : std::integral_constant<bool, !static_cast<bool>(T::value)>{};
|
||||
|
||||
#endif // TOML11_HAS_STD_CONJUNCTION
|
||||
|
||||
} // cxx
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// C++14 index_sequence
|
||||
|
||||
#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX14_VALUE
|
||||
# if defined(__cpp_lib_integer_sequence)
|
||||
# if __cpp_lib_integer_sequence >= 201304L
|
||||
# define TOML11_HAS_STD_INTEGER_SEQUENCE 1
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
namespace cxx
|
||||
{
|
||||
#if defined(TOML11_HAS_STD_INTEGER_SEQUENCE)
|
||||
|
||||
using std::index_sequence;
|
||||
using std::make_index_sequence;
|
||||
|
||||
#else
|
||||
|
||||
template<std::size_t ... Ns> struct index_sequence{};
|
||||
|
||||
template<bool B, std::size_t N, typename T>
|
||||
struct double_index_sequence;
|
||||
|
||||
template<std::size_t N, std::size_t ... Is>
|
||||
struct double_index_sequence<true, N, index_sequence<Is...>>
|
||||
{
|
||||
using type = index_sequence<Is..., (Is+N)..., N*2>;
|
||||
};
|
||||
template<std::size_t N, std::size_t ... Is>
|
||||
struct double_index_sequence<false, N, index_sequence<Is...>>
|
||||
{
|
||||
using type = index_sequence<Is..., (Is+N)...>;
|
||||
};
|
||||
|
||||
template<std::size_t N>
|
||||
struct index_sequence_maker
|
||||
{
|
||||
using type = typename double_index_sequence<
|
||||
N % 2 == 1, N/2, typename index_sequence_maker<N/2>::type
|
||||
>::type;
|
||||
};
|
||||
template<>
|
||||
struct index_sequence_maker<0>
|
||||
{
|
||||
using type = index_sequence<>;
|
||||
};
|
||||
|
||||
template<std::size_t N>
|
||||
using make_index_sequence = typename index_sequence_maker<N>::type;
|
||||
|
||||
#endif // TOML11_HAS_STD_INTEGER_SEQUENCE
|
||||
|
||||
} // cxx
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// C++14 enable_if_t
|
||||
|
||||
#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX14_VALUE
|
||||
# if defined(__cpp_lib_transformation_trait_aliases)
|
||||
# if __cpp_lib_transformation_trait_aliases >= 201304L
|
||||
# define TOML11_HAS_STD_ENABLE_IF_T 1
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
namespace cxx
|
||||
{
|
||||
#if defined(TOML11_HAS_STD_ENABLE_IF_T)
|
||||
|
||||
using std::enable_if_t;
|
||||
|
||||
#else
|
||||
|
||||
template<bool B, typename T>
|
||||
using enable_if_t = typename std::enable_if<B, T>::type;
|
||||
|
||||
#endif // TOML11_HAS_STD_ENABLE_IF_T
|
||||
|
||||
} // cxx
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// return_type_of_t
|
||||
|
||||
#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX17_VALUE
|
||||
# if defined(__cpp_lib_is_invocable)
|
||||
# if __cpp_lib_is_invocable >= 201703
|
||||
# define TOML11_HAS_STD_INVOKE_RESULT 1
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
namespace cxx
|
||||
{
|
||||
#if defined(TOML11_HAS_STD_INVOKE_RESULT)
|
||||
|
||||
template<typename F, typename ... Args>
|
||||
using return_type_of_t = std::invoke_result_t<F, Args...>;
|
||||
|
||||
#else
|
||||
|
||||
// result_of is deprecated after C++17
|
||||
template<typename F, typename ... Args>
|
||||
using return_type_of_t = typename std::result_of<F(Args...)>::type;
|
||||
|
||||
#endif // TOML11_HAS_STD_INVOKE_RESULT
|
||||
|
||||
} // cxx
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// C++17 void_t
|
||||
|
||||
#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX17_VALUE
|
||||
# if defined(__cpp_lib_void_t)
|
||||
# if __cpp_lib_void_t >= 201411L
|
||||
# define TOML11_HAS_STD_VOID_T 1
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
namespace cxx
|
||||
{
|
||||
#if defined(TOML11_HAS_STD_VOID_T)
|
||||
|
||||
using std::void_t;
|
||||
|
||||
#else
|
||||
|
||||
template<typename ...>
|
||||
using void_t = void;
|
||||
|
||||
#endif // TOML11_HAS_STD_VOID_T
|
||||
|
||||
} // cxx
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// (subset of) source_location
|
||||
|
||||
#if ! defined(TOML11_DISABLE_SOURCE_LOCATION) && TOML11_CPLUSPLUS_STANDARD_VERSION >= 202002L
|
||||
# if __has_include(<source_location>)
|
||||
# define TOML11_HAS_STD_SOURCE_LOCATION
|
||||
# endif // has_include
|
||||
#endif // c++20
|
||||
|
||||
#if ! defined(TOML11_DISABLE_SOURCE_LOCATION) && ! defined(TOML11_HAS_STD_SOURCE_LOCATION)
|
||||
# if defined(__GNUC__) && ! defined(__clang__)
|
||||
# if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX14_VALUE
|
||||
# if __has_include(<experimental/source_location>)
|
||||
# define TOML11_HAS_EXPERIMENTAL_SOURCE_LOCATION
|
||||
# endif
|
||||
# endif
|
||||
# endif // GNU g++
|
||||
#endif // not TOML11_HAS_STD_SOURCE_LOCATION
|
||||
|
||||
#if ! defined(TOML11_DISABLE_SOURCE_LOCATION) && ! defined(TOML11_HAS_STD_SOURCE_LOCATION) && ! defined(TOML11_HAS_EXPERIMENTAL_SOURCE_LOCATION)
|
||||
# if defined(__GNUC__) && ! defined(__clang__)
|
||||
# if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9))
|
||||
# define TOML11_HAS_BUILTIN_FILE_LINE 1
|
||||
# define TOML11_BUILTIN_LINE_TYPE int
|
||||
# endif
|
||||
# elif defined(__clang__) // clang 9.0.0 implements builtin_FILE/LINE
|
||||
# if __has_builtin(__builtin_FILE) && __has_builtin(__builtin_LINE)
|
||||
# define TOML11_HAS_BUILTIN_FILE_LINE 1
|
||||
# define TOML11_BUILTIN_LINE_TYPE unsigned int
|
||||
# endif
|
||||
# elif defined(_MSVC_LANG) && defined(_MSC_VER)
|
||||
# if _MSC_VER > 1926
|
||||
# define TOML11_HAS_BUILTIN_FILE_LINE 1
|
||||
# define TOML11_BUILTIN_LINE_TYPE int
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if defined(TOML11_HAS_STD_SOURCE_LOCATION)
|
||||
#include <source_location>
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
namespace cxx
|
||||
{
|
||||
using source_location = std::source_location;
|
||||
|
||||
inline std::string to_string(const source_location& loc)
|
||||
{
|
||||
const char* fname = loc.file_name();
|
||||
if(fname)
|
||||
{
|
||||
return std::string(" at line ") + std::to_string(loc.line()) +
|
||||
std::string(" in file ") + std::string(fname);
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::string(" at line ") + std::to_string(loc.line()) +
|
||||
std::string(" in unknown file");
|
||||
}
|
||||
}
|
||||
|
||||
} // cxx
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
#elif defined(TOML11_HAS_EXPERIMENTAL_SOURCE_LOCATION)
|
||||
#include <experimental/source_location>
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
namespace cxx
|
||||
{
|
||||
using source_location = std::experimental::source_location;
|
||||
|
||||
inline std::string to_string(const source_location& loc)
|
||||
{
|
||||
const char* fname = loc.file_name();
|
||||
if(fname)
|
||||
{
|
||||
return std::string(" at line ") + std::to_string(loc.line()) +
|
||||
std::string(" in file ") + std::string(fname);
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::string(" at line ") + std::to_string(loc.line()) +
|
||||
std::string(" in unknown file");
|
||||
}
|
||||
}
|
||||
|
||||
} // cxx
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
#elif defined(TOML11_HAS_BUILTIN_FILE_LINE)
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
namespace cxx
|
||||
{
|
||||
struct source_location
|
||||
{
|
||||
using line_type = TOML11_BUILTIN_LINE_TYPE;
|
||||
static source_location current(const line_type line = __builtin_LINE(),
|
||||
const char* file = __builtin_FILE())
|
||||
{
|
||||
return source_location(line, file);
|
||||
}
|
||||
|
||||
source_location(const line_type line, const char* file)
|
||||
: line_(line), file_name_(file)
|
||||
{}
|
||||
|
||||
line_type line() const noexcept {return line_;}
|
||||
const char* file_name() const noexcept {return file_name_;}
|
||||
|
||||
private:
|
||||
|
||||
line_type line_;
|
||||
const char* file_name_;
|
||||
};
|
||||
|
||||
inline std::string to_string(const source_location& loc)
|
||||
{
|
||||
const char* fname = loc.file_name();
|
||||
if(fname)
|
||||
{
|
||||
return std::string(" at line ") + std::to_string(loc.line()) +
|
||||
std::string(" in file ") + std::string(fname);
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::string(" at line ") + std::to_string(loc.line()) +
|
||||
std::string(" in unknown file");
|
||||
}
|
||||
}
|
||||
|
||||
} // cxx
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
#else // no builtin
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
namespace cxx
|
||||
{
|
||||
struct source_location
|
||||
{
|
||||
static source_location current() { return source_location{}; }
|
||||
};
|
||||
|
||||
inline std::string to_string(const source_location&)
|
||||
{
|
||||
return std::string("");
|
||||
}
|
||||
} // cxx
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
#endif // TOML11_HAS_STD_SOURCE_LOCATION
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// (subset of) optional
|
||||
|
||||
#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX17_VALUE
|
||||
# if __has_include(<optional>)
|
||||
# include <optional>
|
||||
# endif // has_include(optional)
|
||||
#endif // C++17
|
||||
|
||||
#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX17_VALUE
|
||||
# if defined(__cpp_lib_optional)
|
||||
# if __cpp_lib_optional >= 201606L
|
||||
# define TOML11_HAS_STD_OPTIONAL 1
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if defined(TOML11_HAS_STD_OPTIONAL)
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
namespace cxx
|
||||
{
|
||||
using std::optional;
|
||||
|
||||
inline std::nullopt_t make_nullopt() {return std::nullopt;}
|
||||
|
||||
template<typename charT, typename traitsT>
|
||||
std::basic_ostream<charT, traitsT>&
|
||||
operator<<(std::basic_ostream<charT, traitsT>& os, const std::nullopt_t&)
|
||||
{
|
||||
os << "nullopt";
|
||||
return os;
|
||||
}
|
||||
|
||||
} // cxx
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
|
||||
#else // TOML11_HAS_STD_OPTIONAL
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
namespace cxx
|
||||
{
|
||||
|
||||
struct nullopt_t{};
|
||||
inline nullopt_t make_nullopt() {return nullopt_t{};}
|
||||
|
||||
inline bool operator==(const nullopt_t&, const nullopt_t&) noexcept {return true;}
|
||||
inline bool operator!=(const nullopt_t&, const nullopt_t&) noexcept {return false;}
|
||||
inline bool operator< (const nullopt_t&, const nullopt_t&) noexcept {return false;}
|
||||
inline bool operator<=(const nullopt_t&, const nullopt_t&) noexcept {return true;}
|
||||
inline bool operator> (const nullopt_t&, const nullopt_t&) noexcept {return false;}
|
||||
inline bool operator>=(const nullopt_t&, const nullopt_t&) noexcept {return true;}
|
||||
|
||||
template<typename charT, typename traitsT>
|
||||
std::basic_ostream<charT, traitsT>&
|
||||
operator<<(std::basic_ostream<charT, traitsT>& os, const nullopt_t&)
|
||||
{
|
||||
os << "nullopt";
|
||||
return os;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
class optional
|
||||
{
|
||||
public:
|
||||
|
||||
using value_type = T;
|
||||
|
||||
public:
|
||||
|
||||
optional() noexcept : has_value_(false), null_('\0') {}
|
||||
optional(nullopt_t) noexcept : has_value_(false), null_('\0') {}
|
||||
|
||||
optional(const T& x): has_value_(true), value_(x) {}
|
||||
optional(T&& x): has_value_(true), value_(std::move(x)) {}
|
||||
|
||||
template<typename U, enable_if_t<std::is_constructible<T, U>::value, std::nullptr_t> = nullptr>
|
||||
explicit optional(U&& x): has_value_(true), value_(std::forward<U>(x)) {}
|
||||
|
||||
optional(const optional& rhs): has_value_(rhs.has_value_)
|
||||
{
|
||||
if(rhs.has_value_)
|
||||
{
|
||||
this->assigner(rhs.value_);
|
||||
}
|
||||
}
|
||||
optional(optional&& rhs): has_value_(rhs.has_value_)
|
||||
{
|
||||
if(this->has_value_)
|
||||
{
|
||||
this->assigner(std::move(rhs.value_));
|
||||
}
|
||||
}
|
||||
|
||||
optional& operator=(const optional& rhs)
|
||||
{
|
||||
if(this == std::addressof(rhs)) {return *this;}
|
||||
|
||||
this->cleanup();
|
||||
this->has_value_ = rhs.has_value_;
|
||||
if(this->has_value_)
|
||||
{
|
||||
this->assigner(rhs.value_);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
optional& operator=(optional&& rhs)
|
||||
{
|
||||
if(this == std::addressof(rhs)) {return *this;}
|
||||
|
||||
this->cleanup();
|
||||
this->has_value_ = rhs.has_value_;
|
||||
if(this->has_value_)
|
||||
{
|
||||
this->assigner(std::move(rhs.value_));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename U, enable_if_t<conjunction<
|
||||
negation<std::is_same<T, U>>, std::is_constructible<T, U>
|
||||
>::value, std::nullptr_t> = nullptr>
|
||||
explicit optional(const optional<U>& rhs): has_value_(rhs.has_value_), null_('\0')
|
||||
{
|
||||
if(rhs.has_value_)
|
||||
{
|
||||
this->assigner(rhs.value_);
|
||||
}
|
||||
}
|
||||
template<typename U, enable_if_t<conjunction<
|
||||
negation<std::is_same<T, U>>, std::is_constructible<T, U>
|
||||
>::value, std::nullptr_t> = nullptr>
|
||||
explicit optional(optional<U>&& rhs): has_value_(rhs.has_value_), null_('\0')
|
||||
{
|
||||
if(this->has_value_)
|
||||
{
|
||||
this->assigner(std::move(rhs.value_));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename U, enable_if_t<conjunction<
|
||||
negation<std::is_same<T, U>>, std::is_constructible<T, U>
|
||||
>::value, std::nullptr_t> = nullptr>
|
||||
optional& operator=(const optional<U>& rhs)
|
||||
{
|
||||
if(this == std::addressof(rhs)) {return *this;}
|
||||
|
||||
this->cleanup();
|
||||
this->has_value_ = rhs.has_value_;
|
||||
if(this->has_value_)
|
||||
{
|
||||
this->assigner(rhs.value_);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename U, enable_if_t<conjunction<
|
||||
negation<std::is_same<T, U>>, std::is_constructible<T, U>
|
||||
>::value, std::nullptr_t> = nullptr>
|
||||
optional& operator=(optional<U>&& rhs)
|
||||
{
|
||||
if(this == std::addressof(rhs)) {return *this;}
|
||||
|
||||
this->cleanup();
|
||||
this->has_value_ = rhs.has_value_;
|
||||
if(this->has_value_)
|
||||
{
|
||||
this->assigner(std::move(rhs.value_));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
~optional() noexcept
|
||||
{
|
||||
this->cleanup();
|
||||
}
|
||||
|
||||
explicit operator bool() const noexcept
|
||||
{
|
||||
return has_value_;
|
||||
}
|
||||
|
||||
bool has_value() const noexcept {return has_value_;}
|
||||
|
||||
value_type const& value(source_location loc = source_location::current()) const
|
||||
{
|
||||
if( ! this->has_value_)
|
||||
{
|
||||
throw std::runtime_error("optional::value(): bad_unwrap" + to_string(loc));
|
||||
}
|
||||
return this->value_;
|
||||
}
|
||||
value_type& value(source_location loc = source_location::current())
|
||||
{
|
||||
if( ! this->has_value_)
|
||||
{
|
||||
throw std::runtime_error("optional::value(): bad_unwrap" + to_string(loc));
|
||||
}
|
||||
return this->value_;
|
||||
}
|
||||
|
||||
value_type const& value_or(const value_type& opt) const
|
||||
{
|
||||
if(this->has_value_) {return this->value_;} else {return opt;}
|
||||
}
|
||||
value_type& value_or(value_type& opt)
|
||||
{
|
||||
if(this->has_value_) {return this->value_;} else {return opt;}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void cleanup() noexcept
|
||||
{
|
||||
if(this->has_value_)
|
||||
{
|
||||
value_.~T();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
void assigner(U&& x)
|
||||
{
|
||||
const auto tmp = ::new(std::addressof(this->value_)) value_type(std::forward<U>(x));
|
||||
assert(tmp == std::addressof(this->value_));
|
||||
(void)tmp;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
bool has_value_;
|
||||
union
|
||||
{
|
||||
char null_;
|
||||
T value_;
|
||||
};
|
||||
};
|
||||
} // cxx
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
#endif // TOML11_HAS_STD_OPTIONAL
|
||||
|
||||
#endif // TOML11_COMPAT_HPP
|
||||
74
include/toml11/context.hpp
Normal file
74
include/toml11/context.hpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#ifndef TOML11_CONTEXT_HPP
|
||||
#define TOML11_CONTEXT_HPP
|
||||
|
||||
#include "error_info.hpp"
|
||||
#include "spec.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
|
||||
template<typename TypeConfig>
|
||||
class context
|
||||
{
|
||||
public:
|
||||
|
||||
explicit context(const spec& toml_spec)
|
||||
: toml_spec_(toml_spec), errors_{}
|
||||
{}
|
||||
|
||||
bool has_error() const noexcept {return !errors_.empty();}
|
||||
|
||||
std::vector<error_info> const& errors() const noexcept {return errors_;}
|
||||
|
||||
semantic_version& toml_version() noexcept {return toml_spec_.version;}
|
||||
semantic_version const& toml_version() const noexcept {return toml_spec_.version;}
|
||||
|
||||
spec& toml_spec() noexcept {return toml_spec_;}
|
||||
spec const& toml_spec() const noexcept {return toml_spec_;}
|
||||
|
||||
void report_error(error_info err)
|
||||
{
|
||||
this->errors_.push_back(std::move(err));
|
||||
}
|
||||
|
||||
error_info pop_last_error()
|
||||
{
|
||||
assert( ! errors_.empty());
|
||||
auto e = std::move(errors_.back());
|
||||
errors_.pop_back();
|
||||
return e;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
spec toml_spec_;
|
||||
std::vector<error_info> errors_;
|
||||
};
|
||||
|
||||
} // detail
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
|
||||
#if defined(TOML11_COMPILE_SOURCES)
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
struct type_config;
|
||||
struct ordered_type_config;
|
||||
namespace detail
|
||||
{
|
||||
extern template class context<::toml::type_config>;
|
||||
extern template class context<::toml::ordered_type_config>;
|
||||
} // detail
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
#endif // TOML11_COMPILE_SOURCES
|
||||
|
||||
#endif // TOML11_CONTEXT_HPP
|
||||
212
include/toml11/conversion.hpp
Normal file
212
include/toml11/conversion.hpp
Normal file
@@ -0,0 +1,212 @@
|
||||
#ifndef TOML11_CONVERSION_HPP
|
||||
#define TOML11_CONVERSION_HPP
|
||||
|
||||
#include "find.hpp"
|
||||
#include "from.hpp" // IWYU pragma: keep
|
||||
#include "into.hpp" // IWYU pragma: keep
|
||||
#include "version.hpp"
|
||||
|
||||
#if defined(TOML11_HAS_OPTIONAL)
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
|
||||
template<typename T>
|
||||
inline constexpr bool is_optional_v = false;
|
||||
|
||||
template<typename T>
|
||||
inline constexpr bool is_optional_v<std::optional<T>> = true;
|
||||
|
||||
template<typename T, typename TC>
|
||||
void find_member_variable_from_value(T& obj, const basic_value<TC>& v, const char* var_name)
|
||||
{
|
||||
if constexpr(is_optional_v<T>)
|
||||
{
|
||||
if(v.contains(var_name))
|
||||
{
|
||||
obj = toml::find<typename T::value_type>(v, var_name);
|
||||
}
|
||||
else
|
||||
{
|
||||
obj = std::nullopt;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
obj = toml::find<T>(v, var_name);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, typename TC>
|
||||
void assign_member_variable_to_value(const T& obj, basic_value<TC>& v, const char* var_name)
|
||||
{
|
||||
if constexpr(is_optional_v<T>)
|
||||
{
|
||||
if(obj.has_value())
|
||||
{
|
||||
v[var_name] = obj.value();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
v[var_name] = obj;
|
||||
}
|
||||
}
|
||||
|
||||
} // detail
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
|
||||
#else
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
|
||||
template<typename T, typename TC>
|
||||
void find_member_variable_from_value(T& obj, const basic_value<TC>& v, const char* var_name)
|
||||
{
|
||||
obj = toml::find<T>(v, var_name);
|
||||
}
|
||||
|
||||
template<typename T, typename TC>
|
||||
void assign_member_variable_to_value(const T& obj, basic_value<TC>& v, const char* var_name)
|
||||
{
|
||||
v[var_name] = obj;
|
||||
}
|
||||
|
||||
} // detail
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
|
||||
#endif // optional
|
||||
|
||||
// use it in the following way.
|
||||
// ```cpp
|
||||
// namespace foo
|
||||
// {
|
||||
// struct Foo
|
||||
// {
|
||||
// std::string s;
|
||||
// double d;
|
||||
// int i;
|
||||
// };
|
||||
// } // foo
|
||||
//
|
||||
// TOML11_DEFINE_CONVERSION_NON_INTRUSIVE(foo::Foo, s, d, i)
|
||||
// ```
|
||||
//
|
||||
// And then you can use `toml::get<foo::Foo>(v)` and `toml::find<foo::Foo>(file, "foo");`
|
||||
//
|
||||
|
||||
#define TOML11_STRINGIZE_AUX(x) #x
|
||||
#define TOML11_STRINGIZE(x) TOML11_STRINGIZE_AUX(x)
|
||||
|
||||
#define TOML11_CONCATENATE_AUX(x, y) x##y
|
||||
#define TOML11_CONCATENATE(x, y) TOML11_CONCATENATE_AUX(x, y)
|
||||
|
||||
// ============================================================================
|
||||
// TOML11_DEFINE_CONVERSION_NON_INTRUSIVE
|
||||
|
||||
#ifndef TOML11_WITHOUT_DEFINE_NON_INTRUSIVE
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// TOML11_ARGS_SIZE
|
||||
|
||||
#define TOML11_INDEX_RSEQ() \
|
||||
32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, \
|
||||
16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
|
||||
#define TOML11_ARGS_SIZE_IMPL(\
|
||||
ARG1, ARG2, ARG3, ARG4, ARG5, ARG6, ARG7, ARG8, ARG9, ARG10, \
|
||||
ARG11, ARG12, ARG13, ARG14, ARG15, ARG16, ARG17, ARG18, ARG19, ARG20, \
|
||||
ARG21, ARG22, ARG23, ARG24, ARG25, ARG26, ARG27, ARG28, ARG29, ARG30, \
|
||||
ARG31, ARG32, N, ...) N
|
||||
#define TOML11_ARGS_SIZE_AUX(...) TOML11_ARGS_SIZE_IMPL(__VA_ARGS__)
|
||||
#define TOML11_ARGS_SIZE(...) TOML11_ARGS_SIZE_AUX(__VA_ARGS__, TOML11_INDEX_RSEQ())
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// TOML11_FOR_EACH_VA_ARGS
|
||||
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_1( FUNCTOR, ARG1 ) FUNCTOR(ARG1)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_2( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_1( FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_3( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_2( FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_4( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_3( FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_5( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_4( FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_6( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_5( FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_7( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_6( FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_8( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_7( FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_9( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_8( FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_10(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_9( FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_11(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_10(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_12(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_11(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_13(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_12(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_14(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_13(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_15(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_14(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_16(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_15(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_17(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_16(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_18(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_17(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_19(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_18(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_20(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_19(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_21(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_20(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_22(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_21(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_23(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_22(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_24(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_23(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_25(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_24(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_26(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_25(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_27(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_26(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_28(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_27(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_29(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_28(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_30(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_29(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_31(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_30(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_32(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_31(FUNCTOR, __VA_ARGS__)
|
||||
|
||||
#define TOML11_FOR_EACH_VA_ARGS(FUNCTOR, ...)\
|
||||
TOML11_CONCATENATE(TOML11_FOR_EACH_VA_ARGS_AUX_, TOML11_ARGS_SIZE(__VA_ARGS__))(FUNCTOR, __VA_ARGS__)
|
||||
|
||||
|
||||
#define TOML11_FIND_MEMBER_VARIABLE_FROM_VALUE(VAR_NAME)\
|
||||
toml::detail::find_member_variable_from_value(obj.VAR_NAME, v, TOML11_STRINGIZE(VAR_NAME));
|
||||
|
||||
#define TOML11_ASSIGN_MEMBER_VARIABLE_TO_VALUE(VAR_NAME)\
|
||||
toml::detail::assign_member_variable_to_value(obj.VAR_NAME, v, TOML11_STRINGIZE(VAR_NAME));
|
||||
|
||||
#define TOML11_DEFINE_CONVERSION_NON_INTRUSIVE(NAME, ...)\
|
||||
namespace toml { \
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE { \
|
||||
template<> \
|
||||
struct from<NAME> \
|
||||
{ \
|
||||
template<typename TC> \
|
||||
static NAME from_toml(const basic_value<TC>& v) \
|
||||
{ \
|
||||
NAME obj; \
|
||||
TOML11_FOR_EACH_VA_ARGS(TOML11_FIND_MEMBER_VARIABLE_FROM_VALUE, __VA_ARGS__) \
|
||||
return obj; \
|
||||
} \
|
||||
}; \
|
||||
template<> \
|
||||
struct into<NAME> \
|
||||
{ \
|
||||
template<typename TC> \
|
||||
static basic_value<TC> into_toml(const NAME& obj) \
|
||||
{ \
|
||||
::toml::basic_value<TC> v = typename ::toml::basic_value<TC>::table_type{}; \
|
||||
TOML11_FOR_EACH_VA_ARGS(TOML11_ASSIGN_MEMBER_VARIABLE_TO_VALUE, __VA_ARGS__) \
|
||||
return v; \
|
||||
} \
|
||||
}; \
|
||||
} /* TOML11_INLINE_VERSION_NAMESPACE */ \
|
||||
} /* toml */
|
||||
|
||||
#endif// TOML11_WITHOUT_DEFINE_NON_INTRUSIVE
|
||||
|
||||
#endif // TOML11_CONVERSION_HPP
|
||||
10
include/toml11/datetime.hpp
Normal file
10
include/toml11/datetime.hpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#ifndef TOML11_DATETIME_HPP
|
||||
#define TOML11_DATETIME_HPP
|
||||
|
||||
#include "fwd/datetime_fwd.hpp" // IWYU pragma: export
|
||||
|
||||
#if ! defined(TOML11_COMPILE_SOURCES)
|
||||
#include "impl/datetime_impl.hpp" // IWYU pragma: export
|
||||
#endif
|
||||
|
||||
#endif // TOML11_DATETIME_HPP
|
||||
10
include/toml11/error_info.hpp
Normal file
10
include/toml11/error_info.hpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#ifndef TOML11_ERROR_INFO_HPP
|
||||
#define TOML11_ERROR_INFO_HPP
|
||||
|
||||
#include "fwd/error_info_fwd.hpp" // IWYU pragma: export
|
||||
|
||||
#if ! defined(TOML11_COMPILE_SOURCES)
|
||||
#include "impl/error_info_impl.hpp" // IWYU pragma: export
|
||||
#endif
|
||||
|
||||
#endif // TOML11_ERROR_INFO_HPP
|
||||
22
include/toml11/exception.hpp
Normal file
22
include/toml11/exception.hpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef TOML11_EXCEPTION_HPP
|
||||
#define TOML11_EXCEPTION_HPP
|
||||
|
||||
#include <exception>
|
||||
|
||||
#include "version.hpp"
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
|
||||
struct exception : public std::exception
|
||||
{
|
||||
public:
|
||||
virtual ~exception() noexcept override = default;
|
||||
virtual const char* what() const noexcept override {return "";}
|
||||
};
|
||||
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
#endif // TOMl11_EXCEPTION_HPP
|
||||
592
include/toml11/find.hpp
Normal file
592
include/toml11/find.hpp
Normal file
@@ -0,0 +1,592 @@
|
||||
#ifndef TOML11_FIND_HPP
|
||||
#define TOML11_FIND_HPP
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "get.hpp"
|
||||
#include "value.hpp"
|
||||
#include "version.hpp"
|
||||
|
||||
#if defined(TOML11_HAS_STRING_VIEW)
|
||||
#include <string_view>
|
||||
#endif
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// find<T>(value, key);
|
||||
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<cxx::negation<detail::is_std_optional<T>>::value,
|
||||
decltype(::toml::get<T>(std::declval<basic_value<TC> const&>()))>
|
||||
find(const basic_value<TC>& v, const typename basic_value<TC>::key_type& ky)
|
||||
{
|
||||
return ::toml::get<T>(v.at(ky));
|
||||
}
|
||||
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<cxx::negation<detail::is_std_optional<T>>::value,
|
||||
decltype(::toml::get<T>(std::declval<basic_value<TC>&>()))>
|
||||
find(basic_value<TC>& v, const typename basic_value<TC>::key_type& ky)
|
||||
{
|
||||
return ::toml::get<T>(v.at(ky));
|
||||
}
|
||||
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<cxx::negation<detail::is_std_optional<T>>::value,
|
||||
decltype(::toml::get<T>(std::declval<basic_value<TC>&&>()))>
|
||||
find(basic_value<TC>&& v, const typename basic_value<TC>::key_type& ky)
|
||||
{
|
||||
return ::toml::get<T>(std::move(v.at(ky)));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// find<T>(value, idx)
|
||||
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<cxx::negation<detail::is_std_optional<T>>::value,
|
||||
decltype(::toml::get<T>(std::declval<basic_value<TC> const&>()))>
|
||||
find(const basic_value<TC>& v, const std::size_t idx)
|
||||
{
|
||||
return ::toml::get<T>(v.at(idx));
|
||||
}
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<cxx::negation<detail::is_std_optional<T>>::value,
|
||||
decltype(::toml::get<T>(std::declval<basic_value<TC>&>()))>
|
||||
find(basic_value<TC>& v, const std::size_t idx)
|
||||
{
|
||||
return ::toml::get<T>(v.at(idx));
|
||||
}
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<cxx::negation<detail::is_std_optional<T>>::value,
|
||||
decltype(::toml::get<T>(std::declval<basic_value<TC>&&>()))>
|
||||
find(basic_value<TC>&& v, const std::size_t idx)
|
||||
{
|
||||
return ::toml::get<T>(std::move(v.at(idx)));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// find(value, key/idx), w/o conversion
|
||||
|
||||
template<typename TC>
|
||||
cxx::enable_if_t<detail::is_type_config<TC>::value, basic_value<TC>>&
|
||||
find(basic_value<TC>& v, const typename basic_value<TC>::key_type& ky)
|
||||
{
|
||||
return v.at(ky);
|
||||
}
|
||||
template<typename TC>
|
||||
cxx::enable_if_t<detail::is_type_config<TC>::value, basic_value<TC>> const&
|
||||
find(basic_value<TC> const& v, const typename basic_value<TC>::key_type& ky)
|
||||
{
|
||||
return v.at(ky);
|
||||
}
|
||||
template<typename TC>
|
||||
cxx::enable_if_t<detail::is_type_config<TC>::value, basic_value<TC>>
|
||||
find(basic_value<TC>&& v, const typename basic_value<TC>::key_type& ky)
|
||||
{
|
||||
return basic_value<TC>(std::move(v.at(ky)));
|
||||
}
|
||||
|
||||
template<typename TC>
|
||||
cxx::enable_if_t<detail::is_type_config<TC>::value, basic_value<TC>>&
|
||||
find(basic_value<TC>& v, const std::size_t idx)
|
||||
{
|
||||
return v.at(idx);
|
||||
}
|
||||
template<typename TC>
|
||||
cxx::enable_if_t<detail::is_type_config<TC>::value, basic_value<TC>> const&
|
||||
find(basic_value<TC> const& v, const std::size_t idx)
|
||||
{
|
||||
return v.at(idx);
|
||||
}
|
||||
template<typename TC>
|
||||
cxx::enable_if_t<detail::is_type_config<TC>::value, basic_value<TC>>
|
||||
find(basic_value<TC>&& v, const std::size_t idx)
|
||||
{
|
||||
return basic_value<TC>(std::move(v.at(idx)));
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// find<optional<T>>
|
||||
|
||||
#if defined(TOML11_HAS_OPTIONAL)
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<detail::is_std_optional<T>::value, T>
|
||||
find(const basic_value<TC>& v, const typename basic_value<TC>::key_type& ky)
|
||||
{
|
||||
if(v.contains(ky))
|
||||
{
|
||||
return ::toml::get<typename T::value_type>(v.at(ky));
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<detail::is_std_optional<T>::value, T>
|
||||
find(basic_value<TC>& v, const typename basic_value<TC>::key_type& ky)
|
||||
{
|
||||
if(v.contains(ky))
|
||||
{
|
||||
return ::toml::get<typename T::value_type>(v.at(ky));
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<detail::is_std_optional<T>::value, T>
|
||||
find(basic_value<TC>&& v, const typename basic_value<TC>::key_type& ky)
|
||||
{
|
||||
if(v.contains(ky))
|
||||
{
|
||||
return ::toml::get<typename T::value_type>(std::move(v.at(ky)));
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, typename K, typename TC>
|
||||
cxx::enable_if_t<detail::is_std_optional<T>::value && std::is_integral<K>::value, T>
|
||||
find(const basic_value<TC>& v, const K& k)
|
||||
{
|
||||
if(static_cast<std::size_t>(k) < v.size())
|
||||
{
|
||||
return ::toml::get<typename T::value_type>(v.at(static_cast<std::size_t>(k)));
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, typename K, typename TC>
|
||||
cxx::enable_if_t<detail::is_std_optional<T>::value && std::is_integral<K>::value, T>
|
||||
find(basic_value<TC>& v, const K& k)
|
||||
{
|
||||
if(static_cast<std::size_t>(k) < v.size())
|
||||
{
|
||||
return ::toml::get<typename T::value_type>(v.at(static_cast<std::size_t>(k)));
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, typename K, typename TC>
|
||||
cxx::enable_if_t<detail::is_std_optional<T>::value && std::is_integral<K>::value, T>
|
||||
find(basic_value<TC>&& v, const K& k)
|
||||
{
|
||||
if(static_cast<std::size_t>(k) < v.size())
|
||||
{
|
||||
return ::toml::get<typename T::value_type>(std::move(v.at(static_cast<std::size_t>(k))));
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
#endif // optional
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// toml::find(toml::value, toml::key, Ts&& ... keys)
|
||||
|
||||
namespace detail
|
||||
{
|
||||
|
||||
// It suppresses warnings by -Wsign-conversion when we pass integer literal
|
||||
// to toml::find. integer literal `0` is deduced as an int, and will be
|
||||
// converted to std::size_t. This causes sign-conversion.
|
||||
|
||||
template<typename TC>
|
||||
std::size_t key_cast(const std::size_t& v) noexcept
|
||||
{
|
||||
return v;
|
||||
}
|
||||
template<typename TC, typename T>
|
||||
cxx::enable_if_t<std::is_integral<cxx::remove_cvref_t<T>>::value, std::size_t>
|
||||
key_cast(const T& v) noexcept
|
||||
{
|
||||
return static_cast<std::size_t>(v);
|
||||
}
|
||||
|
||||
// for string-like (string, string literal, string_view)
|
||||
|
||||
template<typename TC>
|
||||
typename basic_value<TC>::key_type const&
|
||||
key_cast(const typename basic_value<TC>::key_type& v) noexcept
|
||||
{
|
||||
return v;
|
||||
}
|
||||
template<typename TC>
|
||||
typename basic_value<TC>::key_type
|
||||
key_cast(const typename basic_value<TC>::key_type::value_type* v)
|
||||
{
|
||||
return typename basic_value<TC>::key_type(v);
|
||||
}
|
||||
#if defined(TOML11_HAS_STRING_VIEW)
|
||||
template<typename TC>
|
||||
typename basic_value<TC>::key_type
|
||||
key_cast(const std::string_view v)
|
||||
{
|
||||
return typename basic_value<TC>::key_type(v);
|
||||
}
|
||||
#endif // string_view
|
||||
|
||||
} // detail
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// find(v, keys...)
|
||||
|
||||
template<typename TC, typename K1, typename K2, typename ... Ks>
|
||||
cxx::enable_if_t<detail::is_type_config<TC>::value, basic_value<TC>> const&
|
||||
find(const basic_value<TC>& v, const K1& k1, const K2& k2, const Ks& ... ks)
|
||||
{
|
||||
return find(v.at(detail::key_cast<TC>(k1)), detail::key_cast<TC>(k2), ks...);
|
||||
}
|
||||
template<typename TC, typename K1, typename K2, typename ... Ks>
|
||||
cxx::enable_if_t<detail::is_type_config<TC>::value, basic_value<TC>>&
|
||||
find(basic_value<TC>& v, const K1& k1, const K2& k2, const Ks& ... ks)
|
||||
{
|
||||
return find(v.at(detail::key_cast<TC>(k1)), detail::key_cast<TC>(k2), ks...);
|
||||
}
|
||||
template<typename TC, typename K1, typename K2, typename ... Ks>
|
||||
cxx::enable_if_t<detail::is_type_config<TC>::value, basic_value<TC>>
|
||||
find(basic_value<TC>&& v, const K1& k1, const K2& k2, const Ks& ... ks)
|
||||
{
|
||||
return find(std::move(v.at(detail::key_cast<TC>(k1))), detail::key_cast<TC>(k2), ks...);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// find<T>(v, keys...)
|
||||
|
||||
template<typename T, typename TC, typename K1, typename K2, typename ... Ks>
|
||||
cxx::enable_if_t<cxx::negation<detail::is_std_optional<T>>::value,
|
||||
decltype(::toml::get<T>(std::declval<const basic_value<TC>&>()))>
|
||||
find(const basic_value<TC>& v, const K1& k1, const K2& k2, const Ks& ... ks)
|
||||
{
|
||||
return find<T>(v.at(detail::key_cast<TC>(k1)), detail::key_cast<TC>(k2), ks...);
|
||||
}
|
||||
template<typename T, typename TC, typename K1, typename K2, typename ... Ks>
|
||||
cxx::enable_if_t<cxx::negation<detail::is_std_optional<T>>::value,
|
||||
decltype(::toml::get<T>(std::declval<basic_value<TC>&>()))>
|
||||
find(basic_value<TC>& v, const K1& k1, const K2& k2, const Ks& ... ks)
|
||||
{
|
||||
return find<T>(v.at(detail::key_cast<TC>(k1)), detail::key_cast<TC>(k2), ks...);
|
||||
}
|
||||
template<typename T, typename TC, typename K1, typename K2, typename ... Ks>
|
||||
cxx::enable_if_t<cxx::negation<detail::is_std_optional<T>>::value,
|
||||
decltype(::toml::get<T>(std::declval<basic_value<TC>&&>()))>
|
||||
find(basic_value<TC>&& v, const K1& k1, const K2& k2, const Ks& ... ks)
|
||||
{
|
||||
return find<T>(std::move(v.at(detail::key_cast<TC>(k1))), detail::key_cast<TC>(k2), ks...);
|
||||
}
|
||||
|
||||
#if defined(TOML11_HAS_OPTIONAL)
|
||||
template<typename T, typename TC, typename K2, typename ... Ks>
|
||||
cxx::enable_if_t<detail::is_std_optional<T>::value, T>
|
||||
find(const basic_value<TC>& v, const typename basic_value<TC>::key_type& k1, const K2& k2, const Ks& ... ks)
|
||||
{
|
||||
if(v.contains(k1))
|
||||
{
|
||||
return find<T>(v.at(k1), detail::key_cast<TC>(k2), ks...);
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
template<typename T, typename TC, typename K2, typename ... Ks>
|
||||
cxx::enable_if_t<detail::is_std_optional<T>::value, T>
|
||||
find(basic_value<TC>& v, const typename basic_value<TC>::key_type& k1, const K2& k2, const Ks& ... ks)
|
||||
{
|
||||
if(v.contains(k1))
|
||||
{
|
||||
return find<T>(v.at(k1), detail::key_cast<TC>(k2), ks...);
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
template<typename T, typename TC, typename K2, typename ... Ks>
|
||||
cxx::enable_if_t<detail::is_std_optional<T>::value, T>
|
||||
find(basic_value<TC>&& v, const typename basic_value<TC>::key_type& k1, const K2& k2, const Ks& ... ks)
|
||||
{
|
||||
if(v.contains(k1))
|
||||
{
|
||||
return find<T>(v.at(k1), detail::key_cast<TC>(k2), ks...);
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, typename TC, typename K1, typename K2, typename ... Ks>
|
||||
cxx::enable_if_t<detail::is_std_optional<T>::value && std::is_integral<K1>::value, T>
|
||||
find(const basic_value<TC>& v, const K1& k1, const K2& k2, const Ks& ... ks)
|
||||
{
|
||||
if(static_cast<std::size_t>(k1) < v.size())
|
||||
{
|
||||
return find<T>(v.at(static_cast<std::size_t>(k1)), detail::key_cast<TC>(k2), ks...);
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
template<typename T, typename TC, typename K1, typename K2, typename ... Ks>
|
||||
cxx::enable_if_t<detail::is_std_optional<T>::value && std::is_integral<K1>::value, T>
|
||||
find(basic_value<TC>& v, const K1& k1, const K2& k2, const Ks& ... ks)
|
||||
{
|
||||
if(static_cast<std::size_t>(k1) < v.size())
|
||||
{
|
||||
return find<T>(v.at(static_cast<std::size_t>(k1)), detail::key_cast<TC>(k2), ks...);
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
template<typename T, typename TC, typename K1, typename K2, typename ... Ks>
|
||||
cxx::enable_if_t<detail::is_std_optional<T>::value && std::is_integral<K1>::value, T>
|
||||
find(basic_value<TC>&& v, const K1& k1, const K2& k2, const Ks& ... ks)
|
||||
{
|
||||
if(static_cast<std::size_t>(k1) < v.size())
|
||||
{
|
||||
return find<T>(v.at(static_cast<std::size_t>(k1)), detail::key_cast<TC>(k2), ks...);
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
#endif // optional
|
||||
|
||||
// ===========================================================================
|
||||
// find_or<T>(value, key, fallback)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// find_or(v, key, other_v)
|
||||
|
||||
template<typename TC, typename K>
|
||||
cxx::enable_if_t<detail::is_type_config<TC>::value, basic_value<TC>>&
|
||||
find_or(basic_value<TC>& v, const K& k, basic_value<TC>& opt) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
return ::toml::find(v, detail::key_cast<TC>(k));
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
return opt;
|
||||
}
|
||||
}
|
||||
template<typename TC, typename K>
|
||||
cxx::enable_if_t<detail::is_type_config<TC>::value, basic_value<TC>> const&
|
||||
find_or(const basic_value<TC>& v, const K& k, const basic_value<TC>& opt) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
return ::toml::find(v, detail::key_cast<TC>(k));
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
return opt;
|
||||
}
|
||||
}
|
||||
template<typename TC, typename K>
|
||||
cxx::enable_if_t<detail::is_type_config<TC>::value, basic_value<TC>>
|
||||
find_or(basic_value<TC>&& v, const K& k, basic_value<TC>&& opt) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
return ::toml::find(v, detail::key_cast<TC>(k));
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
return opt;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// toml types (return type can be a reference)
|
||||
|
||||
template<typename T, typename TC, typename K>
|
||||
cxx::enable_if_t<detail::is_exact_toml_type<T, basic_value<TC>>::value,
|
||||
cxx::remove_cvref_t<T> const&>
|
||||
find_or(const basic_value<TC>& v, const K& k, const T& opt)
|
||||
{
|
||||
try
|
||||
{
|
||||
return ::toml::get<T>(v.at(detail::key_cast<TC>(k)));
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
return opt;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, typename TC, typename K>
|
||||
cxx::enable_if_t<cxx::conjunction<
|
||||
cxx::negation<std::is_const<T>>,
|
||||
detail::is_exact_toml_type<T, basic_value<TC>>
|
||||
>::value, cxx::remove_cvref_t<T>&>
|
||||
find_or(basic_value<TC>& v, const K& k, T& opt)
|
||||
{
|
||||
try
|
||||
{
|
||||
return ::toml::get<T>(v.at(detail::key_cast<TC>(k)));
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
return opt;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, typename TC, typename K>
|
||||
cxx::enable_if_t<detail::is_exact_toml_type<T, basic_value<TC>>::value,
|
||||
cxx::remove_cvref_t<T>>
|
||||
find_or(basic_value<TC>&& v, const K& k, T opt)
|
||||
{
|
||||
try
|
||||
{
|
||||
return ::toml::get<T>(std::move(v.at(detail::key_cast<TC>(k))));
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
return T(std::move(opt));
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// string literal (deduced as std::string)
|
||||
|
||||
// XXX to avoid confusion when T is explicitly specified in find_or<T>(),
|
||||
// we restrict the string type as std::string.
|
||||
template<typename TC, typename K>
|
||||
cxx::enable_if_t<detail::is_type_config<TC>::value, std::string>
|
||||
find_or(const basic_value<TC>& v, const K& k, const char* opt)
|
||||
{
|
||||
try
|
||||
{
|
||||
return ::toml::get<std::string>(v.at(detail::key_cast<TC>(k)));
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
return std::string(opt);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// other types (requires type conversion and return type cannot be a reference)
|
||||
|
||||
template<typename T, typename TC, typename K>
|
||||
cxx::enable_if_t<cxx::conjunction<
|
||||
cxx::negation<detail::is_basic_value<cxx::remove_cvref_t<T>>>,
|
||||
detail::is_not_toml_type<cxx::remove_cvref_t<T>, basic_value<TC>>,
|
||||
cxx::negation<std::is_same<cxx::remove_cvref_t<T>,
|
||||
const typename basic_value<TC>::string_type::value_type*>>
|
||||
>::value, cxx::remove_cvref_t<T>>
|
||||
find_or(const basic_value<TC>& v, const K& ky, T opt)
|
||||
{
|
||||
try
|
||||
{
|
||||
return ::toml::get<cxx::remove_cvref_t<T>>(v.at(detail::key_cast<TC>(ky)));
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
return cxx::remove_cvref_t<T>(std::move(opt));
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// recursive
|
||||
|
||||
namespace detail
|
||||
{
|
||||
|
||||
template<typename ...Ts>
|
||||
auto last_one(Ts&&... args)
|
||||
-> decltype(std::get<sizeof...(Ts)-1>(std::forward_as_tuple(std::forward<Ts>(args)...)))
|
||||
{
|
||||
return std::get<sizeof...(Ts)-1>(std::forward_as_tuple(std::forward<Ts>(args)...));
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
template<typename Value, typename K1, typename K2, typename K3, typename ... Ks>
|
||||
auto find_or(Value&& v, const K1& k1, const K2& k2, K3&& k3, Ks&& ... keys) noexcept
|
||||
-> cxx::enable_if_t<
|
||||
detail::is_basic_value<cxx::remove_cvref_t<Value>>::value,
|
||||
decltype(find_or(v, k2, std::forward<K3>(k3), std::forward<Ks>(keys)...))
|
||||
>
|
||||
{
|
||||
try
|
||||
{
|
||||
return find_or(v.at(k1), k2, std::forward<K3>(k3), std::forward<Ks>(keys)...);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
return detail::last_one(k3, keys...);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, typename TC, typename K1, typename K2, typename K3, typename ... Ks>
|
||||
T find_or(const basic_value<TC>& v, const K1& k1, const K2& k2, const K3& k3, const Ks& ... keys) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
return find_or<T>(v.at(k1), k2, k3, keys...);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
return static_cast<T>(detail::last_one(k3, keys...));
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// find_or_default<T>(value, key)
|
||||
|
||||
template<typename T, typename TC, typename K>
|
||||
cxx::enable_if_t<std::is_default_constructible<T>::value, T>
|
||||
find_or_default(const basic_value<TC>& v, K&& k) noexcept(std::is_nothrow_default_constructible<T>::value)
|
||||
{
|
||||
try
|
||||
{
|
||||
return ::toml::get<T>(v.at(detail::key_cast<TC>(std::forward<K>(k))));
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
return T();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, typename TC, typename K1, typename ... Ks>
|
||||
cxx::enable_if_t<std::is_default_constructible<T>::value, T>
|
||||
find_or_default(const basic_value<TC>& v, K1&& k1, Ks&& ... keys) noexcept(std::is_nothrow_default_constructible<T>::value)
|
||||
{
|
||||
try
|
||||
{
|
||||
return find_or_default<T>(v.at(std::forward<K1>(k1)), std::forward<Ks>(keys)...);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
return T();
|
||||
}
|
||||
}
|
||||
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
#endif // TOML11_FIND_HPP
|
||||
10
include/toml11/format.hpp
Normal file
10
include/toml11/format.hpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#ifndef TOML11_FORMAT_HPP
|
||||
#define TOML11_FORMAT_HPP
|
||||
|
||||
#include "fwd/format_fwd.hpp" // IWYU pragma: export
|
||||
|
||||
#if ! defined(TOML11_COMPILE_SOURCES)
|
||||
#include "impl/format_impl.hpp" // IWYU pragma: export
|
||||
#endif
|
||||
|
||||
#endif// TOML11_FORMAT_HPP
|
||||
21
include/toml11/from.hpp
Normal file
21
include/toml11/from.hpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#ifndef TOML11_FROM_HPP
|
||||
#define TOML11_FROM_HPP
|
||||
#include "version.hpp"
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
|
||||
template<typename T>
|
||||
struct from;
|
||||
// {
|
||||
// static T from_toml(const toml::value& v)
|
||||
// {
|
||||
// // User-defined conversions ...
|
||||
// }
|
||||
// };
|
||||
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
#endif // TOML11_FROM_HPP
|
||||
93
include/toml11/fwd/color_fwd.hpp
Normal file
93
include/toml11/fwd/color_fwd.hpp
Normal file
@@ -0,0 +1,93 @@
|
||||
#ifndef TOML11_COLOR_FWD_HPP
|
||||
#define TOML11_COLOR_FWD_HPP
|
||||
|
||||
#include <iosfwd>
|
||||
|
||||
#include "../version.hpp"
|
||||
|
||||
#ifdef TOML11_COLORIZE_ERROR_MESSAGE
|
||||
#define TOML11_ERROR_MESSAGE_COLORIZED true
|
||||
#else
|
||||
#define TOML11_ERROR_MESSAGE_COLORIZED false
|
||||
#endif
|
||||
|
||||
#ifdef TOML11_USE_THREAD_LOCAL_COLORIZATION
|
||||
#define TOML11_THREAD_LOCAL_COLORIZATION thread_local
|
||||
#else
|
||||
#define TOML11_THREAD_LOCAL_COLORIZATION
|
||||
#endif
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
namespace color
|
||||
{
|
||||
// put ANSI escape sequence to ostream
|
||||
inline namespace ansi
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
|
||||
// Control color mode globally
|
||||
class color_mode
|
||||
{
|
||||
public:
|
||||
|
||||
void enable() noexcept
|
||||
{
|
||||
should_color_ = true;
|
||||
}
|
||||
void disable() noexcept
|
||||
{
|
||||
should_color_ = false;
|
||||
}
|
||||
bool should_color() const noexcept
|
||||
{
|
||||
return should_color_;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
bool should_color_ = TOML11_ERROR_MESSAGE_COLORIZED;
|
||||
};
|
||||
|
||||
inline color_mode& color_status() noexcept
|
||||
{
|
||||
static TOML11_THREAD_LOCAL_COLORIZATION color_mode status;
|
||||
return status;
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
std::ostream& reset (std::ostream& os);
|
||||
std::ostream& bold (std::ostream& os);
|
||||
std::ostream& grey (std::ostream& os);
|
||||
std::ostream& gray (std::ostream& os);
|
||||
std::ostream& red (std::ostream& os);
|
||||
std::ostream& green (std::ostream& os);
|
||||
std::ostream& yellow (std::ostream& os);
|
||||
std::ostream& blue (std::ostream& os);
|
||||
std::ostream& magenta(std::ostream& os);
|
||||
std::ostream& cyan (std::ostream& os);
|
||||
std::ostream& white (std::ostream& os);
|
||||
|
||||
} // ansi
|
||||
|
||||
inline void enable()
|
||||
{
|
||||
return detail::color_status().enable();
|
||||
}
|
||||
inline void disable()
|
||||
{
|
||||
return detail::color_status().disable();
|
||||
}
|
||||
inline bool should_color()
|
||||
{
|
||||
return detail::color_status().should_color();
|
||||
}
|
||||
|
||||
} // color
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
#endif // TOML11_COLOR_FWD_HPP
|
||||
454
include/toml11/fwd/comments_fwd.hpp
Normal file
454
include/toml11/fwd/comments_fwd.hpp
Normal file
@@ -0,0 +1,454 @@
|
||||
#ifndef TOML11_COMMENTS_FWD_HPP
|
||||
#define TOML11_COMMENTS_FWD_HPP
|
||||
|
||||
// to use __has_builtin
|
||||
#include "../version.hpp" // IWYU pragma: keep
|
||||
|
||||
#include <exception>
|
||||
#include <initializer_list>
|
||||
#include <iterator>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <ostream>
|
||||
|
||||
// This file provides mainly two classes, `preserve_comments` and `discard_comments`.
|
||||
// Those two are a container that have the same interface as `std::vector<std::string>`
|
||||
// but bahaves in the opposite way. `preserve_comments` is just the same as
|
||||
// `std::vector<std::string>` and each `std::string` corresponds to a comment line.
|
||||
// Conversely, `discard_comments` discards all the strings and ignores everything
|
||||
// assigned in it. `discard_comments` is always empty and you will encounter an
|
||||
// error whenever you access to the element.
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
class discard_comments; // forward decl
|
||||
|
||||
class preserve_comments
|
||||
{
|
||||
public:
|
||||
// `container_type` is not provided in discard_comments.
|
||||
// do not use this inner-type in a generic code.
|
||||
using container_type = std::vector<std::string>;
|
||||
|
||||
using size_type = container_type::size_type;
|
||||
using difference_type = container_type::difference_type;
|
||||
using value_type = container_type::value_type;
|
||||
using reference = container_type::reference;
|
||||
using const_reference = container_type::const_reference;
|
||||
using pointer = container_type::pointer;
|
||||
using const_pointer = container_type::const_pointer;
|
||||
using iterator = container_type::iterator;
|
||||
using const_iterator = container_type::const_iterator;
|
||||
using reverse_iterator = container_type::reverse_iterator;
|
||||
using const_reverse_iterator = container_type::const_reverse_iterator;
|
||||
|
||||
public:
|
||||
|
||||
preserve_comments() = default;
|
||||
~preserve_comments() = default;
|
||||
preserve_comments(preserve_comments const&) = default;
|
||||
preserve_comments(preserve_comments &&) = default;
|
||||
preserve_comments& operator=(preserve_comments const&) = default;
|
||||
preserve_comments& operator=(preserve_comments &&) = default;
|
||||
|
||||
explicit preserve_comments(const std::vector<std::string>& c): comments(c){}
|
||||
explicit preserve_comments(std::vector<std::string>&& c)
|
||||
: comments(std::move(c))
|
||||
{}
|
||||
preserve_comments& operator=(const std::vector<std::string>& c)
|
||||
{
|
||||
comments = c;
|
||||
return *this;
|
||||
}
|
||||
preserve_comments& operator=(std::vector<std::string>&& c)
|
||||
{
|
||||
comments = std::move(c);
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit preserve_comments(const discard_comments&) {}
|
||||
|
||||
explicit preserve_comments(size_type n): comments(n) {}
|
||||
preserve_comments(size_type n, const std::string& x): comments(n, x) {}
|
||||
preserve_comments(std::initializer_list<std::string> x): comments(x) {}
|
||||
template<typename InputIterator>
|
||||
preserve_comments(InputIterator first, InputIterator last)
|
||||
: comments(first, last)
|
||||
{}
|
||||
|
||||
template<typename InputIterator>
|
||||
void assign(InputIterator first, InputIterator last) {comments.assign(first, last);}
|
||||
void assign(std::initializer_list<std::string> ini) {comments.assign(ini);}
|
||||
void assign(size_type n, const std::string& val) {comments.assign(n, val);}
|
||||
|
||||
// Related to the issue #97.
|
||||
//
|
||||
// `std::vector::insert` and `std::vector::erase` in the STL implementation
|
||||
// included in GCC 4.8.5 takes `std::vector::iterator` instead of
|
||||
// `std::vector::const_iterator`. It causes compilation error in GCC 4.8.5.
|
||||
#if defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__) && !defined(__clang__)
|
||||
# if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) <= 40805
|
||||
# define TOML11_WORKAROUND_GCC_4_8_X_STANDARD_LIBRARY_IMPLEMENTATION
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef TOML11_WORKAROUND_GCC_4_8_X_STANDARD_LIBRARY_IMPLEMENTATION
|
||||
iterator insert(iterator p, const std::string& x)
|
||||
{
|
||||
return comments.insert(p, x);
|
||||
}
|
||||
iterator insert(iterator p, std::string&& x)
|
||||
{
|
||||
return comments.insert(p, std::move(x));
|
||||
}
|
||||
void insert(iterator p, size_type n, const std::string& x)
|
||||
{
|
||||
return comments.insert(p, n, x);
|
||||
}
|
||||
template<typename InputIterator>
|
||||
void insert(iterator p, InputIterator first, InputIterator last)
|
||||
{
|
||||
return comments.insert(p, first, last);
|
||||
}
|
||||
void insert(iterator p, std::initializer_list<std::string> ini)
|
||||
{
|
||||
return comments.insert(p, ini);
|
||||
}
|
||||
|
||||
template<typename ... Ts>
|
||||
iterator emplace(iterator p, Ts&& ... args)
|
||||
{
|
||||
return comments.emplace(p, std::forward<Ts>(args)...);
|
||||
}
|
||||
|
||||
iterator erase(iterator pos) {return comments.erase(pos);}
|
||||
iterator erase(iterator first, iterator last)
|
||||
{
|
||||
return comments.erase(first, last);
|
||||
}
|
||||
#else
|
||||
iterator insert(const_iterator p, const std::string& x)
|
||||
{
|
||||
return comments.insert(p, x);
|
||||
}
|
||||
iterator insert(const_iterator p, std::string&& x)
|
||||
{
|
||||
return comments.insert(p, std::move(x));
|
||||
}
|
||||
iterator insert(const_iterator p, size_type n, const std::string& x)
|
||||
{
|
||||
return comments.insert(p, n, x);
|
||||
}
|
||||
template<typename InputIterator>
|
||||
iterator insert(const_iterator p, InputIterator first, InputIterator last)
|
||||
{
|
||||
return comments.insert(p, first, last);
|
||||
}
|
||||
iterator insert(const_iterator p, std::initializer_list<std::string> ini)
|
||||
{
|
||||
return comments.insert(p, ini);
|
||||
}
|
||||
|
||||
template<typename ... Ts>
|
||||
iterator emplace(const_iterator p, Ts&& ... args)
|
||||
{
|
||||
return comments.emplace(p, std::forward<Ts>(args)...);
|
||||
}
|
||||
|
||||
iterator erase(const_iterator pos) {return comments.erase(pos);}
|
||||
iterator erase(const_iterator first, const_iterator last)
|
||||
{
|
||||
return comments.erase(first, last);
|
||||
}
|
||||
#endif
|
||||
|
||||
void swap(preserve_comments& other) {comments.swap(other.comments);}
|
||||
|
||||
void push_back(const std::string& v) {comments.push_back(v);}
|
||||
void push_back(std::string&& v) {comments.push_back(std::move(v));}
|
||||
void pop_back() {comments.pop_back();}
|
||||
|
||||
template<typename ... Ts>
|
||||
void emplace_back(Ts&& ... args) {comments.emplace_back(std::forward<Ts>(args)...);}
|
||||
|
||||
void clear() {comments.clear();}
|
||||
|
||||
size_type size() const noexcept {return comments.size();}
|
||||
size_type max_size() const noexcept {return comments.max_size();}
|
||||
size_type capacity() const noexcept {return comments.capacity();}
|
||||
bool empty() const noexcept {return comments.empty();}
|
||||
|
||||
void reserve(size_type n) {comments.reserve(n);}
|
||||
void resize(size_type n) {comments.resize(n);}
|
||||
void resize(size_type n, const std::string& c) {comments.resize(n, c);}
|
||||
void shrink_to_fit() {comments.shrink_to_fit();}
|
||||
|
||||
reference operator[](const size_type n) noexcept {return comments[n];}
|
||||
const_reference operator[](const size_type n) const noexcept {return comments[n];}
|
||||
reference at(const size_type n) {return comments.at(n);}
|
||||
const_reference at(const size_type n) const {return comments.at(n);}
|
||||
reference front() noexcept {return comments.front();}
|
||||
const_reference front() const noexcept {return comments.front();}
|
||||
reference back() noexcept {return comments.back();}
|
||||
const_reference back() const noexcept {return comments.back();}
|
||||
|
||||
pointer data() noexcept {return comments.data();}
|
||||
const_pointer data() const noexcept {return comments.data();}
|
||||
|
||||
iterator begin() noexcept {return comments.begin();}
|
||||
iterator end() noexcept {return comments.end();}
|
||||
const_iterator begin() const noexcept {return comments.begin();}
|
||||
const_iterator end() const noexcept {return comments.end();}
|
||||
const_iterator cbegin() const noexcept {return comments.cbegin();}
|
||||
const_iterator cend() const noexcept {return comments.cend();}
|
||||
|
||||
reverse_iterator rbegin() noexcept {return comments.rbegin();}
|
||||
reverse_iterator rend() noexcept {return comments.rend();}
|
||||
const_reverse_iterator rbegin() const noexcept {return comments.rbegin();}
|
||||
const_reverse_iterator rend() const noexcept {return comments.rend();}
|
||||
const_reverse_iterator crbegin() const noexcept {return comments.crbegin();}
|
||||
const_reverse_iterator crend() const noexcept {return comments.crend();}
|
||||
|
||||
friend bool operator==(const preserve_comments&, const preserve_comments&);
|
||||
friend bool operator!=(const preserve_comments&, const preserve_comments&);
|
||||
friend bool operator< (const preserve_comments&, const preserve_comments&);
|
||||
friend bool operator<=(const preserve_comments&, const preserve_comments&);
|
||||
friend bool operator> (const preserve_comments&, const preserve_comments&);
|
||||
friend bool operator>=(const preserve_comments&, const preserve_comments&);
|
||||
|
||||
friend void swap(preserve_comments&, std::vector<std::string>&);
|
||||
friend void swap(std::vector<std::string>&, preserve_comments&);
|
||||
|
||||
private:
|
||||
|
||||
container_type comments;
|
||||
};
|
||||
|
||||
bool operator==(const preserve_comments& lhs, const preserve_comments& rhs);
|
||||
bool operator!=(const preserve_comments& lhs, const preserve_comments& rhs);
|
||||
bool operator< (const preserve_comments& lhs, const preserve_comments& rhs);
|
||||
bool operator<=(const preserve_comments& lhs, const preserve_comments& rhs);
|
||||
bool operator> (const preserve_comments& lhs, const preserve_comments& rhs);
|
||||
bool operator>=(const preserve_comments& lhs, const preserve_comments& rhs);
|
||||
|
||||
void swap(preserve_comments& lhs, preserve_comments& rhs);
|
||||
void swap(preserve_comments& lhs, std::vector<std::string>& rhs);
|
||||
void swap(std::vector<std::string>& lhs, preserve_comments& rhs);
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const preserve_comments& com);
|
||||
|
||||
namespace detail
|
||||
{
|
||||
|
||||
// To provide the same interface with `preserve_comments`, `discard_comments`
|
||||
// should have an iterator. But it does not contain anything, so we need to
|
||||
// add an iterator that points nothing.
|
||||
//
|
||||
// It always points null, so DO NOT unwrap this iterator. It always crashes
|
||||
// your program.
|
||||
template<typename T, bool is_const>
|
||||
struct empty_iterator
|
||||
{
|
||||
using value_type = T;
|
||||
using reference_type = typename std::conditional<is_const, T const&, T&>::type;
|
||||
using pointer_type = typename std::conditional<is_const, T const*, T*>::type;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using iterator_category = std::random_access_iterator_tag;
|
||||
|
||||
empty_iterator() = default;
|
||||
~empty_iterator() = default;
|
||||
empty_iterator(empty_iterator const&) = default;
|
||||
empty_iterator(empty_iterator &&) = default;
|
||||
empty_iterator& operator=(empty_iterator const&) = default;
|
||||
empty_iterator& operator=(empty_iterator &&) = default;
|
||||
|
||||
// DO NOT call these operators.
|
||||
reference_type operator*() const noexcept {std::terminate();}
|
||||
pointer_type operator->() const noexcept {return nullptr;}
|
||||
reference_type operator[](difference_type) const noexcept {return this->operator*();}
|
||||
|
||||
// These operators do nothing.
|
||||
empty_iterator& operator++() noexcept {return *this;}
|
||||
empty_iterator operator++(int) noexcept {return *this;}
|
||||
empty_iterator& operator--() noexcept {return *this;}
|
||||
empty_iterator operator--(int) noexcept {return *this;}
|
||||
|
||||
empty_iterator& operator+=(difference_type) noexcept {return *this;}
|
||||
empty_iterator& operator-=(difference_type) noexcept {return *this;}
|
||||
|
||||
empty_iterator operator+(difference_type) const noexcept {return *this;}
|
||||
empty_iterator operator-(difference_type) const noexcept {return *this;}
|
||||
};
|
||||
|
||||
template<typename T, bool C>
|
||||
bool operator==(const empty_iterator<T, C>&, const empty_iterator<T, C>&) noexcept {return true;}
|
||||
template<typename T, bool C>
|
||||
bool operator!=(const empty_iterator<T, C>&, const empty_iterator<T, C>&) noexcept {return false;}
|
||||
template<typename T, bool C>
|
||||
bool operator< (const empty_iterator<T, C>&, const empty_iterator<T, C>&) noexcept {return false;}
|
||||
template<typename T, bool C>
|
||||
bool operator<=(const empty_iterator<T, C>&, const empty_iterator<T, C>&) noexcept {return true;}
|
||||
template<typename T, bool C>
|
||||
bool operator> (const empty_iterator<T, C>&, const empty_iterator<T, C>&) noexcept {return false;}
|
||||
template<typename T, bool C>
|
||||
bool operator>=(const empty_iterator<T, C>&, const empty_iterator<T, C>&) noexcept {return true;}
|
||||
|
||||
template<typename T, bool C>
|
||||
typename empty_iterator<T, C>::difference_type
|
||||
operator-(const empty_iterator<T, C>&, const empty_iterator<T, C>&) noexcept {return 0;}
|
||||
|
||||
template<typename T, bool C>
|
||||
empty_iterator<T, C>
|
||||
operator+(typename empty_iterator<T, C>::difference_type, const empty_iterator<T, C>& rhs) noexcept {return rhs;}
|
||||
template<typename T, bool C>
|
||||
empty_iterator<T, C>
|
||||
operator+(const empty_iterator<T, C>& lhs, typename empty_iterator<T, C>::difference_type) noexcept {return lhs;}
|
||||
|
||||
} // detail
|
||||
|
||||
// The default comment type. It discards all the comments. It requires only one
|
||||
// byte to contain, so the memory footprint is smaller than preserve_comments.
|
||||
//
|
||||
// It just ignores `push_back`, `insert`, `erase`, and any other modifications.
|
||||
// IT always returns size() == 0, the iterator taken by `begin()` is always the
|
||||
// same as that of `end()`, and accessing through `operator[]` or iterators
|
||||
// always causes a segmentation fault. DO NOT access to the element of this.
|
||||
//
|
||||
// Why this is chose as the default type is because the last version (2.x.y)
|
||||
// does not contain any comments in a value. To minimize the impact on the
|
||||
// efficiency, this is chosen as a default.
|
||||
//
|
||||
// To reduce the memory footprint, later we can try empty base optimization (EBO).
|
||||
class discard_comments
|
||||
{
|
||||
public:
|
||||
using size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using value_type = std::string;
|
||||
using reference = std::string&;
|
||||
using const_reference = std::string const&;
|
||||
using pointer = std::string*;
|
||||
using const_pointer = std::string const*;
|
||||
using iterator = detail::empty_iterator<std::string, false>;
|
||||
using const_iterator = detail::empty_iterator<std::string, true>;
|
||||
using reverse_iterator = detail::empty_iterator<std::string, false>;
|
||||
using const_reverse_iterator = detail::empty_iterator<std::string, true>;
|
||||
|
||||
public:
|
||||
discard_comments() = default;
|
||||
~discard_comments() = default;
|
||||
discard_comments(discard_comments const&) = default;
|
||||
discard_comments(discard_comments &&) = default;
|
||||
discard_comments& operator=(discard_comments const&) = default;
|
||||
discard_comments& operator=(discard_comments &&) = default;
|
||||
|
||||
explicit discard_comments(const std::vector<std::string>&) noexcept {}
|
||||
explicit discard_comments(std::vector<std::string>&&) noexcept {}
|
||||
discard_comments& operator=(const std::vector<std::string>&) noexcept {return *this;}
|
||||
discard_comments& operator=(std::vector<std::string>&&) noexcept {return *this;}
|
||||
|
||||
explicit discard_comments(const preserve_comments&) noexcept {}
|
||||
|
||||
explicit discard_comments(size_type) noexcept {}
|
||||
discard_comments(size_type, const std::string&) noexcept {}
|
||||
discard_comments(std::initializer_list<std::string>) noexcept {}
|
||||
template<typename InputIterator>
|
||||
discard_comments(InputIterator, InputIterator) noexcept {}
|
||||
|
||||
template<typename InputIterator>
|
||||
void assign(InputIterator, InputIterator) noexcept {}
|
||||
void assign(std::initializer_list<std::string>) noexcept {}
|
||||
void assign(size_type, const std::string&) noexcept {}
|
||||
|
||||
iterator insert(const_iterator, const std::string&) {return iterator{};}
|
||||
iterator insert(const_iterator, std::string&&) {return iterator{};}
|
||||
iterator insert(const_iterator, size_type, const std::string&) {return iterator{};}
|
||||
template<typename InputIterator>
|
||||
iterator insert(const_iterator, InputIterator, InputIterator) {return iterator{};}
|
||||
iterator insert(const_iterator, std::initializer_list<std::string>) {return iterator{};}
|
||||
|
||||
template<typename ... Ts>
|
||||
iterator emplace(const_iterator, Ts&& ...) {return iterator{};}
|
||||
iterator erase(const_iterator) {return iterator{};}
|
||||
iterator erase(const_iterator, const_iterator) {return iterator{};}
|
||||
|
||||
void swap(discard_comments&) {return;}
|
||||
|
||||
void push_back(const std::string&) {return;}
|
||||
void push_back(std::string&& ) {return;}
|
||||
void pop_back() {return;}
|
||||
|
||||
template<typename ... Ts>
|
||||
void emplace_back(Ts&& ...) {return;}
|
||||
|
||||
void clear() {return;}
|
||||
|
||||
size_type size() const noexcept {return 0;}
|
||||
size_type max_size() const noexcept {return 0;}
|
||||
size_type capacity() const noexcept {return 0;}
|
||||
bool empty() const noexcept {return true;}
|
||||
|
||||
void reserve(size_type) {return;}
|
||||
void resize(size_type) {return;}
|
||||
void resize(size_type, const std::string&) {return;}
|
||||
void shrink_to_fit() {return;}
|
||||
|
||||
// DO NOT access to the element of this container. This container is always
|
||||
// empty, so accessing through operator[], front/back, data causes address
|
||||
// error.
|
||||
|
||||
reference operator[](const size_type) noexcept {never_call("toml::discard_comment::operator[]");}
|
||||
const_reference operator[](const size_type) const noexcept {never_call("toml::discard_comment::operator[]");}
|
||||
reference at(const size_type) {throw std::out_of_range("toml::discard_comment is always empty.");}
|
||||
const_reference at(const size_type) const {throw std::out_of_range("toml::discard_comment is always empty.");}
|
||||
reference front() noexcept {never_call("toml::discard_comment::front");}
|
||||
const_reference front() const noexcept {never_call("toml::discard_comment::front");}
|
||||
reference back() noexcept {never_call("toml::discard_comment::back");}
|
||||
const_reference back() const noexcept {never_call("toml::discard_comment::back");}
|
||||
|
||||
pointer data() noexcept {return nullptr;}
|
||||
const_pointer data() const noexcept {return nullptr;}
|
||||
|
||||
iterator begin() noexcept {return iterator{};}
|
||||
iterator end() noexcept {return iterator{};}
|
||||
const_iterator begin() const noexcept {return const_iterator{};}
|
||||
const_iterator end() const noexcept {return const_iterator{};}
|
||||
const_iterator cbegin() const noexcept {return const_iterator{};}
|
||||
const_iterator cend() const noexcept {return const_iterator{};}
|
||||
|
||||
reverse_iterator rbegin() noexcept {return iterator{};}
|
||||
reverse_iterator rend() noexcept {return iterator{};}
|
||||
const_reverse_iterator rbegin() const noexcept {return const_iterator{};}
|
||||
const_reverse_iterator rend() const noexcept {return const_iterator{};}
|
||||
const_reverse_iterator crbegin() const noexcept {return const_iterator{};}
|
||||
const_reverse_iterator crend() const noexcept {return const_iterator{};}
|
||||
|
||||
private:
|
||||
|
||||
[[noreturn]] static void never_call(const char *const this_function)
|
||||
{
|
||||
#if __has_builtin(__builtin_unreachable)
|
||||
__builtin_unreachable();
|
||||
#endif
|
||||
throw std::logic_error{this_function};
|
||||
}
|
||||
};
|
||||
|
||||
inline bool operator==(const discard_comments&, const discard_comments&) noexcept {return true;}
|
||||
inline bool operator!=(const discard_comments&, const discard_comments&) noexcept {return false;}
|
||||
inline bool operator< (const discard_comments&, const discard_comments&) noexcept {return false;}
|
||||
inline bool operator<=(const discard_comments&, const discard_comments&) noexcept {return true;}
|
||||
inline bool operator> (const discard_comments&, const discard_comments&) noexcept {return false;}
|
||||
inline bool operator>=(const discard_comments&, const discard_comments&) noexcept {return true;}
|
||||
|
||||
inline void swap(const discard_comments&, const discard_comments&) noexcept {return;}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& os, const discard_comments&) {return os;}
|
||||
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml11
|
||||
#endif // TOML11_COMMENTS_FWD_HPP
|
||||
266
include/toml11/fwd/datetime_fwd.hpp
Normal file
266
include/toml11/fwd/datetime_fwd.hpp
Normal file
@@ -0,0 +1,266 @@
|
||||
#ifndef TOML11_DATETIME_FWD_HPP
|
||||
#define TOML11_DATETIME_FWD_HPP
|
||||
|
||||
#include <chrono>
|
||||
#include <iosfwd>
|
||||
#include <string>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
|
||||
#include "../version.hpp"
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
|
||||
enum class month_t : std::uint8_t
|
||||
{
|
||||
Jan = 0,
|
||||
Feb = 1,
|
||||
Mar = 2,
|
||||
Apr = 3,
|
||||
May = 4,
|
||||
Jun = 5,
|
||||
Jul = 6,
|
||||
Aug = 7,
|
||||
Sep = 8,
|
||||
Oct = 9,
|
||||
Nov = 10,
|
||||
Dec = 11
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
struct local_date
|
||||
{
|
||||
std::int16_t year{0}; // A.D. (like, 2018)
|
||||
std::uint8_t month{0}; // [0, 11]
|
||||
std::uint8_t day{0}; // [1, 31]
|
||||
|
||||
local_date(int y, month_t m, int d)
|
||||
: year {static_cast<std::int16_t>(y)},
|
||||
month{static_cast<std::uint8_t>(m)},
|
||||
day {static_cast<std::uint8_t>(d)}
|
||||
{}
|
||||
|
||||
explicit local_date(const std::tm& t)
|
||||
: year {static_cast<std::int16_t>(t.tm_year + 1900)},
|
||||
month{static_cast<std::uint8_t>(t.tm_mon)},
|
||||
day {static_cast<std::uint8_t>(t.tm_mday)}
|
||||
{}
|
||||
|
||||
explicit local_date(const std::chrono::system_clock::time_point& tp);
|
||||
explicit local_date(const std::time_t t);
|
||||
|
||||
operator std::chrono::system_clock::time_point() const;
|
||||
operator std::time_t() const;
|
||||
|
||||
local_date() = default;
|
||||
~local_date() = default;
|
||||
local_date(local_date const&) = default;
|
||||
local_date(local_date&&) = default;
|
||||
local_date& operator=(local_date const&) = default;
|
||||
local_date& operator=(local_date&&) = default;
|
||||
};
|
||||
bool operator==(const local_date& lhs, const local_date& rhs);
|
||||
bool operator!=(const local_date& lhs, const local_date& rhs);
|
||||
bool operator< (const local_date& lhs, const local_date& rhs);
|
||||
bool operator<=(const local_date& lhs, const local_date& rhs);
|
||||
bool operator> (const local_date& lhs, const local_date& rhs);
|
||||
bool operator>=(const local_date& lhs, const local_date& rhs);
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const local_date& date);
|
||||
std::string to_string(const local_date& date);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
struct local_time
|
||||
{
|
||||
std::uint8_t hour{0}; // [0, 23]
|
||||
std::uint8_t minute{0}; // [0, 59]
|
||||
std::uint8_t second{0}; // [0, 60]
|
||||
std::uint16_t millisecond{0}; // [0, 999]
|
||||
std::uint16_t microsecond{0}; // [0, 999]
|
||||
std::uint16_t nanosecond{0}; // [0, 999]
|
||||
|
||||
local_time(int h, int m, int s,
|
||||
int ms = 0, int us = 0, int ns = 0)
|
||||
: hour {static_cast<std::uint8_t>(h)},
|
||||
minute{static_cast<std::uint8_t>(m)},
|
||||
second{static_cast<std::uint8_t>(s)},
|
||||
millisecond{static_cast<std::uint16_t>(ms)},
|
||||
microsecond{static_cast<std::uint16_t>(us)},
|
||||
nanosecond {static_cast<std::uint16_t>(ns)}
|
||||
{}
|
||||
|
||||
explicit local_time(const std::tm& t)
|
||||
: hour {static_cast<std::uint8_t>(t.tm_hour)},
|
||||
minute{static_cast<std::uint8_t>(t.tm_min )},
|
||||
second{static_cast<std::uint8_t>(t.tm_sec )},
|
||||
millisecond{0}, microsecond{0}, nanosecond{0}
|
||||
{}
|
||||
|
||||
template<typename Rep, typename Period>
|
||||
explicit local_time(const std::chrono::duration<Rep, Period>& t)
|
||||
{
|
||||
const auto h = std::chrono::duration_cast<std::chrono::hours>(t);
|
||||
this->hour = static_cast<std::uint8_t>(h.count());
|
||||
const auto t2 = t - h;
|
||||
const auto m = std::chrono::duration_cast<std::chrono::minutes>(t2);
|
||||
this->minute = static_cast<std::uint8_t>(m.count());
|
||||
const auto t3 = t2 - m;
|
||||
const auto s = std::chrono::duration_cast<std::chrono::seconds>(t3);
|
||||
this->second = static_cast<std::uint8_t>(s.count());
|
||||
const auto t4 = t3 - s;
|
||||
const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(t4);
|
||||
this->millisecond = static_cast<std::uint16_t>(ms.count());
|
||||
const auto t5 = t4 - ms;
|
||||
const auto us = std::chrono::duration_cast<std::chrono::microseconds>(t5);
|
||||
this->microsecond = static_cast<std::uint16_t>(us.count());
|
||||
const auto t6 = t5 - us;
|
||||
const auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(t6);
|
||||
this->nanosecond = static_cast<std::uint16_t>(ns.count());
|
||||
}
|
||||
|
||||
operator std::chrono::nanoseconds() const;
|
||||
|
||||
local_time() = default;
|
||||
~local_time() = default;
|
||||
local_time(local_time const&) = default;
|
||||
local_time(local_time&&) = default;
|
||||
local_time& operator=(local_time const&) = default;
|
||||
local_time& operator=(local_time&&) = default;
|
||||
};
|
||||
|
||||
bool operator==(const local_time& lhs, const local_time& rhs);
|
||||
bool operator!=(const local_time& lhs, const local_time& rhs);
|
||||
bool operator< (const local_time& lhs, const local_time& rhs);
|
||||
bool operator<=(const local_time& lhs, const local_time& rhs);
|
||||
bool operator> (const local_time& lhs, const local_time& rhs);
|
||||
bool operator>=(const local_time& lhs, const local_time& rhs);
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const local_time& time);
|
||||
std::string to_string(const local_time& time);
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
struct time_offset
|
||||
{
|
||||
std::int8_t hour{0}; // [-12, 12]
|
||||
std::int8_t minute{0}; // [-59, 59]
|
||||
|
||||
time_offset(int h, int m)
|
||||
: hour {static_cast<std::int8_t>(h)},
|
||||
minute{static_cast<std::int8_t>(m)}
|
||||
{}
|
||||
|
||||
operator std::chrono::minutes() const;
|
||||
|
||||
time_offset() = default;
|
||||
~time_offset() = default;
|
||||
time_offset(time_offset const&) = default;
|
||||
time_offset(time_offset&&) = default;
|
||||
time_offset& operator=(time_offset const&) = default;
|
||||
time_offset& operator=(time_offset&&) = default;
|
||||
};
|
||||
|
||||
bool operator==(const time_offset& lhs, const time_offset& rhs);
|
||||
bool operator!=(const time_offset& lhs, const time_offset& rhs);
|
||||
bool operator< (const time_offset& lhs, const time_offset& rhs);
|
||||
bool operator<=(const time_offset& lhs, const time_offset& rhs);
|
||||
bool operator> (const time_offset& lhs, const time_offset& rhs);
|
||||
bool operator>=(const time_offset& lhs, const time_offset& rhs);
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const time_offset& offset);
|
||||
|
||||
std::string to_string(const time_offset& offset);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
struct local_datetime
|
||||
{
|
||||
local_date date{};
|
||||
local_time time{};
|
||||
|
||||
local_datetime(local_date d, local_time t): date{d}, time{t} {}
|
||||
|
||||
explicit local_datetime(const std::tm& t): date{t}, time{t}{}
|
||||
|
||||
explicit local_datetime(const std::chrono::system_clock::time_point& tp);
|
||||
explicit local_datetime(const std::time_t t);
|
||||
|
||||
operator std::chrono::system_clock::time_point() const;
|
||||
operator std::time_t() const;
|
||||
|
||||
local_datetime() = default;
|
||||
~local_datetime() = default;
|
||||
local_datetime(local_datetime const&) = default;
|
||||
local_datetime(local_datetime&&) = default;
|
||||
local_datetime& operator=(local_datetime const&) = default;
|
||||
local_datetime& operator=(local_datetime&&) = default;
|
||||
};
|
||||
|
||||
bool operator==(const local_datetime& lhs, const local_datetime& rhs);
|
||||
bool operator!=(const local_datetime& lhs, const local_datetime& rhs);
|
||||
bool operator< (const local_datetime& lhs, const local_datetime& rhs);
|
||||
bool operator<=(const local_datetime& lhs, const local_datetime& rhs);
|
||||
bool operator> (const local_datetime& lhs, const local_datetime& rhs);
|
||||
bool operator>=(const local_datetime& lhs, const local_datetime& rhs);
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const local_datetime& dt);
|
||||
|
||||
std::string to_string(const local_datetime& dt);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
struct offset_datetime
|
||||
{
|
||||
local_date date{};
|
||||
local_time time{};
|
||||
time_offset offset{};
|
||||
|
||||
offset_datetime(local_date d, local_time t, time_offset o)
|
||||
: date{d}, time{t}, offset{o}
|
||||
{}
|
||||
offset_datetime(const local_datetime& dt, time_offset o)
|
||||
: date{dt.date}, time{dt.time}, offset{o}
|
||||
{}
|
||||
// use the current local timezone offset
|
||||
explicit offset_datetime(const local_datetime& ld);
|
||||
explicit offset_datetime(const std::chrono::system_clock::time_point& tp);
|
||||
explicit offset_datetime(const std::time_t& t);
|
||||
explicit offset_datetime(const std::tm& t);
|
||||
|
||||
operator std::chrono::system_clock::time_point() const;
|
||||
|
||||
operator std::time_t() const;
|
||||
|
||||
offset_datetime() = default;
|
||||
~offset_datetime() = default;
|
||||
offset_datetime(offset_datetime const&) = default;
|
||||
offset_datetime(offset_datetime&&) = default;
|
||||
offset_datetime& operator=(offset_datetime const&) = default;
|
||||
offset_datetime& operator=(offset_datetime&&) = default;
|
||||
|
||||
private:
|
||||
|
||||
static time_offset get_local_offset(const std::time_t* tp);
|
||||
};
|
||||
|
||||
bool operator==(const offset_datetime& lhs, const offset_datetime& rhs);
|
||||
bool operator!=(const offset_datetime& lhs, const offset_datetime& rhs);
|
||||
bool operator< (const offset_datetime& lhs, const offset_datetime& rhs);
|
||||
bool operator<=(const offset_datetime& lhs, const offset_datetime& rhs);
|
||||
bool operator> (const offset_datetime& lhs, const offset_datetime& rhs);
|
||||
bool operator>=(const offset_datetime& lhs, const offset_datetime& rhs);
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const offset_datetime& dt);
|
||||
|
||||
std::string to_string(const offset_datetime& dt);
|
||||
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
#endif // TOML11_DATETIME_FWD_HPP
|
||||
101
include/toml11/fwd/error_info_fwd.hpp
Normal file
101
include/toml11/fwd/error_info_fwd.hpp
Normal file
@@ -0,0 +1,101 @@
|
||||
#ifndef TOML11_ERROR_INFO_FWD_HPP
|
||||
#define TOML11_ERROR_INFO_FWD_HPP
|
||||
|
||||
#include "../source_location.hpp"
|
||||
#include "../utility.hpp"
|
||||
#include "../version.hpp"
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
|
||||
// error info returned from parser.
|
||||
struct error_info
|
||||
{
|
||||
error_info(std::string t, source_location l, std::string m, std::string s = "")
|
||||
: title_(std::move(t)), locations_{std::make_pair(std::move(l), std::move(m))},
|
||||
suffix_(std::move(s))
|
||||
{}
|
||||
|
||||
error_info(std::string t, std::vector<std::pair<source_location, std::string>> l,
|
||||
std::string s = "")
|
||||
: title_(std::move(t)), locations_(std::move(l)), suffix_(std::move(s))
|
||||
{}
|
||||
|
||||
std::string const& title() const noexcept {return title_;}
|
||||
std::string & title() noexcept {return title_;}
|
||||
|
||||
std::vector<std::pair<source_location, std::string>> const&
|
||||
locations() const noexcept {return locations_;}
|
||||
|
||||
void add_locations(source_location loc, std::string msg) noexcept
|
||||
{
|
||||
locations_.emplace_back(std::move(loc), std::move(msg));
|
||||
}
|
||||
|
||||
std::string const& suffix() const noexcept {return suffix_;}
|
||||
std::string & suffix() noexcept {return suffix_;}
|
||||
|
||||
private:
|
||||
|
||||
std::string title_;
|
||||
std::vector<std::pair<source_location, std::string>> locations_;
|
||||
std::string suffix_; // hint or something like that
|
||||
};
|
||||
|
||||
// forward decl
|
||||
template<typename TypeConfig>
|
||||
class basic_value;
|
||||
|
||||
namespace detail
|
||||
{
|
||||
inline error_info make_error_info_rec(error_info e)
|
||||
{
|
||||
return e;
|
||||
}
|
||||
inline error_info make_error_info_rec(error_info e, std::string s)
|
||||
{
|
||||
e.suffix() = s;
|
||||
return e;
|
||||
}
|
||||
|
||||
template<typename TC, typename ... Ts>
|
||||
error_info make_error_info_rec(error_info e,
|
||||
const basic_value<TC>& v, std::string msg, Ts&& ... tail);
|
||||
|
||||
template<typename ... Ts>
|
||||
error_info make_error_info_rec(error_info e,
|
||||
source_location loc, std::string msg, Ts&& ... tail)
|
||||
{
|
||||
e.add_locations(std::move(loc), std::move(msg));
|
||||
return make_error_info_rec(std::move(e), std::forward<Ts>(tail)...);
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
template<typename ... Ts>
|
||||
error_info make_error_info(
|
||||
std::string title, source_location loc, std::string msg, Ts&& ... tail)
|
||||
{
|
||||
error_info ei(std::move(title), std::move(loc), std::move(msg));
|
||||
return detail::make_error_info_rec(ei, std::forward<Ts>(tail) ... );
|
||||
}
|
||||
|
||||
std::string format_error(const std::string& errkind, const error_info& err);
|
||||
std::string format_error(const error_info& err);
|
||||
|
||||
// for custom error message
|
||||
template<typename ... Ts>
|
||||
std::string format_error(std::string title,
|
||||
source_location loc, std::string msg, Ts&& ... tail)
|
||||
{
|
||||
return format_error("", make_error_info(std::move(title),
|
||||
std::move(loc), std::move(msg), std::forward<Ts>(tail)...));
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const error_info& e);
|
||||
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
#endif // TOML11_ERROR_INFO_FWD_HPP
|
||||
255
include/toml11/fwd/format_fwd.hpp
Normal file
255
include/toml11/fwd/format_fwd.hpp
Normal file
@@ -0,0 +1,255 @@
|
||||
#ifndef TOML11_FORMAT_FWD_HPP
|
||||
#define TOML11_FORMAT_FWD_HPP
|
||||
|
||||
#include <iosfwd>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "../version.hpp"
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
|
||||
// toml types with serialization info
|
||||
|
||||
enum class indent_char : std::uint8_t
|
||||
{
|
||||
space, // use space
|
||||
tab, // use tab
|
||||
none // no indent
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const indent_char& c);
|
||||
std::string to_string(const indent_char c);
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// boolean
|
||||
|
||||
struct boolean_format_info
|
||||
{
|
||||
// nothing, for now
|
||||
};
|
||||
|
||||
inline bool operator==(const boolean_format_info&, const boolean_format_info&) noexcept
|
||||
{
|
||||
return true;
|
||||
}
|
||||
inline bool operator!=(const boolean_format_info&, const boolean_format_info&) noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// integer
|
||||
|
||||
enum class integer_format : std::uint8_t
|
||||
{
|
||||
dec = 0,
|
||||
bin = 1,
|
||||
oct = 2,
|
||||
hex = 3,
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const integer_format f);
|
||||
std::string to_string(const integer_format);
|
||||
|
||||
struct integer_format_info
|
||||
{
|
||||
integer_format fmt = integer_format::dec;
|
||||
bool uppercase = true; // hex with uppercase
|
||||
std::size_t width = 0; // minimal width (may exceed)
|
||||
std::size_t spacer = 0; // position of `_` (if 0, no spacer)
|
||||
std::string suffix = ""; // _suffix (library extension)
|
||||
};
|
||||
|
||||
bool operator==(const integer_format_info&, const integer_format_info&) noexcept;
|
||||
bool operator!=(const integer_format_info&, const integer_format_info&) noexcept;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// floating
|
||||
|
||||
enum class floating_format : std::uint8_t
|
||||
{
|
||||
defaultfloat = 0,
|
||||
fixed = 1, // does not include exponential part
|
||||
scientific = 2, // always include exponential part
|
||||
hex = 3 // hexfloat extension
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const floating_format f);
|
||||
std::string to_string(const floating_format);
|
||||
|
||||
struct floating_format_info
|
||||
{
|
||||
floating_format fmt = floating_format::defaultfloat;
|
||||
std::size_t prec = 0; // precision (if 0, use the default)
|
||||
std::string suffix = ""; // 1.0e+2_suffix (library extension)
|
||||
};
|
||||
|
||||
bool operator==(const floating_format_info&, const floating_format_info&) noexcept;
|
||||
bool operator!=(const floating_format_info&, const floating_format_info&) noexcept;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// string
|
||||
|
||||
enum class string_format : std::uint8_t
|
||||
{
|
||||
basic = 0,
|
||||
literal = 1,
|
||||
multiline_basic = 2,
|
||||
multiline_literal = 3
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const string_format f);
|
||||
std::string to_string(const string_format);
|
||||
|
||||
struct string_format_info
|
||||
{
|
||||
string_format fmt = string_format::basic;
|
||||
bool start_with_newline = false;
|
||||
};
|
||||
|
||||
bool operator==(const string_format_info&, const string_format_info&) noexcept;
|
||||
bool operator!=(const string_format_info&, const string_format_info&) noexcept;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// datetime
|
||||
|
||||
enum class datetime_delimiter_kind : std::uint8_t
|
||||
{
|
||||
upper_T = 0,
|
||||
lower_t = 1,
|
||||
space = 2,
|
||||
};
|
||||
std::ostream& operator<<(std::ostream& os, const datetime_delimiter_kind d);
|
||||
std::string to_string(const datetime_delimiter_kind);
|
||||
|
||||
struct offset_datetime_format_info
|
||||
{
|
||||
datetime_delimiter_kind delimiter = datetime_delimiter_kind::upper_T;
|
||||
bool has_seconds = true;
|
||||
std::size_t subsecond_precision = 6; // [us]
|
||||
};
|
||||
|
||||
bool operator==(const offset_datetime_format_info&, const offset_datetime_format_info&) noexcept;
|
||||
bool operator!=(const offset_datetime_format_info&, const offset_datetime_format_info&) noexcept;
|
||||
|
||||
struct local_datetime_format_info
|
||||
{
|
||||
datetime_delimiter_kind delimiter = datetime_delimiter_kind::upper_T;
|
||||
bool has_seconds = true;
|
||||
std::size_t subsecond_precision = 6; // [us]
|
||||
};
|
||||
|
||||
bool operator==(const local_datetime_format_info&, const local_datetime_format_info&) noexcept;
|
||||
bool operator!=(const local_datetime_format_info&, const local_datetime_format_info&) noexcept;
|
||||
|
||||
struct local_date_format_info
|
||||
{
|
||||
// nothing, for now
|
||||
};
|
||||
|
||||
bool operator==(const local_date_format_info&, const local_date_format_info&) noexcept;
|
||||
bool operator!=(const local_date_format_info&, const local_date_format_info&) noexcept;
|
||||
|
||||
struct local_time_format_info
|
||||
{
|
||||
bool has_seconds = true;
|
||||
std::size_t subsecond_precision = 6; // [us]
|
||||
};
|
||||
|
||||
bool operator==(const local_time_format_info&, const local_time_format_info&) noexcept;
|
||||
bool operator!=(const local_time_format_info&, const local_time_format_info&) noexcept;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// array
|
||||
|
||||
enum class array_format : std::uint8_t
|
||||
{
|
||||
default_format = 0,
|
||||
oneline = 1,
|
||||
multiline = 2,
|
||||
array_of_tables = 3 // [[format.in.this.way]]
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const array_format f);
|
||||
std::string to_string(const array_format);
|
||||
|
||||
struct array_format_info
|
||||
{
|
||||
array_format fmt = array_format::default_format;
|
||||
indent_char indent_type = indent_char::space;
|
||||
std::int32_t body_indent = 4; // indent in case of multiline
|
||||
std::int32_t closing_indent = 0; // indent of `]`
|
||||
};
|
||||
|
||||
bool operator==(const array_format_info&, const array_format_info&) noexcept;
|
||||
bool operator!=(const array_format_info&, const array_format_info&) noexcept;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// table
|
||||
|
||||
enum class table_format : std::uint8_t
|
||||
{
|
||||
multiline = 0, // [foo] \n bar = "baz"
|
||||
oneline = 1, // foo = {bar = "baz"}
|
||||
dotted = 2, // foo.bar = "baz"
|
||||
multiline_oneline = 3, // foo = { \n bar = "baz" \n }
|
||||
implicit = 4 // [x] defined by [x.y.z]. skip in serializer.
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const table_format f);
|
||||
std::string to_string(const table_format);
|
||||
|
||||
struct table_format_info
|
||||
{
|
||||
table_format fmt = table_format::multiline;
|
||||
indent_char indent_type = indent_char::space;
|
||||
std::int32_t body_indent = 0; // indent of values
|
||||
std::int32_t name_indent = 0; // indent of [table]
|
||||
std::int32_t closing_indent = 0; // in case of {inline-table}
|
||||
};
|
||||
|
||||
bool operator==(const table_format_info&, const table_format_info&) noexcept;
|
||||
bool operator!=(const table_format_info&, const table_format_info&) noexcept;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// wrapper
|
||||
|
||||
namespace detail
|
||||
{
|
||||
template<typename T, typename F>
|
||||
struct value_with_format
|
||||
{
|
||||
using value_type = T;
|
||||
using format_type = F;
|
||||
|
||||
value_with_format() = default;
|
||||
~value_with_format() = default;
|
||||
value_with_format(const value_with_format&) = default;
|
||||
value_with_format(value_with_format&&) = default;
|
||||
value_with_format& operator=(const value_with_format&) = default;
|
||||
value_with_format& operator=(value_with_format&&) = default;
|
||||
|
||||
value_with_format(value_type v, format_type f)
|
||||
: value{std::move(v)}, format{std::move(f)}
|
||||
{}
|
||||
|
||||
template<typename U>
|
||||
value_with_format(value_with_format<U, format_type> other)
|
||||
: value{std::move(other.value)}, format{std::move(other.format)}
|
||||
{}
|
||||
|
||||
value_type value;
|
||||
format_type format;
|
||||
};
|
||||
} // detail
|
||||
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // namespace toml
|
||||
#endif // TOML11_FORMAT_FWD_HPP
|
||||
36
include/toml11/fwd/literal_fwd.hpp
Normal file
36
include/toml11/fwd/literal_fwd.hpp
Normal file
@@ -0,0 +1,36 @@
|
||||
#ifndef TOML11_LITERAL_FWD_HPP
|
||||
#define TOML11_LITERAL_FWD_HPP
|
||||
|
||||
#include "../location.hpp"
|
||||
#include "../types.hpp"
|
||||
#include "../version.hpp" // IWYU pragma: keep for TOML11_HAS_CHAR8_T
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
|
||||
namespace detail
|
||||
{
|
||||
// implementation
|
||||
::toml::value literal_internal_impl(location loc);
|
||||
} // detail
|
||||
|
||||
inline namespace literals
|
||||
{
|
||||
inline namespace toml_literals
|
||||
{
|
||||
|
||||
::toml::value operator""_toml(const char* str, std::size_t len);
|
||||
|
||||
#if defined(TOML11_HAS_CHAR8_T)
|
||||
// value of u8"" literal has been changed from char to char8_t and char8_t is
|
||||
// NOT compatible to char
|
||||
::toml::value operator"" _toml(const char8_t* str, std::size_t len);
|
||||
#endif
|
||||
|
||||
} // toml_literals
|
||||
} // literals
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
#endif // TOML11_LITERAL_FWD_HPP
|
||||
153
include/toml11/fwd/location_fwd.hpp
Normal file
153
include/toml11/fwd/location_fwd.hpp
Normal file
@@ -0,0 +1,153 @@
|
||||
#ifndef TOML11_LOCATION_FWD_HPP
|
||||
#define TOML11_LOCATION_FWD_HPP
|
||||
|
||||
#include "../result.hpp"
|
||||
#include "../version.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
|
||||
class region; // fwd decl
|
||||
|
||||
//
|
||||
// To represent where we are reading in the parse functions.
|
||||
// Since it "points" somewhere in the input stream, the length is always 1.
|
||||
//
|
||||
class location
|
||||
{
|
||||
public:
|
||||
|
||||
using char_type = unsigned char; // must be unsigned
|
||||
using container_type = std::vector<char_type>;
|
||||
using difference_type = typename container_type::difference_type; // to suppress sign-conversion warning
|
||||
using source_ptr = std::shared_ptr<const container_type>;
|
||||
|
||||
public:
|
||||
|
||||
location(source_ptr src, std::string src_name)
|
||||
: source_(std::move(src)), source_name_(std::move(src_name)),
|
||||
location_(0), line_number_(1), column_number_(1)
|
||||
{}
|
||||
|
||||
location(const location&) = default;
|
||||
location(location&&) = default;
|
||||
location& operator=(const location&) = default;
|
||||
location& operator=(location&&) = default;
|
||||
~location() = default;
|
||||
|
||||
void advance(std::size_t n = 1) noexcept;
|
||||
void retrace() noexcept;
|
||||
|
||||
bool is_ok() const noexcept { return static_cast<bool>(this->source_); }
|
||||
|
||||
bool eof() const noexcept;
|
||||
char_type current() const;
|
||||
|
||||
char_type peek();
|
||||
|
||||
std::size_t get_location() const noexcept
|
||||
{
|
||||
return this->location_;
|
||||
}
|
||||
|
||||
std::size_t line_number() const noexcept
|
||||
{
|
||||
return this->line_number_;
|
||||
}
|
||||
std::size_t column_number() const noexcept
|
||||
{
|
||||
return this->column_number_;
|
||||
}
|
||||
std::string get_line() const;
|
||||
|
||||
source_ptr const& source() const noexcept {return this->source_;}
|
||||
std::string const& source_name() const noexcept {return this->source_name_;}
|
||||
|
||||
private:
|
||||
|
||||
void advance_impl(const std::size_t n);
|
||||
void retrace_impl();
|
||||
std::size_t calc_column_number() const noexcept;
|
||||
|
||||
private:
|
||||
|
||||
friend region;
|
||||
|
||||
private:
|
||||
|
||||
source_ptr source_;
|
||||
std::string source_name_;
|
||||
std::size_t location_; // std::vector<>::difference_type is signed
|
||||
std::size_t line_number_;
|
||||
std::size_t column_number_;
|
||||
};
|
||||
|
||||
bool operator==(const location& lhs, const location& rhs) noexcept;
|
||||
bool operator!=(const location& lhs, const location& rhs);
|
||||
|
||||
location prev(const location& loc);
|
||||
location next(const location& loc);
|
||||
location make_temporary_location(const std::string& str) noexcept;
|
||||
|
||||
template<typename F>
|
||||
result<location, none_t>
|
||||
find_if(const location& first, const location& last, const F& func) noexcept
|
||||
{
|
||||
if(first.source() != last.source()) { return err(); }
|
||||
if(first.get_location() >= last.get_location()) { return err(); }
|
||||
|
||||
auto loc = first;
|
||||
while(loc.get_location() != last.get_location())
|
||||
{
|
||||
if(func(loc.current()))
|
||||
{
|
||||
return ok(loc);
|
||||
}
|
||||
loc.advance();
|
||||
}
|
||||
return err();
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
result<location, none_t>
|
||||
rfind_if(location first, const location& last, const F& func)
|
||||
{
|
||||
if(first.source() != last.source()) { return err(); }
|
||||
if(first.get_location() >= last.get_location()) { return err(); }
|
||||
|
||||
auto loc = last;
|
||||
while(loc.get_location() != first.get_location())
|
||||
{
|
||||
if(func(loc.current()))
|
||||
{
|
||||
return ok(loc);
|
||||
}
|
||||
loc.retrace();
|
||||
}
|
||||
if(func(first.current()))
|
||||
{
|
||||
return ok(first);
|
||||
}
|
||||
return err();
|
||||
}
|
||||
|
||||
result<location, none_t> find(const location& first, const location& last,
|
||||
const location::char_type val);
|
||||
result<location, none_t> rfind(const location& first, const location& last,
|
||||
const location::char_type val);
|
||||
|
||||
std::size_t count(const location& first, const location& last,
|
||||
const location::char_type& c);
|
||||
|
||||
} // detail
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
#endif // TOML11_LOCATION_FWD_HPP
|
||||
114
include/toml11/fwd/region_fwd.hpp
Normal file
114
include/toml11/fwd/region_fwd.hpp
Normal file
@@ -0,0 +1,114 @@
|
||||
#ifndef TOML11_REGION_FWD_HPP
|
||||
#define TOML11_REGION_FWD_HPP
|
||||
|
||||
#include "../location.hpp"
|
||||
#include "../version.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
|
||||
//
|
||||
// To represent where is a toml::value defined, or where does an error occur.
|
||||
// Stored in toml::value. source_location will be constructed based on this.
|
||||
//
|
||||
class region
|
||||
{
|
||||
public:
|
||||
|
||||
using char_type = location::char_type;
|
||||
using container_type = location::container_type;
|
||||
using difference_type = location::difference_type;
|
||||
using source_ptr = location::source_ptr;
|
||||
|
||||
using iterator = typename container_type::iterator;
|
||||
using const_iterator = typename container_type::const_iterator;
|
||||
|
||||
public:
|
||||
|
||||
// a value that is constructed manually does not have input stream info
|
||||
region()
|
||||
: source_(nullptr), source_name_(""), length_(0),
|
||||
first_(0), first_line_(0), first_column_(0), last_(0), last_line_(0),
|
||||
last_column_(0)
|
||||
{}
|
||||
|
||||
// a value defined in [first, last).
|
||||
// Those source must be the same. Instread, `region` does not make sense.
|
||||
region(const location& first, const location& last);
|
||||
|
||||
// shorthand of [loc, loc+1)
|
||||
explicit region(const location& loc);
|
||||
|
||||
~region() = default;
|
||||
region(const region&) = default;
|
||||
region(region&&) = default;
|
||||
region& operator=(const region&) = default;
|
||||
region& operator=(region&&) = default;
|
||||
|
||||
bool is_ok() const noexcept { return static_cast<bool>(this->source_); }
|
||||
|
||||
operator bool() const noexcept { return this->is_ok(); }
|
||||
|
||||
std::size_t length() const noexcept {return this->length_;}
|
||||
|
||||
std::size_t first_line_number() const noexcept
|
||||
{
|
||||
return this->first_line_;
|
||||
}
|
||||
std::size_t first_column_number() const noexcept
|
||||
{
|
||||
return this->first_column_;
|
||||
}
|
||||
std::size_t last_line_number() const noexcept
|
||||
{
|
||||
return this->last_line_;
|
||||
}
|
||||
std::size_t last_column_number() const noexcept
|
||||
{
|
||||
return this->last_column_;
|
||||
}
|
||||
|
||||
char_type at(std::size_t i) const;
|
||||
|
||||
const_iterator begin() const noexcept;
|
||||
const_iterator end() const noexcept;
|
||||
const_iterator cbegin() const noexcept;
|
||||
const_iterator cend() const noexcept;
|
||||
|
||||
std::string as_string() const;
|
||||
std::vector<std::pair<std::string, std::size_t>> as_lines() const;
|
||||
|
||||
source_ptr const& source() const noexcept {return this->source_;}
|
||||
std::string const& source_name() const noexcept {return this->source_name_;}
|
||||
|
||||
private:
|
||||
|
||||
std::pair<std::string, std::size_t>
|
||||
take_line(const_iterator begin, const_iterator end) const;
|
||||
|
||||
private:
|
||||
|
||||
source_ptr source_;
|
||||
std::string source_name_;
|
||||
std::size_t length_;
|
||||
std::size_t first_;
|
||||
std::size_t first_line_;
|
||||
std::size_t first_column_;
|
||||
std::size_t last_;
|
||||
std::size_t last_line_;
|
||||
std::size_t last_column_;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // namespace toml
|
||||
#endif // TOML11_REGION_FWD_HPP
|
||||
370
include/toml11/fwd/scanner_fwd.hpp
Normal file
370
include/toml11/fwd/scanner_fwd.hpp
Normal file
@@ -0,0 +1,370 @@
|
||||
#ifndef TOML11_SCANNER_FWD_HPP
|
||||
#define TOML11_SCANNER_FWD_HPP
|
||||
|
||||
#include "../region.hpp"
|
||||
#include "../version.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <cctype>
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
|
||||
class scanner_base
|
||||
{
|
||||
public:
|
||||
virtual ~scanner_base() = default;
|
||||
virtual region scan(location& loc) const = 0;
|
||||
virtual scanner_base* clone() const = 0;
|
||||
|
||||
// returns expected character or set of characters or literal.
|
||||
// to show the error location, it changes loc (in `sequence`, especially).
|
||||
virtual std::string expected_chars(location& loc) const = 0;
|
||||
virtual std::string name() const = 0;
|
||||
};
|
||||
|
||||
// make `scanner*` copyable
|
||||
struct scanner_storage
|
||||
{
|
||||
template<typename Scanner, cxx::enable_if_t<
|
||||
std::is_base_of<scanner_base, cxx::remove_cvref_t<Scanner>>::value,
|
||||
std::nullptr_t> = nullptr>
|
||||
explicit scanner_storage(Scanner&& s)
|
||||
: scanner_(cxx::make_unique<cxx::remove_cvref_t<Scanner>>(std::forward<Scanner>(s)))
|
||||
{}
|
||||
~scanner_storage() = default;
|
||||
|
||||
scanner_storage(const scanner_storage& other);
|
||||
scanner_storage& operator=(const scanner_storage& other);
|
||||
scanner_storage(scanner_storage&&) = default;
|
||||
scanner_storage& operator=(scanner_storage&&) = default;
|
||||
|
||||
bool is_ok() const noexcept {return static_cast<bool>(scanner_);}
|
||||
|
||||
region scan(location& loc) const;
|
||||
|
||||
std::string expected_chars(location& loc) const;
|
||||
|
||||
scanner_base& get() const noexcept;
|
||||
|
||||
std::string name() const;
|
||||
|
||||
private:
|
||||
|
||||
std::unique_ptr<scanner_base> scanner_;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
class character final : public scanner_base
|
||||
{
|
||||
public:
|
||||
|
||||
using char_type = location::char_type;
|
||||
|
||||
public:
|
||||
|
||||
explicit character(const char_type c) noexcept
|
||||
: value_(c)
|
||||
{}
|
||||
~character() override = default;
|
||||
|
||||
region scan(location& loc) const override;
|
||||
|
||||
std::string expected_chars(location&) const override;
|
||||
|
||||
scanner_base* clone() const override;
|
||||
|
||||
std::string name() const override;
|
||||
|
||||
private:
|
||||
char_type value_;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
class character_either final : public scanner_base
|
||||
{
|
||||
public:
|
||||
|
||||
using char_type = location::char_type;
|
||||
|
||||
public:
|
||||
|
||||
template<std::size_t N>
|
||||
explicit character_either(const char (&cs)[N]) noexcept
|
||||
: value_(cs), size_(N-1) // remove null character at the end
|
||||
{}
|
||||
~character_either() override = default;
|
||||
|
||||
region scan(location& loc) const override;
|
||||
|
||||
std::string expected_chars(location&) const override;
|
||||
|
||||
scanner_base* clone() const override;
|
||||
|
||||
std::string name() const override;
|
||||
|
||||
private:
|
||||
const char* value_;
|
||||
std::size_t size_;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
class character_in_range final : public scanner_base
|
||||
{
|
||||
public:
|
||||
|
||||
using char_type = location::char_type;
|
||||
|
||||
public:
|
||||
|
||||
explicit character_in_range(const char_type from, const char_type to) noexcept
|
||||
: from_(from), to_(to)
|
||||
{}
|
||||
~character_in_range() override = default;
|
||||
|
||||
region scan(location& loc) const override;
|
||||
|
||||
std::string expected_chars(location&) const override;
|
||||
|
||||
scanner_base* clone() const override;
|
||||
|
||||
std::string name() const override;
|
||||
|
||||
private:
|
||||
char_type from_;
|
||||
char_type to_;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
class literal final : public scanner_base
|
||||
{
|
||||
public:
|
||||
|
||||
using char_type = location::char_type;
|
||||
|
||||
public:
|
||||
|
||||
template<std::size_t N>
|
||||
explicit literal(const char (&cs)[N]) noexcept
|
||||
: value_(cs), size_(N-1) // remove null character at the end
|
||||
{}
|
||||
~literal() override = default;
|
||||
|
||||
region scan(location& loc) const override;
|
||||
|
||||
std::string expected_chars(location&) const override;
|
||||
|
||||
scanner_base* clone() const override;
|
||||
|
||||
std::string name() const override;
|
||||
|
||||
private:
|
||||
const char* value_;
|
||||
std::size_t size_;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
class sequence final: public scanner_base
|
||||
{
|
||||
public:
|
||||
using char_type = location::char_type;
|
||||
|
||||
public:
|
||||
|
||||
template<typename ... Ts>
|
||||
explicit sequence(Ts&& ... args)
|
||||
{
|
||||
push_back_all(std::forward<Ts>(args)...);
|
||||
}
|
||||
sequence(const sequence&) = default;
|
||||
sequence(sequence&&) = default;
|
||||
sequence& operator=(const sequence&) = default;
|
||||
sequence& operator=(sequence&&) = default;
|
||||
~sequence() override = default;
|
||||
|
||||
region scan(location& loc) const override;
|
||||
|
||||
std::string expected_chars(location& loc) const override;
|
||||
|
||||
scanner_base* clone() const override;
|
||||
|
||||
std::string name() const override;
|
||||
|
||||
private:
|
||||
|
||||
void push_back_all()
|
||||
{
|
||||
return;
|
||||
}
|
||||
template<typename T, typename ... Ts>
|
||||
void push_back_all(T&& head, Ts&& ... args)
|
||||
{
|
||||
others_.emplace_back(std::forward<T>(head));
|
||||
push_back_all(std::forward<Ts>(args)...);
|
||||
return;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<scanner_storage> others_;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
class either final: public scanner_base
|
||||
{
|
||||
public:
|
||||
using char_type = location::char_type;
|
||||
|
||||
public:
|
||||
|
||||
template<typename ... Ts>
|
||||
explicit either(Ts&& ... args)
|
||||
{
|
||||
push_back_all(std::forward<Ts>(args)...);
|
||||
}
|
||||
either(const either&) = default;
|
||||
either(either&&) = default;
|
||||
either& operator=(const either&) = default;
|
||||
either& operator=(either&&) = default;
|
||||
~either() override = default;
|
||||
|
||||
region scan(location& loc) const override;
|
||||
|
||||
std::string expected_chars(location& loc) const override;
|
||||
|
||||
scanner_base* clone() const override;
|
||||
|
||||
std::string name() const override;
|
||||
|
||||
private:
|
||||
|
||||
void push_back_all()
|
||||
{
|
||||
return;
|
||||
}
|
||||
template<typename T, typename ... Ts>
|
||||
void push_back_all(T&& head, Ts&& ... args)
|
||||
{
|
||||
others_.emplace_back(std::forward<T>(head));
|
||||
push_back_all(std::forward<Ts>(args)...);
|
||||
return;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<scanner_storage> others_;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
class repeat_exact final: public scanner_base
|
||||
{
|
||||
public:
|
||||
using char_type = location::char_type;
|
||||
|
||||
public:
|
||||
|
||||
template<typename Scanner>
|
||||
repeat_exact(const std::size_t length, Scanner&& other)
|
||||
: length_(length), other_(std::forward<Scanner>(other))
|
||||
{}
|
||||
repeat_exact(const repeat_exact&) = default;
|
||||
repeat_exact(repeat_exact&&) = default;
|
||||
repeat_exact& operator=(const repeat_exact&) = default;
|
||||
repeat_exact& operator=(repeat_exact&&) = default;
|
||||
~repeat_exact() override = default;
|
||||
|
||||
region scan(location& loc) const override;
|
||||
|
||||
std::string expected_chars(location& loc) const override;
|
||||
|
||||
scanner_base* clone() const override;
|
||||
|
||||
std::string name() const override;
|
||||
|
||||
private:
|
||||
std::size_t length_;
|
||||
scanner_storage other_;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
class repeat_at_least final: public scanner_base
|
||||
{
|
||||
public:
|
||||
using char_type = location::char_type;
|
||||
|
||||
public:
|
||||
|
||||
template<typename Scanner>
|
||||
repeat_at_least(const std::size_t length, Scanner&& s)
|
||||
: length_(length), other_(std::forward<Scanner>(s))
|
||||
{}
|
||||
repeat_at_least(const repeat_at_least&) = default;
|
||||
repeat_at_least(repeat_at_least&&) = default;
|
||||
repeat_at_least& operator=(const repeat_at_least&) = default;
|
||||
repeat_at_least& operator=(repeat_at_least&&) = default;
|
||||
~repeat_at_least() override = default;
|
||||
|
||||
region scan(location& loc) const override;
|
||||
|
||||
std::string expected_chars(location& loc) const override;
|
||||
|
||||
scanner_base* clone() const override;
|
||||
|
||||
std::string name() const override;
|
||||
|
||||
private:
|
||||
std::size_t length_;
|
||||
scanner_storage other_;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
class maybe final: public scanner_base
|
||||
{
|
||||
public:
|
||||
using char_type = location::char_type;
|
||||
|
||||
public:
|
||||
|
||||
template<typename Scanner>
|
||||
explicit maybe(Scanner&& s)
|
||||
: other_(std::forward<Scanner>(s))
|
||||
{}
|
||||
maybe(const maybe&) = default;
|
||||
maybe(maybe&&) = default;
|
||||
maybe& operator=(const maybe&) = default;
|
||||
maybe& operator=(maybe&&) = default;
|
||||
~maybe() override = default;
|
||||
|
||||
region scan(location& loc) const override;
|
||||
|
||||
std::string expected_chars(location&) const override;
|
||||
|
||||
scanner_base* clone() const override;
|
||||
|
||||
std::string name() const override;
|
||||
|
||||
private:
|
||||
scanner_storage other_;
|
||||
};
|
||||
|
||||
} // detail
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
#endif // TOML11_SCANNER_FWD_HPP
|
||||
152
include/toml11/fwd/source_location_fwd.hpp
Normal file
152
include/toml11/fwd/source_location_fwd.hpp
Normal file
@@ -0,0 +1,152 @@
|
||||
#ifndef TOML11_SOURCE_LOCATION_FWD_HPP
|
||||
#define TOML11_SOURCE_LOCATION_FWD_HPP
|
||||
|
||||
#include "../region.hpp"
|
||||
#include "../version.hpp"
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
|
||||
//
|
||||
// A struct to contain location in a toml file.
|
||||
//
|
||||
// To reduce memory consumption, it omits unrelated parts of long lines. like:
|
||||
//
|
||||
// 1. one long line, short region
|
||||
// ```
|
||||
// |
|
||||
// 1 | ... "foo", "bar", baz, "qux", "foobar", ...
|
||||
// | ^-- unknown value
|
||||
// ```
|
||||
// 2. long region
|
||||
// ```
|
||||
// |
|
||||
// 1 | array = [ "foo", ... "bar" ]
|
||||
// | ^^^^^^^^^^^^^^^^^^^^- in this array
|
||||
// ```
|
||||
// 3. many lines
|
||||
// |
|
||||
// 1 | array = [ "foo",
|
||||
// | ^^^^^^^^
|
||||
// | ...
|
||||
// | ^^^
|
||||
// |
|
||||
// 10 | , "bar"]
|
||||
// | ^^^^^^^^- in this array
|
||||
// ```
|
||||
//
|
||||
struct source_location
|
||||
{
|
||||
public:
|
||||
|
||||
explicit source_location(const detail::region& r);
|
||||
~source_location() = default;
|
||||
source_location(source_location const&) = default;
|
||||
source_location(source_location &&) = default;
|
||||
source_location& operator=(source_location const&) = default;
|
||||
source_location& operator=(source_location &&) = default;
|
||||
|
||||
bool is_ok() const noexcept {return this->is_ok_;}
|
||||
std::size_t length() const noexcept {return this->length_;}
|
||||
|
||||
std::size_t first_line_number() const noexcept {return this->first_line_;}
|
||||
std::size_t first_column_number() const noexcept {return this->first_column_;}
|
||||
std::size_t last_line_number() const noexcept {return this->last_line_;}
|
||||
std::size_t last_column_number() const noexcept {return this->last_column_;}
|
||||
|
||||
std::string const& file_name() const noexcept {return this->file_name_;}
|
||||
|
||||
std::size_t num_lines() const noexcept {return this->line_str_.size();}
|
||||
|
||||
std::string const& first_line() const;
|
||||
std::string const& last_line() const;
|
||||
|
||||
std::vector<std::string> const& lines() const noexcept {return line_str_;}
|
||||
|
||||
// for internal use
|
||||
std::size_t first_column_offset() const noexcept {return this->first_offset_;}
|
||||
std::size_t last_column_offset() const noexcept {return this->last_offset_;}
|
||||
|
||||
private:
|
||||
|
||||
bool is_ok_;
|
||||
std::size_t first_line_;
|
||||
std::size_t first_column_; // column num in the actual file
|
||||
std::size_t first_offset_; // column num in the shown line
|
||||
std::size_t last_line_;
|
||||
std::size_t last_column_; // column num in the actual file
|
||||
std::size_t last_offset_; // column num in the shown line
|
||||
std::size_t length_;
|
||||
std::string file_name_;
|
||||
std::vector<std::string> line_str_;
|
||||
};
|
||||
|
||||
namespace detail
|
||||
{
|
||||
|
||||
std::size_t integer_width_base10(std::size_t i) noexcept;
|
||||
|
||||
inline std::size_t line_width() noexcept {return 0;}
|
||||
|
||||
template<typename ... Ts>
|
||||
std::size_t line_width(const source_location& loc, const std::string& /*msg*/,
|
||||
const Ts& ... tail) noexcept
|
||||
{
|
||||
return (std::max)(
|
||||
integer_width_base10(loc.last_line_number()), line_width(tail...));
|
||||
}
|
||||
|
||||
std::ostringstream&
|
||||
format_filename(std::ostringstream& oss, const source_location& loc);
|
||||
|
||||
std::ostringstream&
|
||||
format_empty_line(std::ostringstream& oss, const std::size_t lnw);
|
||||
|
||||
std::ostringstream& format_line(std::ostringstream& oss,
|
||||
const std::size_t lnw, const std::size_t linenum, const std::string& line);
|
||||
|
||||
std::ostringstream& format_underline(std::ostringstream& oss,
|
||||
const std::size_t lnw, const std::size_t col, const std::size_t len,
|
||||
const std::string& msg);
|
||||
|
||||
std::string format_location_impl(const std::size_t lnw,
|
||||
const std::string& prev_fname,
|
||||
const source_location& loc, const std::string& msg);
|
||||
|
||||
inline std::string format_location_rec(const std::size_t, const std::string&)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
template<typename ... Ts>
|
||||
std::string format_location_rec(const std::size_t lnw,
|
||||
const std::string& prev_fname,
|
||||
const source_location& loc, const std::string& msg,
|
||||
const Ts& ... tail)
|
||||
{
|
||||
return format_location_impl(lnw, prev_fname, loc, msg) +
|
||||
format_location_rec(lnw, loc.file_name(), tail...);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// format a location info without title
|
||||
template<typename ... Ts>
|
||||
std::string format_location(
|
||||
const source_location& loc, const std::string& msg, const Ts& ... tail)
|
||||
{
|
||||
const auto lnw = detail::line_width(loc, msg, tail...);
|
||||
|
||||
const std::string f(""); // at the 1st iteration, no prev_filename is given
|
||||
return detail::format_location_rec(lnw, f, loc, msg, tail...);
|
||||
}
|
||||
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
#endif // TOML11_SOURCE_LOCATION_FWD_HPP
|
||||
419
include/toml11/fwd/syntax_fwd.hpp
Normal file
419
include/toml11/fwd/syntax_fwd.hpp
Normal file
@@ -0,0 +1,419 @@
|
||||
#ifndef TOML11_SYNTAX_FWD_HPP
|
||||
#define TOML11_SYNTAX_FWD_HPP
|
||||
|
||||
#include "../scanner.hpp"
|
||||
#include "../spec.hpp"
|
||||
#include "../version.hpp"
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
namespace syntax
|
||||
{
|
||||
|
||||
using char_type = location::char_type;
|
||||
|
||||
// ===========================================================================
|
||||
// UTF-8
|
||||
|
||||
// avoid redundant representation and out-of-unicode sequence
|
||||
|
||||
character_in_range const& utf8_1byte (const spec&);
|
||||
sequence const& utf8_2bytes(const spec&);
|
||||
sequence const& utf8_3bytes(const spec&);
|
||||
sequence const& utf8_4bytes(const spec&);
|
||||
|
||||
class non_ascii final : public scanner_base
|
||||
{
|
||||
public:
|
||||
|
||||
using char_type = location::char_type;
|
||||
|
||||
public:
|
||||
|
||||
explicit non_ascii(const spec& s) noexcept
|
||||
: utf8_2B_(utf8_2bytes(s)),
|
||||
utf8_3B_(utf8_3bytes(s)),
|
||||
utf8_4B_(utf8_4bytes(s))
|
||||
{}
|
||||
~non_ascii() override = default;
|
||||
|
||||
region scan(location& loc) const override
|
||||
{
|
||||
{
|
||||
const auto reg = utf8_2B_.scan(loc);
|
||||
if(reg.is_ok()) {return reg;}
|
||||
}
|
||||
{
|
||||
const auto reg = utf8_3B_.scan(loc);
|
||||
if(reg.is_ok()) {return reg;}
|
||||
}
|
||||
{
|
||||
const auto reg = utf8_4B_.scan(loc);
|
||||
if(reg.is_ok()) {return reg;}
|
||||
}
|
||||
return region{};
|
||||
}
|
||||
|
||||
std::string expected_chars(location&) const override
|
||||
{
|
||||
return "non-ascii utf-8 bytes";
|
||||
}
|
||||
|
||||
scanner_base* clone() const override
|
||||
{
|
||||
return new non_ascii(*this);
|
||||
}
|
||||
|
||||
std::string name() const override
|
||||
{
|
||||
return "non_ascii";
|
||||
}
|
||||
|
||||
private:
|
||||
sequence utf8_2B_;
|
||||
sequence utf8_3B_;
|
||||
sequence utf8_4B_;
|
||||
};
|
||||
|
||||
// ===========================================================================
|
||||
// Whitespace
|
||||
|
||||
character_either const& wschar(const spec&);
|
||||
|
||||
repeat_at_least const& ws(const spec& s);
|
||||
|
||||
// ===========================================================================
|
||||
// Newline
|
||||
|
||||
either const& newline(const spec&);
|
||||
|
||||
// ===========================================================================
|
||||
// Comments
|
||||
|
||||
either const& allowed_comment_char(const spec& s);
|
||||
|
||||
// XXX Note that it does not take newline
|
||||
sequence const& comment(const spec& s);
|
||||
|
||||
// ===========================================================================
|
||||
// Boolean
|
||||
|
||||
either const& boolean(const spec&);
|
||||
|
||||
// ===========================================================================
|
||||
// Integer
|
||||
|
||||
class digit final : public scanner_base
|
||||
{
|
||||
public:
|
||||
|
||||
using char_type = location::char_type;
|
||||
|
||||
public:
|
||||
|
||||
explicit digit(const spec&) noexcept
|
||||
: scanner_(char_type('0'), char_type('9'))
|
||||
{}
|
||||
|
||||
|
||||
~digit() override = default;
|
||||
|
||||
region scan(location& loc) const override
|
||||
{
|
||||
return scanner_.scan(loc);
|
||||
}
|
||||
|
||||
std::string expected_chars(location&) const override
|
||||
{
|
||||
return "digit [0-9]";
|
||||
}
|
||||
|
||||
scanner_base* clone() const override
|
||||
{
|
||||
return new digit(*this);
|
||||
}
|
||||
|
||||
std::string name() const override
|
||||
{
|
||||
return "digit";
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
character_in_range scanner_;
|
||||
};
|
||||
|
||||
class alpha final : public scanner_base
|
||||
{
|
||||
public:
|
||||
|
||||
using char_type = location::char_type;
|
||||
|
||||
public:
|
||||
|
||||
explicit alpha(const spec&) noexcept
|
||||
: lowercase_(char_type('a'), char_type('z')),
|
||||
uppercase_(char_type('A'), char_type('Z'))
|
||||
{}
|
||||
~alpha() override = default;
|
||||
|
||||
region scan(location& loc) const override
|
||||
{
|
||||
{
|
||||
const auto reg = lowercase_.scan(loc);
|
||||
if(reg.is_ok()) {return reg;}
|
||||
}
|
||||
{
|
||||
const auto reg = uppercase_.scan(loc);
|
||||
if(reg.is_ok()) {return reg;}
|
||||
}
|
||||
return region{};
|
||||
}
|
||||
|
||||
std::string expected_chars(location&) const override
|
||||
{
|
||||
return "alpha [a-zA-Z]";
|
||||
}
|
||||
|
||||
scanner_base* clone() const override
|
||||
{
|
||||
return new alpha(*this);
|
||||
}
|
||||
|
||||
std::string name() const override
|
||||
{
|
||||
return "alpha";
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
character_in_range lowercase_;
|
||||
character_in_range uppercase_;
|
||||
};
|
||||
|
||||
class hexdig final : public scanner_base
|
||||
{
|
||||
public:
|
||||
|
||||
using char_type = location::char_type;
|
||||
|
||||
public:
|
||||
|
||||
explicit hexdig(const spec& s) noexcept
|
||||
: digit_(s),
|
||||
lowercase_(char_type('a'), char_type('f')),
|
||||
uppercase_(char_type('A'), char_type('F'))
|
||||
{}
|
||||
~hexdig() override = default;
|
||||
|
||||
region scan(location& loc) const override
|
||||
{
|
||||
{
|
||||
const auto reg = digit_.scan(loc);
|
||||
if(reg.is_ok()) {return reg;}
|
||||
}
|
||||
{
|
||||
const auto reg = lowercase_.scan(loc);
|
||||
if(reg.is_ok()) {return reg;}
|
||||
}
|
||||
{
|
||||
const auto reg = uppercase_.scan(loc);
|
||||
if(reg.is_ok()) {return reg;}
|
||||
}
|
||||
return region{};
|
||||
}
|
||||
|
||||
std::string expected_chars(location&) const override
|
||||
{
|
||||
return "hex [0-9a-fA-F]";
|
||||
}
|
||||
|
||||
scanner_base* clone() const override
|
||||
{
|
||||
return new hexdig(*this);
|
||||
}
|
||||
|
||||
std::string name() const override
|
||||
{
|
||||
return "hexdig";
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
digit digit_;
|
||||
character_in_range lowercase_;
|
||||
character_in_range uppercase_;
|
||||
};
|
||||
|
||||
sequence const& num_suffix(const spec& s);
|
||||
|
||||
sequence const& dec_int(const spec& s);
|
||||
sequence const& hex_int(const spec& s);
|
||||
sequence const& oct_int(const spec&);
|
||||
sequence const& bin_int(const spec&);
|
||||
either const& integer(const spec& s);
|
||||
|
||||
// ===========================================================================
|
||||
// Floating
|
||||
|
||||
sequence const& zero_prefixable_int(const spec& s);
|
||||
sequence const& fractional_part(const spec& s);
|
||||
sequence const& exponent_part(const spec& s);
|
||||
sequence const& hex_floating(const spec& s);
|
||||
either const& floating(const spec& s);
|
||||
|
||||
// ===========================================================================
|
||||
// Datetime
|
||||
|
||||
sequence const& local_date(const spec& s);
|
||||
sequence const& local_time(const spec& s);
|
||||
either const& time_offset(const spec& s);
|
||||
sequence const& full_time(const spec& s);
|
||||
character_either const& time_delim(const spec&);
|
||||
sequence const& local_datetime(const spec& s);
|
||||
sequence const& offset_datetime(const spec& s);
|
||||
|
||||
// ===========================================================================
|
||||
// String
|
||||
|
||||
sequence const& escaped_x2(const spec& s);
|
||||
sequence const& escaped_u4(const spec& s);
|
||||
sequence const& escaped_U8(const spec& s);
|
||||
|
||||
sequence const& escaped (const spec& s);
|
||||
either const& basic_char (const spec& s);
|
||||
sequence const& basic_string(const spec& s);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// multiline string
|
||||
|
||||
sequence const& escaped_newline(const spec& s);
|
||||
sequence const& ml_basic_string(const spec& s);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// literal string
|
||||
|
||||
either const& literal_char(const spec& s);
|
||||
sequence const& literal_string(const spec& s);
|
||||
sequence const& ml_literal_string(const spec& s);
|
||||
either const& string(const spec& s);
|
||||
|
||||
// ===========================================================================
|
||||
// Keys
|
||||
|
||||
// to keep `expected_chars` simple
|
||||
class non_ascii_key_char final : public scanner_base
|
||||
{
|
||||
public:
|
||||
|
||||
using char_type = location::char_type;
|
||||
|
||||
private:
|
||||
|
||||
using in_range = character_in_range; // make definition short
|
||||
|
||||
public:
|
||||
|
||||
explicit non_ascii_key_char(const spec& s) noexcept;
|
||||
~non_ascii_key_char() override = default;
|
||||
|
||||
region scan(location& loc) const override;
|
||||
|
||||
std::string expected_chars(location&) const override
|
||||
{
|
||||
return "bare key non-ASCII script";
|
||||
}
|
||||
|
||||
scanner_base* clone() const override
|
||||
{
|
||||
return new non_ascii_key_char(*this);
|
||||
}
|
||||
|
||||
std::string name() const override
|
||||
{
|
||||
return "non-ASCII bare key";
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
std::uint32_t read_utf8(location& loc) const;
|
||||
};
|
||||
|
||||
|
||||
repeat_at_least const& unquoted_key(const spec& s);
|
||||
either const& quoted_key(const spec& s);
|
||||
either const& simple_key(const spec& s);
|
||||
sequence const& dot_sep(const spec& s);
|
||||
sequence const& dotted_key(const spec& s);
|
||||
|
||||
class key final : public scanner_base
|
||||
{
|
||||
public:
|
||||
|
||||
using char_type = location::char_type;
|
||||
|
||||
public:
|
||||
|
||||
explicit key(const spec& s) noexcept
|
||||
: dotted_(dotted_key(s)),
|
||||
simple_(simple_key(s))
|
||||
{}
|
||||
~key() override = default;
|
||||
|
||||
region scan(location& loc) const override
|
||||
{
|
||||
{
|
||||
const auto reg = dotted_.scan(loc);
|
||||
if(reg.is_ok()) {return reg;}
|
||||
}
|
||||
{
|
||||
const auto reg = simple_.scan(loc);
|
||||
if(reg.is_ok()) {return reg;}
|
||||
}
|
||||
return region{};
|
||||
}
|
||||
|
||||
std::string expected_chars(location&) const override
|
||||
{
|
||||
return "basic key([a-zA-Z0-9_-]) or quoted key(\" or ')";
|
||||
}
|
||||
|
||||
scanner_base* clone() const override
|
||||
{
|
||||
return new key(*this);
|
||||
}
|
||||
|
||||
std::string name() const override
|
||||
{
|
||||
return "key";
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
sequence dotted_;
|
||||
either simple_;
|
||||
};
|
||||
|
||||
sequence const& keyval_sep(const spec& s);
|
||||
|
||||
// ===========================================================================
|
||||
// Table key
|
||||
|
||||
sequence const& std_table(const spec& s);
|
||||
|
||||
sequence const& array_table(const spec& s);
|
||||
|
||||
// ===========================================================================
|
||||
// extension: null
|
||||
|
||||
literal const& null_value(const spec&);
|
||||
|
||||
} // namespace syntax
|
||||
} // namespace detail
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // namespace toml
|
||||
#endif // TOML11_SYNTAX_FWD_HPP
|
||||
121
include/toml11/fwd/value_t_fwd.hpp
Normal file
121
include/toml11/fwd/value_t_fwd.hpp
Normal file
@@ -0,0 +1,121 @@
|
||||
#ifndef TOML11_VALUE_T_FWD_HPP
|
||||
#define TOML11_VALUE_T_FWD_HPP
|
||||
|
||||
#include "../compat.hpp"
|
||||
#include "../format.hpp"
|
||||
#include "../version.hpp"
|
||||
|
||||
#include <iosfwd>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
|
||||
// forward decl
|
||||
template<typename TypeConfig>
|
||||
class basic_value;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// enum representing toml types
|
||||
|
||||
enum class value_t : std::uint8_t
|
||||
{
|
||||
empty = 0,
|
||||
boolean = 1,
|
||||
integer = 2,
|
||||
floating = 3,
|
||||
string = 4,
|
||||
offset_datetime = 5,
|
||||
local_datetime = 6,
|
||||
local_date = 7,
|
||||
local_time = 8,
|
||||
array = 9,
|
||||
table = 10
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, value_t t);
|
||||
std::string to_string(value_t t);
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// meta functions for internal use
|
||||
|
||||
namespace detail
|
||||
{
|
||||
|
||||
template<value_t V>
|
||||
using value_t_constant = std::integral_constant<value_t, V>;
|
||||
|
||||
template<typename T, typename Value>
|
||||
struct type_to_enum : value_t_constant<value_t::empty> {};
|
||||
|
||||
template<typename V> struct type_to_enum<typename V::boolean_type , V> : value_t_constant<value_t::boolean > {};
|
||||
template<typename V> struct type_to_enum<typename V::integer_type , V> : value_t_constant<value_t::integer > {};
|
||||
template<typename V> struct type_to_enum<typename V::floating_type , V> : value_t_constant<value_t::floating > {};
|
||||
template<typename V> struct type_to_enum<typename V::string_type , V> : value_t_constant<value_t::string > {};
|
||||
template<typename V> struct type_to_enum<typename V::offset_datetime_type, V> : value_t_constant<value_t::offset_datetime> {};
|
||||
template<typename V> struct type_to_enum<typename V::local_datetime_type , V> : value_t_constant<value_t::local_datetime > {};
|
||||
template<typename V> struct type_to_enum<typename V::local_date_type , V> : value_t_constant<value_t::local_date > {};
|
||||
template<typename V> struct type_to_enum<typename V::local_time_type , V> : value_t_constant<value_t::local_time > {};
|
||||
template<typename V> struct type_to_enum<typename V::array_type , V> : value_t_constant<value_t::array > {};
|
||||
template<typename V> struct type_to_enum<typename V::table_type , V> : value_t_constant<value_t::table > {};
|
||||
|
||||
template<value_t V, typename Value>
|
||||
struct enum_to_type { using type = void; };
|
||||
|
||||
template<typename V> struct enum_to_type<value_t::boolean , V> { using type = typename V::boolean_type ; };
|
||||
template<typename V> struct enum_to_type<value_t::integer , V> { using type = typename V::integer_type ; };
|
||||
template<typename V> struct enum_to_type<value_t::floating , V> { using type = typename V::floating_type ; };
|
||||
template<typename V> struct enum_to_type<value_t::string , V> { using type = typename V::string_type ; };
|
||||
template<typename V> struct enum_to_type<value_t::offset_datetime, V> { using type = typename V::offset_datetime_type; };
|
||||
template<typename V> struct enum_to_type<value_t::local_datetime , V> { using type = typename V::local_datetime_type ; };
|
||||
template<typename V> struct enum_to_type<value_t::local_date , V> { using type = typename V::local_date_type ; };
|
||||
template<typename V> struct enum_to_type<value_t::local_time , V> { using type = typename V::local_time_type ; };
|
||||
template<typename V> struct enum_to_type<value_t::array , V> { using type = typename V::array_type ; };
|
||||
template<typename V> struct enum_to_type<value_t::table , V> { using type = typename V::table_type ; };
|
||||
|
||||
template<value_t V, typename Value>
|
||||
using enum_to_type_t = typename enum_to_type<V, Value>::type;
|
||||
|
||||
template<value_t V>
|
||||
struct enum_to_fmt_type { using type = void; };
|
||||
|
||||
template<> struct enum_to_fmt_type<value_t::boolean > { using type = boolean_format_info ; };
|
||||
template<> struct enum_to_fmt_type<value_t::integer > { using type = integer_format_info ; };
|
||||
template<> struct enum_to_fmt_type<value_t::floating > { using type = floating_format_info ; };
|
||||
template<> struct enum_to_fmt_type<value_t::string > { using type = string_format_info ; };
|
||||
template<> struct enum_to_fmt_type<value_t::offset_datetime> { using type = offset_datetime_format_info; };
|
||||
template<> struct enum_to_fmt_type<value_t::local_datetime > { using type = local_datetime_format_info ; };
|
||||
template<> struct enum_to_fmt_type<value_t::local_date > { using type = local_date_format_info ; };
|
||||
template<> struct enum_to_fmt_type<value_t::local_time > { using type = local_time_format_info ; };
|
||||
template<> struct enum_to_fmt_type<value_t::array > { using type = array_format_info ; };
|
||||
template<> struct enum_to_fmt_type<value_t::table > { using type = table_format_info ; };
|
||||
|
||||
template<value_t V>
|
||||
using enum_to_fmt_type_t = typename enum_to_fmt_type<V>::type;
|
||||
|
||||
template<typename T, typename Value>
|
||||
struct is_exact_toml_type0 : cxx::disjunction<
|
||||
std::is_same<T, typename Value::boolean_type >,
|
||||
std::is_same<T, typename Value::integer_type >,
|
||||
std::is_same<T, typename Value::floating_type >,
|
||||
std::is_same<T, typename Value::string_type >,
|
||||
std::is_same<T, typename Value::offset_datetime_type>,
|
||||
std::is_same<T, typename Value::local_datetime_type >,
|
||||
std::is_same<T, typename Value::local_date_type >,
|
||||
std::is_same<T, typename Value::local_time_type >,
|
||||
std::is_same<T, typename Value::array_type >,
|
||||
std::is_same<T, typename Value::table_type >
|
||||
>{};
|
||||
template<typename T, typename V> struct is_exact_toml_type: is_exact_toml_type0<cxx::remove_cvref_t<T>, V> {};
|
||||
template<typename T, typename V> struct is_not_toml_type : cxx::negation<is_exact_toml_type<T, V>> {};
|
||||
|
||||
} // namespace detail
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // namespace toml
|
||||
#endif // TOML11_VALUE_T_FWD_HPP
|
||||
655
include/toml11/get.hpp
Normal file
655
include/toml11/get.hpp
Normal file
@@ -0,0 +1,655 @@
|
||||
#ifndef TOML11_GET_HPP
|
||||
#define TOML11_GET_HPP
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "from.hpp"
|
||||
#include "types.hpp"
|
||||
#include "value.hpp"
|
||||
#include "version.hpp"
|
||||
|
||||
#if defined(TOML11_HAS_STRING_VIEW)
|
||||
#include <string_view>
|
||||
#endif // string_view
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
|
||||
// ============================================================================
|
||||
// T is toml::value; identity transformation.
|
||||
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<std::is_same<T, basic_value<TC>>::value, T>&
|
||||
get(basic_value<TC>& v)
|
||||
{
|
||||
return v;
|
||||
}
|
||||
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<std::is_same<T, basic_value<TC>>::value, T> const&
|
||||
get(const basic_value<TC>& v)
|
||||
{
|
||||
return v;
|
||||
}
|
||||
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<std::is_same<T, basic_value<TC>>::value, T>
|
||||
get(basic_value<TC>&& v)
|
||||
{
|
||||
return basic_value<TC>(std::move(v));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// exact toml::* type
|
||||
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<detail::is_exact_toml_type<T, basic_value<TC>>::value, T> &
|
||||
get(basic_value<TC>& v)
|
||||
{
|
||||
constexpr auto ty = detail::type_to_enum<T, basic_value<TC>>::value;
|
||||
return detail::getter<TC, ty>::get(v);
|
||||
}
|
||||
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<detail::is_exact_toml_type<T, basic_value<TC>>::value, T> const&
|
||||
get(const basic_value<TC>& v)
|
||||
{
|
||||
constexpr auto ty = detail::type_to_enum<T, basic_value<TC>>::value;
|
||||
return detail::getter<TC, ty>::get(v);
|
||||
}
|
||||
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<detail::is_exact_toml_type<T, basic_value<TC>>::value, T>
|
||||
get(basic_value<TC>&& v)
|
||||
{
|
||||
constexpr auto ty = detail::type_to_enum<T, basic_value<TC>>::value;
|
||||
return detail::getter<TC, ty>::get(std::move(v));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// T is toml::basic_value<U>
|
||||
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<cxx::conjunction<
|
||||
detail::is_basic_value<T>,
|
||||
cxx::negation<std::is_same<T, basic_value<TC>>>
|
||||
>::value, T>
|
||||
get(basic_value<TC> v)
|
||||
{
|
||||
return T(std::move(v));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// integer convertible from toml::value::integer_type
|
||||
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<cxx::conjunction<
|
||||
std::is_integral<T>,
|
||||
cxx::negation<std::is_same<T, bool>>,
|
||||
detail::is_not_toml_type<T, basic_value<TC>>,
|
||||
cxx::negation<detail::has_from_toml_method<T, TC>>,
|
||||
cxx::negation<detail::has_specialized_from<T>>
|
||||
>::value, T>
|
||||
get(const basic_value<TC>& v)
|
||||
{
|
||||
return static_cast<T>(v.as_integer());
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// floating point convertible from toml::value::floating_type
|
||||
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<cxx::conjunction<
|
||||
std::is_floating_point<T>,
|
||||
detail::is_not_toml_type<T, basic_value<TC>>,
|
||||
cxx::negation<detail::has_from_toml_method<T, TC>>,
|
||||
cxx::negation<detail::has_specialized_from<T>>
|
||||
>::value, T>
|
||||
get(const basic_value<TC>& v)
|
||||
{
|
||||
return static_cast<T>(v.as_floating());
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// std::string with different char/trait/allocator
|
||||
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<cxx::conjunction<
|
||||
detail::is_not_toml_type<T, basic_value<TC>>,
|
||||
detail::is_1byte_std_basic_string<T>
|
||||
>::value, T>
|
||||
get(const basic_value<TC>& v)
|
||||
{
|
||||
return detail::string_conv<cxx::remove_cvref_t<T>>(v.as_string());
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// std::string_view
|
||||
|
||||
#if defined(TOML11_HAS_STRING_VIEW)
|
||||
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<detail::is_string_view_of<T, typename basic_value<TC>::string_type>::value, T>
|
||||
get(const basic_value<TC>& v)
|
||||
{
|
||||
return T(v.as_string());
|
||||
}
|
||||
|
||||
#endif // string_view
|
||||
|
||||
// ============================================================================
|
||||
// std::chrono::duration from toml::local_time
|
||||
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<detail::is_chrono_duration<T>::value, T>
|
||||
get(const basic_value<TC>& v)
|
||||
{
|
||||
return std::chrono::duration_cast<T>(
|
||||
std::chrono::nanoseconds(v.as_local_time()));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// std::chrono::system_clock::time_point from toml::datetime variants
|
||||
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<
|
||||
std::is_same<std::chrono::system_clock::time_point, T>::value, T>
|
||||
get(const basic_value<TC>& v)
|
||||
{
|
||||
switch(v.type())
|
||||
{
|
||||
case value_t::local_date:
|
||||
{
|
||||
return std::chrono::system_clock::time_point(v.as_local_date());
|
||||
}
|
||||
case value_t::local_datetime:
|
||||
{
|
||||
return std::chrono::system_clock::time_point(v.as_local_datetime());
|
||||
}
|
||||
case value_t::offset_datetime:
|
||||
{
|
||||
return std::chrono::system_clock::time_point(v.as_offset_datetime());
|
||||
}
|
||||
default:
|
||||
{
|
||||
const auto loc = v.location();
|
||||
throw type_error(format_error("toml::get: "
|
||||
"bad_cast to std::chrono::system_clock::time_point", loc,
|
||||
"the actual type is " + to_string(v.type())), loc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// forward declaration to use this recursively. ignore this and go ahead.
|
||||
|
||||
// array-like (w/ push_back)
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<cxx::conjunction<
|
||||
detail::is_container<T>, // T is a container
|
||||
detail::has_push_back_method<T>, // .push_back() works
|
||||
detail::is_not_toml_type<T, basic_value<TC>>, // but not toml::array
|
||||
cxx::negation<detail::is_std_basic_string<T>>, // but not std::basic_string<CharT>
|
||||
#if defined(TOML11_HAS_STRING_VIEW)
|
||||
cxx::negation<detail::is_std_basic_string_view<T>>, // but not std::basic_string_view<CharT>
|
||||
#endif
|
||||
cxx::negation<detail::has_from_toml_method<T, TC>>, // no T.from_toml()
|
||||
cxx::negation<detail::has_specialized_from<T>>, // no toml::from<T>
|
||||
cxx::negation<std::is_constructible<T, const basic_value<TC>&>>
|
||||
>::value, T>
|
||||
get(const basic_value<TC>&);
|
||||
|
||||
// std::array
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<detail::is_std_array<T>::value, T>
|
||||
get(const basic_value<TC>&);
|
||||
|
||||
// std::forward_list
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<detail::is_std_forward_list<T>::value, T>
|
||||
get(const basic_value<TC>&);
|
||||
|
||||
// std::pair<T1, T2>
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<detail::is_std_pair<T>::value, T>
|
||||
get(const basic_value<TC>&);
|
||||
|
||||
// std::tuple<T1, T2, ...>
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<detail::is_std_tuple<T>::value, T>
|
||||
get(const basic_value<TC>&);
|
||||
|
||||
// std::map<key, value> (key is convertible from toml::value::key_type)
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<cxx::conjunction<
|
||||
detail::is_map<T>, // T is map
|
||||
detail::is_not_toml_type<T, basic_value<TC>>, // but not toml::table
|
||||
std::is_convertible<typename basic_value<TC>::key_type,
|
||||
typename T::key_type>, // keys are convertible
|
||||
cxx::negation<detail::has_from_toml_method<T, TC>>, // no T.from_toml()
|
||||
cxx::negation<detail::has_specialized_from<T>>, // no toml::from<T>
|
||||
cxx::negation<std::is_constructible<T, const basic_value<TC>&>>
|
||||
>::value, T>
|
||||
get(const basic_value<TC>& v);
|
||||
|
||||
// std::map<key, value> (key is not convertible from toml::value::key_type, but
|
||||
// is a std::basic_string)
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<cxx::conjunction<
|
||||
detail::is_map<T>, // T is map
|
||||
detail::is_not_toml_type<T, basic_value<TC>>, // but not toml::table
|
||||
cxx::negation<std::is_convertible<typename basic_value<TC>::key_type,
|
||||
typename T::key_type>>, // keys are NOT convertible
|
||||
detail::is_1byte_std_basic_string<typename T::key_type>, // is std::basic_string
|
||||
cxx::negation<detail::has_from_toml_method<T, TC>>, // no T.from_toml()
|
||||
cxx::negation<detail::has_specialized_from<T>>, // no toml::from<T>
|
||||
cxx::negation<std::is_constructible<T, const basic_value<TC>&>>
|
||||
>::value, T>
|
||||
get(const basic_value<TC>& v);
|
||||
|
||||
// toml::from<T>::from_toml(v)
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<detail::has_specialized_from<T>::value, T>
|
||||
get(const basic_value<TC>&);
|
||||
|
||||
// has T.from_toml(v) but no from<T>
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<cxx::conjunction<
|
||||
detail::has_from_toml_method<T, TC>, // has T.from_toml()
|
||||
cxx::negation<detail::has_specialized_from<T>>, // no toml::from<T>
|
||||
std::is_default_constructible<T> // T{} works
|
||||
>::value, T>
|
||||
get(const basic_value<TC>&);
|
||||
|
||||
// T(const toml::value&) and T is not toml::basic_value,
|
||||
// and it does not have `from<T>` nor `from_toml`.
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<cxx::conjunction<
|
||||
std::is_constructible<T, const basic_value<TC>&>, // has T(const basic_value&)
|
||||
cxx::negation<detail::is_basic_value<T>>, // but not basic_value itself
|
||||
cxx::negation<detail::has_from_toml_method<T, TC>>, // no .from_toml()
|
||||
cxx::negation<detail::has_specialized_from<T>> // no toml::from<T>
|
||||
>::value, T>
|
||||
get(const basic_value<TC>&);
|
||||
|
||||
// ============================================================================
|
||||
// array-like types; most likely STL container, like std::vector, etc.
|
||||
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<cxx::conjunction<
|
||||
detail::is_container<T>, // T is a container
|
||||
detail::has_push_back_method<T>, // .push_back() works
|
||||
detail::is_not_toml_type<T, basic_value<TC>>, // but not toml::array
|
||||
cxx::negation<detail::is_std_basic_string<T>>, // but not std::basic_string<CharT>
|
||||
#if defined(TOML11_HAS_STRING_VIEW)
|
||||
cxx::negation<detail::is_std_basic_string_view<T>>, // but not std::basic_string_view<CharT>
|
||||
#endif
|
||||
cxx::negation<detail::has_from_toml_method<T, TC>>, // no T.from_toml()
|
||||
cxx::negation<detail::has_specialized_from<T>>, // no toml::from<T>
|
||||
cxx::negation<std::is_constructible<T, const basic_value<TC>&>>
|
||||
>::value, T>
|
||||
get(const basic_value<TC>& v)
|
||||
{
|
||||
using value_type = typename T::value_type;
|
||||
const auto& a = v.as_array();
|
||||
|
||||
T container;
|
||||
detail::try_reserve(container, a.size()); // if T has .reserve(), call it
|
||||
|
||||
for(const auto& elem : a)
|
||||
{
|
||||
container.push_back(get<value_type>(elem));
|
||||
}
|
||||
return container;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// std::array
|
||||
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<detail::is_std_array<T>::value, T>
|
||||
get(const basic_value<TC>& v)
|
||||
{
|
||||
using value_type = typename T::value_type;
|
||||
const auto& a = v.as_array();
|
||||
|
||||
T container;
|
||||
if(a.size() != container.size())
|
||||
{
|
||||
const auto loc = v.location();
|
||||
throw std::out_of_range(format_error("toml::get: while converting to an array: "
|
||||
" array size is " + std::to_string(container.size()) +
|
||||
" but there are " + std::to_string(a.size()) + " elements in toml array.",
|
||||
loc, "here"));
|
||||
}
|
||||
for(std::size_t i=0; i<a.size(); ++i)
|
||||
{
|
||||
container.at(i) = ::toml::get<value_type>(a.at(i));
|
||||
}
|
||||
return container;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// std::forward_list
|
||||
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<detail::is_std_forward_list<T>::value, T>
|
||||
get(const basic_value<TC>& v)
|
||||
{
|
||||
using value_type = typename T::value_type;
|
||||
|
||||
T container;
|
||||
for(const auto& elem : v.as_array())
|
||||
{
|
||||
container.push_front(get<value_type>(elem));
|
||||
}
|
||||
container.reverse();
|
||||
return container;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// std::pair
|
||||
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<detail::is_std_pair<T>::value, T>
|
||||
get(const basic_value<TC>& v)
|
||||
{
|
||||
using first_type = typename T::first_type;
|
||||
using second_type = typename T::second_type;
|
||||
|
||||
const auto& ar = v.as_array();
|
||||
if(ar.size() != 2)
|
||||
{
|
||||
const auto loc = v.location();
|
||||
throw std::out_of_range(format_error("toml::get: while converting std::pair: "
|
||||
" but there are " + std::to_string(ar.size()) + " > 2 elements in toml array.",
|
||||
loc, "here"));
|
||||
}
|
||||
return std::make_pair(::toml::get<first_type >(ar.at(0)),
|
||||
::toml::get<second_type>(ar.at(1)));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// std::tuple.
|
||||
|
||||
namespace detail
|
||||
{
|
||||
template<typename T, typename Array, std::size_t ... I>
|
||||
T get_tuple_impl(const Array& a, cxx::index_sequence<I...>)
|
||||
{
|
||||
return std::make_tuple(
|
||||
::toml::get<typename std::tuple_element<I, T>::type>(a.at(I))...);
|
||||
}
|
||||
} // detail
|
||||
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<detail::is_std_tuple<T>::value, T>
|
||||
get(const basic_value<TC>& v)
|
||||
{
|
||||
const auto& ar = v.as_array();
|
||||
if(ar.size() != std::tuple_size<T>::value)
|
||||
{
|
||||
const auto loc = v.location();
|
||||
throw std::out_of_range(format_error("toml::get: while converting std::tuple: "
|
||||
" there are " + std::to_string(ar.size()) + " > " +
|
||||
std::to_string(std::tuple_size<T>::value) + " elements in toml array.",
|
||||
loc, "here"));
|
||||
}
|
||||
return detail::get_tuple_impl<T>(ar,
|
||||
cxx::make_index_sequence<std::tuple_size<T>::value>{});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// std::unordered_set
|
||||
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<toml::detail::is_unordered_set<T>::value, T>
|
||||
get(const basic_value<TC>& v)
|
||||
{
|
||||
using value_type = typename T::value_type;
|
||||
const auto& a = v.as_array();
|
||||
|
||||
T container;
|
||||
for (const auto& elem : a)
|
||||
{
|
||||
container.insert(get<value_type>(elem));
|
||||
}
|
||||
return container;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// map-like types; most likely STL map, like std::map or std::unordered_map.
|
||||
|
||||
// key is convertible from toml::value::key_type
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<cxx::conjunction<
|
||||
detail::is_map<T>, // T is map
|
||||
detail::is_not_toml_type<T, basic_value<TC>>, // but not toml::table
|
||||
std::is_convertible<typename basic_value<TC>::key_type,
|
||||
typename T::key_type>, // keys are convertible
|
||||
cxx::negation<detail::has_from_toml_method<T, TC>>, // no T.from_toml()
|
||||
cxx::negation<detail::has_specialized_from<T>>, // no toml::from<T>
|
||||
cxx::negation<std::is_constructible<T, const basic_value<TC>&>>
|
||||
>::value, T>
|
||||
get(const basic_value<TC>& v)
|
||||
{
|
||||
using key_type = typename T::key_type;
|
||||
using mapped_type = typename T::mapped_type;
|
||||
static_assert(
|
||||
std::is_convertible<typename basic_value<TC>::key_type, key_type>::value,
|
||||
"toml::get only supports map type of which key_type is "
|
||||
"convertible from toml::basic_value::key_type.");
|
||||
|
||||
T m;
|
||||
for(const auto& kv : v.as_table())
|
||||
{
|
||||
m.emplace(key_type(kv.first), get<mapped_type>(kv.second));
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
// key is NOT convertible from toml::value::key_type but std::basic_string
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<cxx::conjunction<
|
||||
detail::is_map<T>, // T is map
|
||||
detail::is_not_toml_type<T, basic_value<TC>>, // but not toml::table
|
||||
cxx::negation<std::is_convertible<typename basic_value<TC>::key_type,
|
||||
typename T::key_type>>, // keys are NOT convertible
|
||||
detail::is_1byte_std_basic_string<typename T::key_type>, // is std::basic_string
|
||||
cxx::negation<detail::has_from_toml_method<T, TC>>, // no T.from_toml()
|
||||
cxx::negation<detail::has_specialized_from<T>>, // no toml::from<T>
|
||||
cxx::negation<std::is_constructible<T, const basic_value<TC>&>>
|
||||
>::value, T>
|
||||
get(const basic_value<TC>& v)
|
||||
{
|
||||
using key_type = typename T::key_type;
|
||||
using mapped_type = typename T::mapped_type;
|
||||
|
||||
T m;
|
||||
for(const auto& kv : v.as_table())
|
||||
{
|
||||
m.emplace(detail::string_conv<key_type>(kv.first), get<mapped_type>(kv.second));
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// user-defined, but convertible types.
|
||||
|
||||
// toml::from<T>
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<detail::has_specialized_from<T>::value, T>
|
||||
get(const basic_value<TC>& v)
|
||||
{
|
||||
return ::toml::from<T>::from_toml(v);
|
||||
}
|
||||
|
||||
// has T.from_toml(v) but no from<T>
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<cxx::conjunction<
|
||||
detail::has_from_toml_method<T, TC>, // has T.from_toml()
|
||||
cxx::negation<detail::has_specialized_from<T>>, // no toml::from<T>
|
||||
std::is_default_constructible<T> // T{} works
|
||||
>::value, T>
|
||||
get(const basic_value<TC>& v)
|
||||
{
|
||||
T ud;
|
||||
ud.from_toml(v);
|
||||
return ud;
|
||||
}
|
||||
|
||||
// T(const toml::value&) and T is not toml::basic_value,
|
||||
// and it does not have `from<T>` nor `from_toml`.
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<cxx::conjunction<
|
||||
std::is_constructible<T, const basic_value<TC>&>, // has T(const basic_value&)
|
||||
cxx::negation<detail::is_basic_value<T>>, // but not basic_value itself
|
||||
cxx::negation<detail::has_from_toml_method<T, TC>>, // no .from_toml()
|
||||
cxx::negation<detail::has_specialized_from<T>> // no toml::from<T>
|
||||
>::value, T>
|
||||
get(const basic_value<TC>& v)
|
||||
{
|
||||
return T(v);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// get_or(value, fallback)
|
||||
|
||||
template<typename TC>
|
||||
cxx::enable_if_t<detail::is_type_config<TC>::value, basic_value<TC>> const&
|
||||
get_or(const basic_value<TC>& v, const basic_value<TC>&)
|
||||
{
|
||||
return v;
|
||||
}
|
||||
|
||||
template<typename TC>
|
||||
cxx::enable_if_t<detail::is_type_config<TC>::value, basic_value<TC>>&
|
||||
get_or(basic_value<TC>& v, basic_value<TC>&)
|
||||
{
|
||||
return v;
|
||||
}
|
||||
|
||||
template<typename TC>
|
||||
cxx::enable_if_t<detail::is_type_config<TC>::value, basic_value<TC>>
|
||||
get_or(basic_value<TC>&& v, basic_value<TC>&&)
|
||||
{
|
||||
return v;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// specialization for the exact toml types (return type becomes lvalue ref)
|
||||
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<
|
||||
detail::is_exact_toml_type<T, basic_value<TC>>::value, T> const&
|
||||
get_or(const basic_value<TC>& v, const T& opt) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
return get<cxx::remove_cvref_t<T>>(v);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
return opt;
|
||||
}
|
||||
}
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<cxx::conjunction<
|
||||
cxx::negation<std::is_const<T>>,
|
||||
detail::is_exact_toml_type<T, basic_value<TC>>
|
||||
>::value, T>&
|
||||
get_or(basic_value<TC>& v, T& opt) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
return get<cxx::remove_cvref_t<T>>(v);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
return opt;
|
||||
}
|
||||
}
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<detail::is_exact_toml_type<cxx::remove_cvref_t<T>,
|
||||
basic_value<TC>>::value, cxx::remove_cvref_t<T>>
|
||||
get_or(basic_value<TC>&& v, T&& opt) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
return get<cxx::remove_cvref_t<T>>(std::move(v));
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
return cxx::remove_cvref_t<T>(std::forward<T>(opt));
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// specialization for string literal
|
||||
|
||||
// template<std::size_t N, typename TC>
|
||||
// typename basic_value<TC>::string_type
|
||||
// get_or(const basic_value<TC>& v,
|
||||
// const typename basic_value<TC>::string_type::value_type (&opt)[N])
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// return v.as_string();
|
||||
// }
|
||||
// catch(...)
|
||||
// {
|
||||
// return typename basic_value<TC>::string_type(opt);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// The above only matches to the literal, like `get_or(v, "foo");` but not
|
||||
// ```cpp
|
||||
// const auto opt = "foo";
|
||||
// const auto str = get_or(v, opt);
|
||||
// ```
|
||||
// . And the latter causes an error.
|
||||
// To match to both `"foo"` and `const auto opt = "foo"`, we take a pointer to
|
||||
// a character here.
|
||||
|
||||
template<typename TC>
|
||||
typename basic_value<TC>::string_type
|
||||
get_or(const basic_value<TC>& v,
|
||||
const typename basic_value<TC>::string_type::value_type* opt)
|
||||
{
|
||||
try
|
||||
{
|
||||
return v.as_string();
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
return typename basic_value<TC>::string_type(opt);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// others (require type conversion and return type cannot be lvalue reference)
|
||||
|
||||
template<typename T, typename TC>
|
||||
cxx::enable_if_t<cxx::conjunction<
|
||||
cxx::negation<detail::is_basic_value<T>>,
|
||||
cxx::negation<detail::is_exact_toml_type<T, basic_value<TC>>>,
|
||||
cxx::negation<std::is_same<cxx::remove_cvref_t<T>, typename basic_value<TC>::string_type::value_type const*>>
|
||||
>::value, cxx::remove_cvref_t<T>>
|
||||
get_or(const basic_value<TC>& v, T&& opt)
|
||||
{
|
||||
try
|
||||
{
|
||||
return get<cxx::remove_cvref_t<T>>(v);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
return cxx::remove_cvref_t<T>(std::forward<T>(opt));
|
||||
}
|
||||
}
|
||||
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
#endif // TOML11_GET_HPP
|
||||
79
include/toml11/impl/color_impl.hpp
Normal file
79
include/toml11/impl/color_impl.hpp
Normal file
@@ -0,0 +1,79 @@
|
||||
#ifndef TOML11_COLOR_IMPL_HPP
|
||||
#define TOML11_COLOR_IMPL_HPP
|
||||
|
||||
#include "../fwd/color_fwd.hpp"
|
||||
#include "../version.hpp"
|
||||
|
||||
#include <ostream>
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
namespace color
|
||||
{
|
||||
// put ANSI escape sequence to ostream
|
||||
inline namespace ansi
|
||||
{
|
||||
|
||||
TOML11_INLINE std::ostream& reset(std::ostream& os)
|
||||
{
|
||||
if(detail::color_status().should_color()) {os << "\033[00m";}
|
||||
return os;
|
||||
}
|
||||
TOML11_INLINE std::ostream& bold(std::ostream& os)
|
||||
{
|
||||
if(detail::color_status().should_color()) {os << "\033[01m";}
|
||||
return os;
|
||||
}
|
||||
TOML11_INLINE std::ostream& grey(std::ostream& os)
|
||||
{
|
||||
if(detail::color_status().should_color()) {os << "\033[30m";}
|
||||
return os;
|
||||
}
|
||||
TOML11_INLINE std::ostream& gray(std::ostream& os)
|
||||
{
|
||||
if(detail::color_status().should_color()) {os << "\033[30m";}
|
||||
return os;
|
||||
}
|
||||
TOML11_INLINE std::ostream& red(std::ostream& os)
|
||||
{
|
||||
if(detail::color_status().should_color()) {os << "\033[31m";}
|
||||
return os;
|
||||
}
|
||||
TOML11_INLINE std::ostream& green(std::ostream& os)
|
||||
{
|
||||
if(detail::color_status().should_color()) {os << "\033[32m";}
|
||||
return os;
|
||||
}
|
||||
TOML11_INLINE std::ostream& yellow(std::ostream& os)
|
||||
{
|
||||
if(detail::color_status().should_color()) {os << "\033[33m";}
|
||||
return os;
|
||||
}
|
||||
TOML11_INLINE std::ostream& blue(std::ostream& os)
|
||||
{
|
||||
if(detail::color_status().should_color()) {os << "\033[34m";}
|
||||
return os;
|
||||
}
|
||||
TOML11_INLINE std::ostream& magenta(std::ostream& os)
|
||||
{
|
||||
if(detail::color_status().should_color()) {os << "\033[35m";}
|
||||
return os;
|
||||
}
|
||||
TOML11_INLINE std::ostream& cyan (std::ostream& os)
|
||||
{
|
||||
if(detail::color_status().should_color()) {os << "\033[36m";}
|
||||
return os;
|
||||
}
|
||||
TOML11_INLINE std::ostream& white (std::ostream& os)
|
||||
{
|
||||
if(detail::color_status().should_color()) {os << "\033[37m";}
|
||||
return os;
|
||||
}
|
||||
|
||||
} // ansi
|
||||
} // color
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
#endif // TOML11_COLOR_IMPL_HPP
|
||||
50
include/toml11/impl/comments_impl.hpp
Normal file
50
include/toml11/impl/comments_impl.hpp
Normal file
@@ -0,0 +1,50 @@
|
||||
#ifndef TOML11_COMMENTS_IMPL_HPP
|
||||
#define TOML11_COMMENTS_IMPL_HPP
|
||||
|
||||
#include "../fwd/comments_fwd.hpp" // IWYU pragma: keep
|
||||
#include "../version.hpp"
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
|
||||
TOML11_INLINE bool operator==(const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments == rhs.comments;}
|
||||
TOML11_INLINE bool operator!=(const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments != rhs.comments;}
|
||||
TOML11_INLINE bool operator< (const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments < rhs.comments;}
|
||||
TOML11_INLINE bool operator<=(const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments <= rhs.comments;}
|
||||
TOML11_INLINE bool operator> (const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments > rhs.comments;}
|
||||
TOML11_INLINE bool operator>=(const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments >= rhs.comments;}
|
||||
|
||||
TOML11_INLINE void swap(preserve_comments& lhs, preserve_comments& rhs)
|
||||
{
|
||||
lhs.swap(rhs);
|
||||
return;
|
||||
}
|
||||
TOML11_INLINE void swap(preserve_comments& lhs, std::vector<std::string>& rhs)
|
||||
{
|
||||
lhs.comments.swap(rhs);
|
||||
return;
|
||||
}
|
||||
TOML11_INLINE void swap(std::vector<std::string>& lhs, preserve_comments& rhs)
|
||||
{
|
||||
lhs.swap(rhs.comments);
|
||||
return;
|
||||
}
|
||||
|
||||
TOML11_INLINE std::ostream& operator<<(std::ostream& os, const preserve_comments& com)
|
||||
{
|
||||
for(const auto& c : com)
|
||||
{
|
||||
if(c.front() != '#')
|
||||
{
|
||||
os << '#';
|
||||
}
|
||||
os << c << '\n';
|
||||
}
|
||||
return os;
|
||||
}
|
||||
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml11
|
||||
#endif // TOML11_COMMENTS_IMPL_HPP
|
||||
521
include/toml11/impl/datetime_impl.hpp
Normal file
521
include/toml11/impl/datetime_impl.hpp
Normal file
@@ -0,0 +1,521 @@
|
||||
#ifndef TOML11_DATETIME_IMPL_HPP
|
||||
#define TOML11_DATETIME_IMPL_HPP
|
||||
|
||||
#include "../fwd/datetime_fwd.hpp"
|
||||
#include "../version.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <iomanip>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <tuple>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
|
||||
// To avoid non-threadsafe std::localtime. In C11 (not C++11!), localtime_s is
|
||||
// provided in the absolutely same purpose, but C++11 is actually not compatible
|
||||
// with C11. We need to dispatch the function depending on the OS.
|
||||
namespace detail
|
||||
{
|
||||
// TODO: find more sophisticated way to handle this
|
||||
#if defined(_MSC_VER)
|
||||
TOML11_INLINE std::tm localtime_s(const std::time_t* src)
|
||||
{
|
||||
std::tm dst;
|
||||
const auto result = ::localtime_s(&dst, src);
|
||||
if (result) { throw std::runtime_error("localtime_s failed."); }
|
||||
return dst;
|
||||
}
|
||||
TOML11_INLINE std::tm gmtime_s(const std::time_t* src)
|
||||
{
|
||||
std::tm dst;
|
||||
const auto result = ::gmtime_s(&dst, src);
|
||||
if (result) { throw std::runtime_error("gmtime_s failed."); }
|
||||
return dst;
|
||||
}
|
||||
#elif (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 1) || defined(_XOPEN_SOURCE) || defined(_BSD_SOURCE) || defined(_SVID_SOURCE) || defined(_POSIX_SOURCE)
|
||||
TOML11_INLINE std::tm localtime_s(const std::time_t* src)
|
||||
{
|
||||
std::tm dst;
|
||||
const auto result = ::localtime_r(src, &dst);
|
||||
if (!result) { throw std::runtime_error("localtime_r failed."); }
|
||||
return dst;
|
||||
}
|
||||
TOML11_INLINE std::tm gmtime_s(const std::time_t* src)
|
||||
{
|
||||
std::tm dst;
|
||||
const auto result = ::gmtime_r(src, &dst);
|
||||
if (!result) { throw std::runtime_error("gmtime_r failed."); }
|
||||
return dst;
|
||||
}
|
||||
#else // fallback. not threadsafe
|
||||
TOML11_INLINE std::tm localtime_s(const std::time_t* src)
|
||||
{
|
||||
const auto result = std::localtime(src);
|
||||
if (!result) { throw std::runtime_error("localtime failed."); }
|
||||
return *result;
|
||||
}
|
||||
TOML11_INLINE std::tm gmtime_s(const std::time_t* src)
|
||||
{
|
||||
const auto result = std::gmtime(src);
|
||||
if (!result) { throw std::runtime_error("gmtime failed."); }
|
||||
return *result;
|
||||
}
|
||||
#endif
|
||||
} // detail
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
TOML11_INLINE local_date::local_date(const std::chrono::system_clock::time_point& tp)
|
||||
{
|
||||
const auto t = std::chrono::system_clock::to_time_t(tp);
|
||||
const auto time = detail::localtime_s(&t);
|
||||
*this = local_date(time);
|
||||
}
|
||||
|
||||
TOML11_INLINE local_date::local_date(const std::time_t t)
|
||||
: local_date{std::chrono::system_clock::from_time_t(t)}
|
||||
{}
|
||||
|
||||
TOML11_INLINE local_date::operator std::chrono::system_clock::time_point() const
|
||||
{
|
||||
// std::mktime returns date as local time zone. no conversion needed
|
||||
std::tm t;
|
||||
t.tm_sec = 0;
|
||||
t.tm_min = 0;
|
||||
t.tm_hour = 0;
|
||||
t.tm_mday = static_cast<int>(this->day);
|
||||
t.tm_mon = static_cast<int>(this->month);
|
||||
t.tm_year = static_cast<int>(this->year) - 1900;
|
||||
t.tm_wday = 0; // the value will be ignored
|
||||
t.tm_yday = 0; // the value will be ignored
|
||||
t.tm_isdst = -1;
|
||||
return std::chrono::system_clock::from_time_t(std::mktime(&t));
|
||||
}
|
||||
|
||||
TOML11_INLINE local_date::operator std::time_t() const
|
||||
{
|
||||
return std::chrono::system_clock::to_time_t(
|
||||
std::chrono::system_clock::time_point(*this));
|
||||
}
|
||||
|
||||
TOML11_INLINE bool operator==(const local_date& lhs, const local_date& rhs)
|
||||
{
|
||||
return std::make_tuple(lhs.year, lhs.month, lhs.day) ==
|
||||
std::make_tuple(rhs.year, rhs.month, rhs.day);
|
||||
}
|
||||
TOML11_INLINE bool operator!=(const local_date& lhs, const local_date& rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
TOML11_INLINE bool operator< (const local_date& lhs, const local_date& rhs)
|
||||
{
|
||||
return std::make_tuple(lhs.year, lhs.month, lhs.day) <
|
||||
std::make_tuple(rhs.year, rhs.month, rhs.day);
|
||||
}
|
||||
TOML11_INLINE bool operator<=(const local_date& lhs, const local_date& rhs)
|
||||
{
|
||||
return (lhs < rhs) || (lhs == rhs);
|
||||
}
|
||||
TOML11_INLINE bool operator> (const local_date& lhs, const local_date& rhs)
|
||||
{
|
||||
return !(lhs <= rhs);
|
||||
}
|
||||
TOML11_INLINE bool operator>=(const local_date& lhs, const local_date& rhs)
|
||||
{
|
||||
return !(lhs < rhs);
|
||||
}
|
||||
|
||||
TOML11_INLINE std::ostream& operator<<(std::ostream& os, const local_date& date)
|
||||
{
|
||||
os << std::setfill('0') << std::setw(4) << static_cast<int>(date.year ) << '-';
|
||||
os << std::setfill('0') << std::setw(2) << static_cast<int>(date.month) + 1 << '-';
|
||||
os << std::setfill('0') << std::setw(2) << static_cast<int>(date.day ) ;
|
||||
return os;
|
||||
}
|
||||
|
||||
TOML11_INLINE std::string to_string(const local_date& date)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss.imbue(std::locale::classic());
|
||||
oss << date;
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
TOML11_INLINE local_time::operator std::chrono::nanoseconds() const
|
||||
{
|
||||
return std::chrono::nanoseconds (this->nanosecond) +
|
||||
std::chrono::microseconds(this->microsecond) +
|
||||
std::chrono::milliseconds(this->millisecond) +
|
||||
std::chrono::seconds(this->second) +
|
||||
std::chrono::minutes(this->minute) +
|
||||
std::chrono::hours(this->hour);
|
||||
}
|
||||
|
||||
TOML11_INLINE bool operator==(const local_time& lhs, const local_time& rhs)
|
||||
{
|
||||
return std::make_tuple(lhs.hour, lhs.minute, lhs.second, lhs.millisecond, lhs.microsecond, lhs.nanosecond) ==
|
||||
std::make_tuple(rhs.hour, rhs.minute, rhs.second, rhs.millisecond, rhs.microsecond, rhs.nanosecond);
|
||||
}
|
||||
TOML11_INLINE bool operator!=(const local_time& lhs, const local_time& rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
TOML11_INLINE bool operator< (const local_time& lhs, const local_time& rhs)
|
||||
{
|
||||
return std::make_tuple(lhs.hour, lhs.minute, lhs.second, lhs.millisecond, lhs.microsecond, lhs.nanosecond) <
|
||||
std::make_tuple(rhs.hour, rhs.minute, rhs.second, rhs.millisecond, rhs.microsecond, rhs.nanosecond);
|
||||
}
|
||||
TOML11_INLINE bool operator<=(const local_time& lhs, const local_time& rhs)
|
||||
{
|
||||
return (lhs < rhs) || (lhs == rhs);
|
||||
}
|
||||
TOML11_INLINE bool operator> (const local_time& lhs, const local_time& rhs)
|
||||
{
|
||||
return !(lhs <= rhs);
|
||||
}
|
||||
TOML11_INLINE bool operator>=(const local_time& lhs, const local_time& rhs)
|
||||
{
|
||||
return !(lhs < rhs);
|
||||
}
|
||||
|
||||
TOML11_INLINE std::ostream& operator<<(std::ostream& os, const local_time& time)
|
||||
{
|
||||
os << std::setfill('0') << std::setw(2) << static_cast<int>(time.hour ) << ':';
|
||||
os << std::setfill('0') << std::setw(2) << static_cast<int>(time.minute) << ':';
|
||||
os << std::setfill('0') << std::setw(2) << static_cast<int>(time.second);
|
||||
if(time.millisecond != 0 || time.microsecond != 0 || time.nanosecond != 0)
|
||||
{
|
||||
os << '.';
|
||||
os << std::setfill('0') << std::setw(3) << static_cast<int>(time.millisecond);
|
||||
if(time.microsecond != 0 || time.nanosecond != 0)
|
||||
{
|
||||
os << std::setfill('0') << std::setw(3) << static_cast<int>(time.microsecond);
|
||||
if(time.nanosecond != 0)
|
||||
{
|
||||
os << std::setfill('0') << std::setw(3) << static_cast<int>(time.nanosecond);
|
||||
}
|
||||
}
|
||||
}
|
||||
return os;
|
||||
}
|
||||
|
||||
TOML11_INLINE std::string to_string(const local_time& time)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss.imbue(std::locale::classic());
|
||||
oss << time;
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
TOML11_INLINE time_offset::operator std::chrono::minutes() const
|
||||
{
|
||||
return std::chrono::minutes(this->minute) +
|
||||
std::chrono::hours(this->hour);
|
||||
}
|
||||
|
||||
TOML11_INLINE bool operator==(const time_offset& lhs, const time_offset& rhs)
|
||||
{
|
||||
return std::make_tuple(lhs.hour, lhs.minute) ==
|
||||
std::make_tuple(rhs.hour, rhs.minute);
|
||||
}
|
||||
TOML11_INLINE bool operator!=(const time_offset& lhs, const time_offset& rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
TOML11_INLINE bool operator< (const time_offset& lhs, const time_offset& rhs)
|
||||
{
|
||||
return std::make_tuple(lhs.hour, lhs.minute) <
|
||||
std::make_tuple(rhs.hour, rhs.minute);
|
||||
}
|
||||
TOML11_INLINE bool operator<=(const time_offset& lhs, const time_offset& rhs)
|
||||
{
|
||||
return (lhs < rhs) || (lhs == rhs);
|
||||
}
|
||||
TOML11_INLINE bool operator> (const time_offset& lhs, const time_offset& rhs)
|
||||
{
|
||||
return !(lhs <= rhs);
|
||||
}
|
||||
TOML11_INLINE bool operator>=(const time_offset& lhs, const time_offset& rhs)
|
||||
{
|
||||
return !(lhs < rhs);
|
||||
}
|
||||
|
||||
TOML11_INLINE std::ostream& operator<<(std::ostream& os, const time_offset& offset)
|
||||
{
|
||||
if(offset.hour == 0 && offset.minute == 0)
|
||||
{
|
||||
os << 'Z';
|
||||
return os;
|
||||
}
|
||||
int minute = static_cast<int>(offset.hour) * 60 + offset.minute;
|
||||
if(minute < 0){os << '-'; minute = std::abs(minute);} else {os << '+';}
|
||||
os << std::setfill('0') << std::setw(2) << minute / 60 << ':';
|
||||
os << std::setfill('0') << std::setw(2) << minute % 60;
|
||||
return os;
|
||||
}
|
||||
|
||||
TOML11_INLINE std::string to_string(const time_offset& offset)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss.imbue(std::locale::classic());
|
||||
oss << offset;
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
TOML11_INLINE local_datetime::local_datetime(const std::chrono::system_clock::time_point& tp)
|
||||
{
|
||||
const auto t = std::chrono::system_clock::to_time_t(tp);
|
||||
std::tm ltime = detail::localtime_s(&t);
|
||||
|
||||
this->date = local_date(ltime);
|
||||
this->time = local_time(ltime);
|
||||
|
||||
// std::tm lacks subsecond information, so diff between tp and tm
|
||||
// can be used to get millisecond & microsecond information.
|
||||
const auto t_diff = tp -
|
||||
std::chrono::system_clock::from_time_t(std::mktime(<ime));
|
||||
this->time.millisecond = static_cast<std::uint16_t>(
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(t_diff).count());
|
||||
this->time.microsecond = static_cast<std::uint16_t>(
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(t_diff).count());
|
||||
this->time.nanosecond = static_cast<std::uint16_t>(
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds >(t_diff).count());
|
||||
}
|
||||
|
||||
TOML11_INLINE local_datetime::local_datetime(const std::time_t t)
|
||||
: local_datetime{std::chrono::system_clock::from_time_t(t)}
|
||||
{}
|
||||
|
||||
TOML11_INLINE local_datetime::operator std::chrono::system_clock::time_point() const
|
||||
{
|
||||
using internal_duration =
|
||||
typename std::chrono::system_clock::time_point::duration;
|
||||
|
||||
// Normally DST begins at A.M. 3 or 4. If we re-use conversion operator
|
||||
// of local_date and local_time independently, the conversion fails if
|
||||
// it is the day when DST begins or ends. Since local_date considers the
|
||||
// time is 00:00 A.M. and local_time does not consider DST because it
|
||||
// does not have any date information. We need to consider both date and
|
||||
// time information at the same time to convert it correctly.
|
||||
|
||||
std::tm t;
|
||||
t.tm_sec = static_cast<int>(this->time.second);
|
||||
t.tm_min = static_cast<int>(this->time.minute);
|
||||
t.tm_hour = static_cast<int>(this->time.hour);
|
||||
t.tm_mday = static_cast<int>(this->date.day);
|
||||
t.tm_mon = static_cast<int>(this->date.month);
|
||||
t.tm_year = static_cast<int>(this->date.year) - 1900;
|
||||
t.tm_wday = 0; // the value will be ignored
|
||||
t.tm_yday = 0; // the value will be ignored
|
||||
t.tm_isdst = -1;
|
||||
|
||||
// std::mktime returns date as local time zone. no conversion needed
|
||||
auto dt = std::chrono::system_clock::from_time_t(std::mktime(&t));
|
||||
dt += std::chrono::duration_cast<internal_duration>(
|
||||
std::chrono::milliseconds(this->time.millisecond) +
|
||||
std::chrono::microseconds(this->time.microsecond) +
|
||||
std::chrono::nanoseconds (this->time.nanosecond));
|
||||
return dt;
|
||||
}
|
||||
|
||||
TOML11_INLINE local_datetime::operator std::time_t() const
|
||||
{
|
||||
return std::chrono::system_clock::to_time_t(
|
||||
std::chrono::system_clock::time_point(*this));
|
||||
}
|
||||
|
||||
TOML11_INLINE bool operator==(const local_datetime& lhs, const local_datetime& rhs)
|
||||
{
|
||||
return std::make_tuple(lhs.date, lhs.time) ==
|
||||
std::make_tuple(rhs.date, rhs.time);
|
||||
}
|
||||
TOML11_INLINE bool operator!=(const local_datetime& lhs, const local_datetime& rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
TOML11_INLINE bool operator< (const local_datetime& lhs, const local_datetime& rhs)
|
||||
{
|
||||
return std::make_tuple(lhs.date, lhs.time) <
|
||||
std::make_tuple(rhs.date, rhs.time);
|
||||
}
|
||||
TOML11_INLINE bool operator<=(const local_datetime& lhs, const local_datetime& rhs)
|
||||
{
|
||||
return (lhs < rhs) || (lhs == rhs);
|
||||
}
|
||||
TOML11_INLINE bool operator> (const local_datetime& lhs, const local_datetime& rhs)
|
||||
{
|
||||
return !(lhs <= rhs);
|
||||
}
|
||||
TOML11_INLINE bool operator>=(const local_datetime& lhs, const local_datetime& rhs)
|
||||
{
|
||||
return !(lhs < rhs);
|
||||
}
|
||||
|
||||
TOML11_INLINE std::ostream& operator<<(std::ostream& os, const local_datetime& dt)
|
||||
{
|
||||
os << dt.date << 'T' << dt.time;
|
||||
return os;
|
||||
}
|
||||
|
||||
TOML11_INLINE std::string to_string(const local_datetime& dt)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss.imbue(std::locale::classic());
|
||||
oss << dt;
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
TOML11_INLINE offset_datetime::offset_datetime(const local_datetime& ld)
|
||||
: date{ld.date}, time{ld.time}, offset{get_local_offset(nullptr)}
|
||||
// use the current local timezone offset
|
||||
{}
|
||||
TOML11_INLINE offset_datetime::offset_datetime(const std::chrono::system_clock::time_point& tp)
|
||||
: offset{0, 0} // use gmtime
|
||||
{
|
||||
const auto timet = std::chrono::system_clock::to_time_t(tp);
|
||||
const auto tm = detail::gmtime_s(&timet);
|
||||
this->date = local_date(tm);
|
||||
this->time = local_time(tm);
|
||||
}
|
||||
TOML11_INLINE offset_datetime::offset_datetime(const std::time_t& t)
|
||||
: offset{0, 0} // use gmtime
|
||||
{
|
||||
const auto tm = detail::gmtime_s(&t);
|
||||
this->date = local_date(tm);
|
||||
this->time = local_time(tm);
|
||||
}
|
||||
TOML11_INLINE offset_datetime::offset_datetime(const std::tm& t)
|
||||
: offset{0, 0} // assume gmtime
|
||||
{
|
||||
this->date = local_date(t);
|
||||
this->time = local_time(t);
|
||||
}
|
||||
|
||||
TOML11_INLINE offset_datetime::operator std::chrono::system_clock::time_point() const
|
||||
{
|
||||
// get date-time
|
||||
using internal_duration =
|
||||
typename std::chrono::system_clock::time_point::duration;
|
||||
|
||||
// first, convert it to local date-time information in the same way as
|
||||
// local_datetime does. later we will use time_t to adjust time offset.
|
||||
std::tm t;
|
||||
t.tm_sec = static_cast<int>(this->time.second);
|
||||
t.tm_min = static_cast<int>(this->time.minute);
|
||||
t.tm_hour = static_cast<int>(this->time.hour);
|
||||
t.tm_mday = static_cast<int>(this->date.day);
|
||||
t.tm_mon = static_cast<int>(this->date.month);
|
||||
t.tm_year = static_cast<int>(this->date.year) - 1900;
|
||||
t.tm_wday = 0; // the value will be ignored
|
||||
t.tm_yday = 0; // the value will be ignored
|
||||
t.tm_isdst = -1;
|
||||
const std::time_t tp_loc = std::mktime(std::addressof(t));
|
||||
|
||||
auto tp = std::chrono::system_clock::from_time_t(tp_loc);
|
||||
tp += std::chrono::duration_cast<internal_duration>(
|
||||
std::chrono::milliseconds(this->time.millisecond) +
|
||||
std::chrono::microseconds(this->time.microsecond) +
|
||||
std::chrono::nanoseconds (this->time.nanosecond));
|
||||
|
||||
// Since mktime uses local time zone, it should be corrected.
|
||||
// `12:00:00+09:00` means `03:00:00Z`. So mktime returns `03:00:00Z` if
|
||||
// we are in `+09:00` timezone. To represent `12:00:00Z` there, we need
|
||||
// to add `+09:00` to `03:00:00Z`.
|
||||
// Here, it uses the time_t converted from date-time info to handle
|
||||
// daylight saving time.
|
||||
const auto ofs = get_local_offset(std::addressof(tp_loc));
|
||||
tp += std::chrono::hours (ofs.hour);
|
||||
tp += std::chrono::minutes(ofs.minute);
|
||||
|
||||
// We got `12:00:00Z` by correcting local timezone applied by mktime.
|
||||
// Then we will apply the offset. Let's say `12:00:00-08:00` is given.
|
||||
// And now, we have `12:00:00Z`. `12:00:00-08:00` means `20:00:00Z`.
|
||||
// So we need to subtract the offset.
|
||||
tp -= std::chrono::minutes(this->offset);
|
||||
return tp;
|
||||
}
|
||||
|
||||
TOML11_INLINE offset_datetime::operator std::time_t() const
|
||||
{
|
||||
return std::chrono::system_clock::to_time_t(
|
||||
std::chrono::system_clock::time_point(*this));
|
||||
}
|
||||
|
||||
TOML11_INLINE time_offset offset_datetime::get_local_offset(const std::time_t* tp)
|
||||
{
|
||||
// get local timezone with the same date-time information as mktime
|
||||
const auto t = detail::localtime_s(tp);
|
||||
|
||||
std::array<char, 6> buf;
|
||||
const auto result = std::strftime(buf.data(), 6, "%z", &t); // +hhmm\0
|
||||
if(result != 5)
|
||||
{
|
||||
throw std::runtime_error("toml::offset_datetime: cannot obtain "
|
||||
"timezone information of current env");
|
||||
}
|
||||
const int ofs = std::atoi(buf.data());
|
||||
const int ofs_h = ofs / 100;
|
||||
const int ofs_m = ofs - (ofs_h * 100);
|
||||
return time_offset(ofs_h, ofs_m);
|
||||
}
|
||||
|
||||
TOML11_INLINE bool operator==(const offset_datetime& lhs, const offset_datetime& rhs)
|
||||
{
|
||||
return std::make_tuple(lhs.date, lhs.time, lhs.offset) ==
|
||||
std::make_tuple(rhs.date, rhs.time, rhs.offset);
|
||||
}
|
||||
TOML11_INLINE bool operator!=(const offset_datetime& lhs, const offset_datetime& rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
TOML11_INLINE bool operator< (const offset_datetime& lhs, const offset_datetime& rhs)
|
||||
{
|
||||
return std::make_tuple(lhs.date, lhs.time, lhs.offset) <
|
||||
std::make_tuple(rhs.date, rhs.time, rhs.offset);
|
||||
}
|
||||
TOML11_INLINE bool operator<=(const offset_datetime& lhs, const offset_datetime& rhs)
|
||||
{
|
||||
return (lhs < rhs) || (lhs == rhs);
|
||||
}
|
||||
TOML11_INLINE bool operator> (const offset_datetime& lhs, const offset_datetime& rhs)
|
||||
{
|
||||
return !(lhs <= rhs);
|
||||
}
|
||||
TOML11_INLINE bool operator>=(const offset_datetime& lhs, const offset_datetime& rhs)
|
||||
{
|
||||
return !(lhs < rhs);
|
||||
}
|
||||
|
||||
TOML11_INLINE std::ostream& operator<<(std::ostream& os, const offset_datetime& dt)
|
||||
{
|
||||
os << dt.date << 'T' << dt.time << dt.offset;
|
||||
return os;
|
||||
}
|
||||
|
||||
TOML11_INLINE std::string to_string(const offset_datetime& dt)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss.imbue(std::locale::classic());
|
||||
oss << dt;
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
#endif // TOML11_DATETIME_IMPL_HPP
|
||||
79
include/toml11/impl/error_info_impl.hpp
Normal file
79
include/toml11/impl/error_info_impl.hpp
Normal file
@@ -0,0 +1,79 @@
|
||||
#ifndef TOML11_ERROR_INFO_IMPL_HPP
|
||||
#define TOML11_ERROR_INFO_IMPL_HPP
|
||||
|
||||
#include "../fwd/error_info_fwd.hpp"
|
||||
#include "../fwd/color_fwd.hpp"
|
||||
#include "../version.hpp"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
|
||||
TOML11_INLINE std::string format_error(const std::string& errkind, const error_info& err)
|
||||
{
|
||||
std::string errmsg;
|
||||
if( ! errkind.empty())
|
||||
{
|
||||
errmsg = errkind;
|
||||
errmsg += ' ';
|
||||
}
|
||||
errmsg += err.title();
|
||||
errmsg += '\n';
|
||||
|
||||
const auto lnw = [&err]() {
|
||||
std::size_t width = 0;
|
||||
for(const auto& l : err.locations())
|
||||
{
|
||||
width = (std::max)(detail::integer_width_base10(l.first.last_line_number()), width);
|
||||
}
|
||||
return width;
|
||||
}();
|
||||
|
||||
bool first = true;
|
||||
std::string prev_fname;
|
||||
for(const auto& lm : err.locations())
|
||||
{
|
||||
if( ! first)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << detail::make_string(lnw + 1, ' ')
|
||||
<< color::bold << color::blue << " |" << color::reset
|
||||
<< color::bold << " ...\n" << color::reset;
|
||||
oss << detail::make_string(lnw + 1, ' ')
|
||||
<< color::bold << color::blue << " |\n" << color::reset;
|
||||
errmsg += oss.str();
|
||||
}
|
||||
|
||||
const auto& l = lm.first;
|
||||
const auto& m = lm.second;
|
||||
|
||||
errmsg += detail::format_location_impl(lnw, prev_fname, l, m);
|
||||
|
||||
prev_fname = l.file_name();
|
||||
first = false;
|
||||
}
|
||||
|
||||
errmsg += err.suffix();
|
||||
|
||||
return errmsg;
|
||||
}
|
||||
|
||||
TOML11_INLINE std::string format_error(const error_info& err)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << color::red << color::bold << "[error]" << color::reset;
|
||||
return format_error(oss.str(), err);
|
||||
}
|
||||
|
||||
TOML11_INLINE std::ostream& operator<<(std::ostream& os, const error_info& e)
|
||||
{
|
||||
os << format_error(e);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
#endif // TOML11_ERROR_INFO_IMPL_HPP
|
||||
300
include/toml11/impl/format_impl.hpp
Normal file
300
include/toml11/impl/format_impl.hpp
Normal file
@@ -0,0 +1,300 @@
|
||||
#ifndef TOML11_FORMAT_IMPL_HPP
|
||||
#define TOML11_FORMAT_IMPL_HPP
|
||||
|
||||
#include "../fwd/format_fwd.hpp"
|
||||
#include "../version.hpp"
|
||||
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
|
||||
// toml types with serialization info
|
||||
|
||||
TOML11_INLINE std::ostream& operator<<(std::ostream& os, const indent_char& c)
|
||||
{
|
||||
switch(c)
|
||||
{
|
||||
case indent_char::space: {os << "space" ; break;}
|
||||
case indent_char::tab: {os << "tab" ; break;}
|
||||
case indent_char::none: {os << "none" ; break;}
|
||||
default:
|
||||
{
|
||||
os << "unknown indent char: " << static_cast<std::uint8_t>(c);
|
||||
}
|
||||
}
|
||||
return os;
|
||||
}
|
||||
|
||||
TOML11_INLINE std::string to_string(const indent_char c)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << c;
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// boolean
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// integer
|
||||
|
||||
TOML11_INLINE std::ostream& operator<<(std::ostream& os, const integer_format f)
|
||||
{
|
||||
switch(f)
|
||||
{
|
||||
case integer_format::dec: {os << "dec"; break;}
|
||||
case integer_format::bin: {os << "bin"; break;}
|
||||
case integer_format::oct: {os << "oct"; break;}
|
||||
case integer_format::hex: {os << "hex"; break;}
|
||||
default:
|
||||
{
|
||||
os << "unknown integer_format: " << static_cast<std::uint8_t>(f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return os;
|
||||
}
|
||||
TOML11_INLINE std::string to_string(const integer_format c)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << c;
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
|
||||
TOML11_INLINE bool operator==(const integer_format_info& lhs, const integer_format_info& rhs) noexcept
|
||||
{
|
||||
return lhs.fmt == rhs.fmt &&
|
||||
lhs.uppercase == rhs.uppercase &&
|
||||
lhs.width == rhs.width &&
|
||||
lhs.spacer == rhs.spacer &&
|
||||
lhs.suffix == rhs.suffix ;
|
||||
}
|
||||
TOML11_INLINE bool operator!=(const integer_format_info& lhs, const integer_format_info& rhs) noexcept
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// floating
|
||||
|
||||
TOML11_INLINE std::ostream& operator<<(std::ostream& os, const floating_format f)
|
||||
{
|
||||
switch(f)
|
||||
{
|
||||
case floating_format::defaultfloat: {os << "defaultfloat"; break;}
|
||||
case floating_format::fixed : {os << "fixed" ; break;}
|
||||
case floating_format::scientific : {os << "scientific" ; break;}
|
||||
case floating_format::hex : {os << "hex" ; break;}
|
||||
default:
|
||||
{
|
||||
os << "unknown floating_format: " << static_cast<std::uint8_t>(f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return os;
|
||||
}
|
||||
TOML11_INLINE std::string to_string(const floating_format c)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << c;
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
TOML11_INLINE bool operator==(const floating_format_info& lhs, const floating_format_info& rhs) noexcept
|
||||
{
|
||||
return lhs.fmt == rhs.fmt &&
|
||||
lhs.prec == rhs.prec &&
|
||||
lhs.suffix == rhs.suffix ;
|
||||
}
|
||||
TOML11_INLINE bool operator!=(const floating_format_info& lhs, const floating_format_info& rhs) noexcept
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// string
|
||||
|
||||
TOML11_INLINE std::ostream& operator<<(std::ostream& os, const string_format f)
|
||||
{
|
||||
switch(f)
|
||||
{
|
||||
case string_format::basic : {os << "basic" ; break;}
|
||||
case string_format::literal : {os << "literal" ; break;}
|
||||
case string_format::multiline_basic : {os << "multiline_basic" ; break;}
|
||||
case string_format::multiline_literal: {os << "multiline_literal"; break;}
|
||||
default:
|
||||
{
|
||||
os << "unknown string_format: " << static_cast<std::uint8_t>(f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return os;
|
||||
}
|
||||
TOML11_INLINE std::string to_string(const string_format c)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << c;
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
TOML11_INLINE bool operator==(const string_format_info& lhs, const string_format_info& rhs) noexcept
|
||||
{
|
||||
return lhs.fmt == rhs.fmt &&
|
||||
lhs.start_with_newline == rhs.start_with_newline ;
|
||||
}
|
||||
TOML11_INLINE bool operator!=(const string_format_info& lhs, const string_format_info& rhs) noexcept
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
// ----------------------------------------------------------------------------
|
||||
// datetime
|
||||
|
||||
TOML11_INLINE std::ostream& operator<<(std::ostream& os, const datetime_delimiter_kind d)
|
||||
{
|
||||
switch(d)
|
||||
{
|
||||
case datetime_delimiter_kind::upper_T: { os << "upper_T, "; break; }
|
||||
case datetime_delimiter_kind::lower_t: { os << "lower_t, "; break; }
|
||||
case datetime_delimiter_kind::space: { os << "space, "; break; }
|
||||
default:
|
||||
{
|
||||
os << "unknown datetime delimiter: " << static_cast<std::uint8_t>(d);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return os;
|
||||
}
|
||||
TOML11_INLINE std::string to_string(const datetime_delimiter_kind c)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << c;
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
TOML11_INLINE bool operator==(const offset_datetime_format_info& lhs, const offset_datetime_format_info& rhs) noexcept
|
||||
{
|
||||
return lhs.delimiter == rhs.delimiter &&
|
||||
lhs.has_seconds == rhs.has_seconds &&
|
||||
lhs.subsecond_precision == rhs.subsecond_precision ;
|
||||
}
|
||||
TOML11_INLINE bool operator!=(const offset_datetime_format_info& lhs, const offset_datetime_format_info& rhs) noexcept
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
TOML11_INLINE bool operator==(const local_datetime_format_info& lhs, const local_datetime_format_info& rhs) noexcept
|
||||
{
|
||||
return lhs.delimiter == rhs.delimiter &&
|
||||
lhs.has_seconds == rhs.has_seconds &&
|
||||
lhs.subsecond_precision == rhs.subsecond_precision ;
|
||||
}
|
||||
TOML11_INLINE bool operator!=(const local_datetime_format_info& lhs, const local_datetime_format_info& rhs) noexcept
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
TOML11_INLINE bool operator==(const local_date_format_info&, const local_date_format_info&) noexcept
|
||||
{
|
||||
return true;
|
||||
}
|
||||
TOML11_INLINE bool operator!=(const local_date_format_info& lhs, const local_date_format_info& rhs) noexcept
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
TOML11_INLINE bool operator==(const local_time_format_info& lhs, const local_time_format_info& rhs) noexcept
|
||||
{
|
||||
return lhs.has_seconds == rhs.has_seconds &&
|
||||
lhs.subsecond_precision == rhs.subsecond_precision ;
|
||||
}
|
||||
TOML11_INLINE bool operator!=(const local_time_format_info& lhs, const local_time_format_info& rhs) noexcept
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// array
|
||||
|
||||
TOML11_INLINE std::ostream& operator<<(std::ostream& os, const array_format f)
|
||||
{
|
||||
switch(f)
|
||||
{
|
||||
case array_format::default_format : {os << "default_format" ; break;}
|
||||
case array_format::oneline : {os << "oneline" ; break;}
|
||||
case array_format::multiline : {os << "multiline" ; break;}
|
||||
case array_format::array_of_tables: {os << "array_of_tables"; break;}
|
||||
default:
|
||||
{
|
||||
os << "unknown array_format: " << static_cast<std::uint8_t>(f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return os;
|
||||
}
|
||||
TOML11_INLINE std::string to_string(const array_format c)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << c;
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
TOML11_INLINE bool operator==(const array_format_info& lhs, const array_format_info& rhs) noexcept
|
||||
{
|
||||
return lhs.fmt == rhs.fmt &&
|
||||
lhs.indent_type == rhs.indent_type &&
|
||||
lhs.body_indent == rhs.body_indent &&
|
||||
lhs.closing_indent == rhs.closing_indent ;
|
||||
}
|
||||
TOML11_INLINE bool operator!=(const array_format_info& lhs, const array_format_info& rhs) noexcept
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// table
|
||||
|
||||
TOML11_INLINE std::ostream& operator<<(std::ostream& os, const table_format f)
|
||||
{
|
||||
switch(f)
|
||||
{
|
||||
case table_format::multiline : {os << "multiline" ; break;}
|
||||
case table_format::oneline : {os << "oneline" ; break;}
|
||||
case table_format::dotted : {os << "dotted" ; break;}
|
||||
case table_format::multiline_oneline: {os << "multiline_oneline"; break;}
|
||||
case table_format::implicit : {os << "implicit" ; break;}
|
||||
default:
|
||||
{
|
||||
os << "unknown table_format: " << static_cast<std::uint8_t>(f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return os;
|
||||
}
|
||||
TOML11_INLINE std::string to_string(const table_format c)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << c;
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
TOML11_INLINE bool operator==(const table_format_info& lhs, const table_format_info& rhs) noexcept
|
||||
{
|
||||
return lhs.fmt == rhs.fmt &&
|
||||
lhs.indent_type == rhs.indent_type &&
|
||||
lhs.body_indent == rhs.body_indent &&
|
||||
lhs.name_indent == rhs.name_indent &&
|
||||
lhs.closing_indent == rhs.closing_indent ;
|
||||
}
|
||||
TOML11_INLINE bool operator!=(const table_format_info& lhs, const table_format_info& rhs) noexcept
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // namespace toml
|
||||
#endif // TOML11_FORMAT_IMPL_HPP
|
||||
178
include/toml11/impl/literal_impl.hpp
Normal file
178
include/toml11/impl/literal_impl.hpp
Normal file
@@ -0,0 +1,178 @@
|
||||
#ifndef TOML11_LITERAL_IMPL_HPP
|
||||
#define TOML11_LITERAL_IMPL_HPP
|
||||
|
||||
#include "../fwd/literal_fwd.hpp"
|
||||
#include "../parser.hpp"
|
||||
#include "../syntax.hpp"
|
||||
#include "../version.hpp"
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
|
||||
namespace detail
|
||||
{
|
||||
// implementation
|
||||
TOML11_INLINE ::toml::value literal_internal_impl(location loc)
|
||||
{
|
||||
const auto s = ::toml::spec::default_version();
|
||||
context<type_config> ctx(s);
|
||||
|
||||
const auto front = loc;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// check if it is a raw value.
|
||||
|
||||
// skip empty lines and comment lines
|
||||
auto sp = skip_multiline_spacer(loc, ctx);
|
||||
if(loc.eof())
|
||||
{
|
||||
::toml::value val;
|
||||
if(sp.has_value())
|
||||
{
|
||||
for(std::size_t i=0; i<sp.value().comments.size(); ++i)
|
||||
{
|
||||
val.comments().push_back(std::move(sp.value().comments.at(i)));
|
||||
}
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
// to distinguish arrays and tables, first check it is a table or not.
|
||||
//
|
||||
// "[1,2,3]"_toml; // json: [1, 2, 3]
|
||||
// "[table]"_toml; // json: {"table": {}}
|
||||
// "[[1,2,3]]"_toml; // json: [[1, 2, 3]]
|
||||
// "[[table]]"_toml; // json: {"table": [{}]}
|
||||
//
|
||||
// "[[1]]"_toml; // json: {"1": [{}]}
|
||||
// "1 = [{}]"_toml; // json: {"1": [{}]}
|
||||
// "[[1,]]"_toml; // json: [[1]]
|
||||
// "[[1],]"_toml; // json: [[1]]
|
||||
const auto val_start = loc;
|
||||
|
||||
const bool is_table_key = syntax::std_table(s).scan(loc).is_ok();
|
||||
loc = val_start;
|
||||
const bool is_aots_key = syntax::array_table(s).scan(loc).is_ok();
|
||||
loc = val_start;
|
||||
|
||||
// If it is neither a table-key or a array-of-table-key, it may be a value.
|
||||
if(!is_table_key && !is_aots_key)
|
||||
{
|
||||
auto data = parse_value(loc, ctx);
|
||||
if(data.is_ok())
|
||||
{
|
||||
auto val = std::move(data.unwrap());
|
||||
if(sp.has_value())
|
||||
{
|
||||
for(std::size_t i=0; i<sp.value().comments.size(); ++i)
|
||||
{
|
||||
val.comments().push_back(std::move(sp.value().comments.at(i)));
|
||||
}
|
||||
}
|
||||
auto com_res = parse_comment_line(loc, ctx);
|
||||
if(com_res.is_ok() && com_res.unwrap().has_value())
|
||||
{
|
||||
val.comments().push_back(com_res.unwrap().value());
|
||||
}
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Note that still it can be a table, because the literal might be something
|
||||
// like the following.
|
||||
// ```cpp
|
||||
// // c++11 raw-string literal
|
||||
// const auto val = R"(
|
||||
// key = "value"
|
||||
// int = 42
|
||||
// )"_toml;
|
||||
// ```
|
||||
// It is a valid toml file.
|
||||
// It should be parsed as if we parse a file with this content.
|
||||
|
||||
loc = front;
|
||||
auto data = parse_file(loc, ctx);
|
||||
if(data.is_ok())
|
||||
{
|
||||
return data.unwrap();
|
||||
}
|
||||
else // not a value && not a file. error.
|
||||
{
|
||||
std::string msg;
|
||||
for(const auto& err : data.unwrap_err())
|
||||
{
|
||||
msg += format_error(err);
|
||||
}
|
||||
throw ::toml::syntax_error(std::move(msg), std::move(data.unwrap_err()));
|
||||
}
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
inline namespace literals
|
||||
{
|
||||
inline namespace toml_literals
|
||||
{
|
||||
|
||||
TOML11_INLINE ::toml::value
|
||||
operator""_toml(const char* str, std::size_t len)
|
||||
{
|
||||
if(len == 0)
|
||||
{
|
||||
return ::toml::value{};
|
||||
}
|
||||
|
||||
::toml::detail::location::container_type c(len);
|
||||
std::copy(reinterpret_cast<const ::toml::detail::location::char_type*>(str),
|
||||
reinterpret_cast<const ::toml::detail::location::char_type*>(str + len),
|
||||
c.begin());
|
||||
if( ! c.empty() && c.back())
|
||||
{
|
||||
c.push_back('\n'); // to make it easy to parse comment, we add newline
|
||||
}
|
||||
|
||||
return literal_internal_impl(::toml::detail::location(
|
||||
std::make_shared<const toml::detail::location::container_type>(std::move(c)),
|
||||
"TOML literal encoded in a C++ code"));
|
||||
}
|
||||
|
||||
#if defined(__cpp_char8_t)
|
||||
# if __cpp_char8_t >= 201811L
|
||||
# define TOML11_HAS_CHAR8_T 1
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if defined(TOML11_HAS_CHAR8_T)
|
||||
// value of u8"" literal has been changed from char to char8_t and char8_t is
|
||||
// NOT compatible to char
|
||||
TOML11_INLINE ::toml::value
|
||||
operator"" _toml(const char8_t* str, std::size_t len)
|
||||
{
|
||||
if(len == 0)
|
||||
{
|
||||
return ::toml::value{};
|
||||
}
|
||||
|
||||
::toml::detail::location::container_type c(len);
|
||||
std::copy(reinterpret_cast<const ::toml::detail::location::char_type*>(str),
|
||||
reinterpret_cast<const ::toml::detail::location::char_type*>(str + len),
|
||||
c.begin());
|
||||
if( ! c.empty() && c.back())
|
||||
{
|
||||
c.push_back('\n'); // to make it easy to parse comment, we add newline
|
||||
}
|
||||
|
||||
return literal_internal_impl(::toml::detail::location(
|
||||
std::make_shared<const toml::detail::location::container_type>(std::move(c)),
|
||||
"TOML literal encoded in a C++ code"));
|
||||
}
|
||||
#endif
|
||||
|
||||
} // toml_literals
|
||||
} // literals
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
#endif // TOML11_LITERAL_IMPL_HPP
|
||||
212
include/toml11/impl/location_impl.hpp
Normal file
212
include/toml11/impl/location_impl.hpp
Normal file
@@ -0,0 +1,212 @@
|
||||
#ifndef TOML11_LOCATION_IMPL_HPP
|
||||
#define TOML11_LOCATION_IMPL_HPP
|
||||
|
||||
#include "../fwd/location_fwd.hpp"
|
||||
#include "../utility.hpp"
|
||||
#include "../version.hpp"
|
||||
|
||||
namespace toml
|
||||
{
|
||||
inline namespace TOML11_INLINE_VERSION_NAMESPACE
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
|
||||
TOML11_INLINE void location::advance(std::size_t n) noexcept
|
||||
{
|
||||
assert(this->is_ok());
|
||||
if(this->location_ + n < this->source_->size())
|
||||
{
|
||||
this->advance_impl(n);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->advance_impl(this->source_->size() - this->location_);
|
||||
|
||||
assert(this->location_ == this->source_->size());
|
||||
}
|
||||
}
|
||||
TOML11_INLINE void location::retrace(/*restricted to n=1*/) noexcept
|
||||
{
|
||||
assert(this->is_ok());
|
||||
if(this->location_ == 0)
|
||||
{
|
||||
this->location_ = 0;
|
||||
this->line_number_ = 1;
|
||||
this->column_number_ = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
this->retrace_impl();
|
||||
}
|
||||
}
|
||||
|
||||
TOML11_INLINE bool location::eof() const noexcept
|
||||
{
|
||||
assert(this->is_ok());
|
||||
return this->location_ >= this->source_->size();
|
||||
}
|
||||
TOML11_INLINE location::char_type location::current() const
|
||||
{
|
||||
assert(this->is_ok());
|
||||
if(this->eof()) {return '\0';}
|
||||
|
||||
assert(this->location_ < this->source_->size());
|
||||
return this->source_->at(this->location_);
|
||||
}
|
||||
|
||||
TOML11_INLINE location::char_type location::peek()
|
||||
{
|
||||
assert(this->is_ok());
|
||||
if(this->location_ >= this->source_->size())
|
||||
{
|
||||
return '\0';
|
||||
}
|
||||
else
|
||||
{
|
||||
return this->source_->at(this->location_ + 1);
|
||||
}
|
||||
}
|
||||
|
||||
TOML11_INLINE std::string location::get_line() const
|
||||
{
|
||||
assert(this->is_ok());
|
||||
const auto iter = std::next(this->source_->cbegin(), static_cast<difference_type>(this->location_));
|
||||
const auto riter = cxx::make_reverse_iterator(iter);
|
||||
|
||||
const auto prev = std::find(riter, this->source_->crend(), char_type('\n'));
|
||||
const auto next = std::find(iter, this->source_->cend(), char_type('\n'));
|
||||
|
||||
return make_string(std::next(prev.base()), next);
|
||||
}
|
||||
|
||||
TOML11_INLINE std::size_t location::calc_column_number() const noexcept
|
||||
{
|
||||
assert(this->is_ok());
|
||||
const auto iter = std::next(this->source_->cbegin(), static_cast<difference_type>(this->location_));
|
||||
const auto riter = cxx::make_reverse_iterator(iter);
|
||||
const auto prev = std::find(riter, this->source_->crend(), char_type('\n'));
|
||||
|
||||
assert(prev.base() <= iter);
|
||||
return static_cast<std::size_t>(std::distance(prev.base(), iter) + 1); // 1-origin
|
||||
}
|
||||
|
||||
TOML11_INLINE void location::advance_impl(const std::size_t n)
|
||||
{
|
||||
assert(this->is_ok());
|
||||
assert(this->location_ + n <= this->source_->size());
|
||||
|
||||
auto iter = this->source_->cbegin();
|
||||
std::advance(iter, static_cast<difference_type>(this->location_));
|
||||
|
||||
for(std::size_t i=0; i<n; ++i)
|
||||
{
|
||||
const auto c = *iter;
|
||||
if(c == char_type('\n'))
|
||||
{
|
||||
this->line_number_ += 1;
|
||||
this->column_number_ = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
this->column_number_ += 1;
|
||||
}
|
||||
iter++;
|
||||
}
|
||||
this->location_ += n;
|
||||
return;
|
||||
}
|
||||
TOML11_INLINE void location::retrace_impl(/*n == 1*/)
|
||||
{
|
||||
assert(this->is_ok());
|
||||
assert(this->location_ != 0);
|
||||
|
||||
this->location_ -= 1;
|
||||
|
||||
auto iter = this->source_->cbegin();
|
||||
std::advance(iter, static_cast<difference_type>(this->location_));
|
||||
if(*iter == '\n')
|
||||
{
|
||||
this->line_number_ -= 1;
|
||||
this->column_number_ = this->calc_column_number();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
TOML11_INLINE bool operator==(const location& lhs, const location& rhs) noexcept
|
||||
{
|
||||
if( ! lhs.is_ok() || ! rhs.is_ok())
|
||||
{
|
||||
return (!lhs.is_ok()) && (!rhs.is_ok());
|
||||
}
|
||||
return lhs.source() == rhs.source() &&
|
||||
lhs.source_name() == rhs.source_name() &&
|
||||
lhs.get_location() == rhs.get_location();
|
||||
}
|
||||
TOML11_INLINE bool operator!=(const location& lhs, const location& rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
TOML11_INLINE location prev(const location& loc)
|
||||
{
|
||||
location p(loc);
|
||||
p.retrace();
|
||||
return p;
|
||||
}
|
||||
TOML11_INLINE location next(const location& loc)
|
||||
{
|
||||
location p(loc);
|
||||
p.advance(1);
|
||||
return p;
|
||||
}
|
||||
|
||||
TOML11_INLINE location make_temporary_location(const std::string& str) noexcept
|
||||
{
|
||||
location::container_type cont(str.size());
|
||||
std::transform(str.begin(), str.end(), cont.begin(),
|
||||
[](const std::string::value_type& c) {
|
||||
return cxx::bit_cast<location::char_type>(c);
|
||||
});
|
||||
return location(std::make_shared<const location::container_type>(
|
||||
std::move(cont)), "internal temporary");
|
||||
}
|
||||
|
||||
TOML11_INLINE result<location, none_t>
|
||||
find(const location& first, const location& last, const location::char_type val)
|
||||
{
|
||||
return find_if(first, last, [val](const location::char_type c) {
|
||||
return c == val;
|
||||
});
|
||||
}
|
||||
TOML11_INLINE result<location, none_t>
|
||||
rfind(const location& first, const location& last, const location::char_type val)
|
||||
{
|
||||
return rfind_if(first, last, [val](const location::char_type c) {
|
||||
return c == val;
|
||||
});
|
||||
}
|
||||
|
||||
TOML11_INLINE std::size_t
|
||||
count(const location& first, const location& last, const location::char_type& c)
|
||||
{
|
||||
if(first.source() != last.source()) { return 0; }
|
||||
if(first.get_location() >= last.get_location()) { return 0; }
|
||||
|
||||
auto loc = first;
|
||||
std::size_t num = 0;
|
||||
while(loc.get_location() != last.get_location())
|
||||
{
|
||||
if(loc.current() == c)
|
||||
{
|
||||
num += 1;
|
||||
}
|
||||
loc.advance();
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
} // detail
|
||||
} // TOML11_INLINE_VERSION_NAMESPACE
|
||||
} // toml
|
||||
#endif // TOML11_LOCATION_HPP
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user