mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-15 04:53:00 +08:00
⛅ Sync 2026-10-11 13:16:00
This commit is contained in:
1 parent
705dadd362
commit
e6d1b4e2d7
105 files changed
+26511
-50
No files matched your search
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Select the plugin and Chinese translation without the SDK's all-package defaults."""
|
||||
import pathlib
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
def configure(directory):
|
||||
directory = pathlib.Path(directory)
|
||||
if not (directory / "rules.mk").is_file() or not (directory / "Makefile").is_file():
|
||||
raise ValueError("Expected an OpenWrt SDK directory")
|
||||
config = directory / ".config"
|
||||
lines = config.read_text().splitlines() if config.exists() else []
|
||||
pattern = r"(?:# )?CONFIG_(?:ALL(?:_NONSHARED|_KMODS)?\b|PACKAGE_|LUCI_LANG_)"
|
||||
retained = [line for line in lines if not re.match(pattern, line)]
|
||||
selected = [
|
||||
"# CONFIG_ALL is not set", "# CONFIG_ALL_NONSHARED is not set", "# CONFIG_ALL_KMODS is not set",
|
||||
"CONFIG_PACKAGE_luci-app-device-manager=m", "CONFIG_LUCI_LANG_zh_Hans=y",
|
||||
"CONFIG_PACKAGE_luci-i18n-device-manager-zh-cn=m",
|
||||
]
|
||||
config.write_text("\n".join(retained + selected) + "\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 2:
|
||||
sys.exit("Usage: configure-sdk.py <sdk-directory>")
|
||||
configure(sys.argv[1])
|
||||
Executable
+258
@@ -0,0 +1,258 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# deploy.sh - Deploy luci-app-device-manager directly to an OpenWrt router for development/testing.
|
||||
# SSH authentication is performed once; all deployment commands reuse that connection.
|
||||
#
|
||||
# Usage:
|
||||
# ./tools/deploy.sh [options] <target>
|
||||
#
|
||||
# Examples:
|
||||
# ./tools/deploy.sh 192.168.1.1
|
||||
# ./tools/deploy.sh root@192.168.1.1
|
||||
# ./tools/deploy.sh -p 2222 root@192.168.1.1
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Color formatting
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
print_info() {
|
||||
printf "${BLUE}[INFO]${NC} %s\n" "$1"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
printf "${GREEN}[SUCCESS]${NC} %s\n" "$1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
printf "${RED}[ERROR]${NC} %s\n" "$1" >&2
|
||||
}
|
||||
|
||||
show_usage() {
|
||||
cat <<EOF
|
||||
Usage: $0 [options] <target>
|
||||
|
||||
Arguments:
|
||||
<target> Router IP address or SSH user@host (e.g., 192.168.1.1 or root@192.168.1.1)
|
||||
|
||||
Options:
|
||||
-p, --port <port> SSH port number (default: 22)
|
||||
-h, --help Show this help message
|
||||
|
||||
Examples:
|
||||
$0 192.168.1.1
|
||||
$0 root@192.168.1.1
|
||||
$0 -p 2222 root@192.168.31.1
|
||||
EOF
|
||||
}
|
||||
|
||||
# Parse options
|
||||
SSH_PORT="22"
|
||||
TARGET=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-p|--port)
|
||||
if [[ $# -lt 2 || ! "$2" =~ ^[0-9]+$ || ${#2} -gt 5 ]] || (( 10#$2 < 1 || 10#$2 > 65535 )); then
|
||||
print_error "SSH port must be an integer between 1 and 65535."
|
||||
exit 1
|
||||
fi
|
||||
SSH_PORT="$2"
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
show_usage
|
||||
exit 0
|
||||
;;
|
||||
-*)
|
||||
print_error "Unknown option: $1"
|
||||
show_usage
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
if [[ -z "$TARGET" ]]; then
|
||||
TARGET="$1"
|
||||
else
|
||||
print_error "Unexpected argument: $1"
|
||||
show_usage
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$TARGET" ]]; then
|
||||
print_error "Target router address is required."
|
||||
show_usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Normalize SSH target
|
||||
if [[ "$TARGET" != *"@"* ]]; then
|
||||
SSH_TARGET="root@${TARGET}"
|
||||
ROUTER_IP="${TARGET}"
|
||||
else
|
||||
SSH_TARGET="${TARGET}"
|
||||
ROUTER_IP="${TARGET#*@}"
|
||||
fi
|
||||
|
||||
# Check prerequisites
|
||||
for cmd in ssh scp mktemp; do
|
||||
if ! command -v "$cmd" &>/dev/null; then
|
||||
print_error "Required local command '$cmd' is not found in PATH."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Resolve workspace directory
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
|
||||
# Verify essential project files exist
|
||||
if [[ ! -f "${PROJECT_ROOT}/htdocs/luci-static/resources/view/device-manager/devices.js" ]]; then
|
||||
print_error "Missing frontend view file: htdocs/.../devices.js"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "${PROJECT_ROOT}/root/usr/share/luci/menu.d/luci-app-device-manager.json" ]]; then
|
||||
print_error "Missing menu definition file: root/.../menu.d/luci-app-device-manager.json"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "${PROJECT_ROOT}/root/usr/share/rpcd/acl.d/luci-app-device-manager.json" ]]; then
|
||||
print_error "Missing ACL definition file: root/.../acl.d/luci-app-device-manager.json"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for resource in model.js service.js preferences.js table.js device-dialog.js group-dialog.js styles.css; do
|
||||
if [[ ! -f "${PROJECT_ROOT}/htdocs/luci-static/resources/device-manager/${resource}" ]]; then
|
||||
print_error "Missing frontend resource: device-manager/${resource}"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ! -f "${PROJECT_ROOT}/root/usr/libexec/rpcd/luci.device-manager" || ! -f "${PROJECT_ROOT}/tools/initialize-config.sh" ]]; then
|
||||
print_error "Missing rpcd helper or configuration initializer."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Keep the control socket private and its path short enough for Unix socket limits.
|
||||
SSH_CONTROL_DIR="$(mktemp -d /tmp/luci-deploy.XXXXXX)"
|
||||
SSH_CONTROL_PATH="${SSH_CONTROL_DIR}/ssh"
|
||||
SSH_COMMON_OPTS=(-o ConnectTimeout=5 -o StrictHostKeyChecking=accept-new -o "ControlPath=${SSH_CONTROL_PATH}")
|
||||
SSH_OPTS=(-p "${SSH_PORT}" "${SSH_COMMON_OPTS[@]}" -o ControlMaster=no -o BatchMode=yes)
|
||||
SCP_OPTS=(-P "${SSH_PORT}" "${SSH_COMMON_OPTS[@]}" -o ControlMaster=no -o BatchMode=yes)
|
||||
|
||||
cleanup() {
|
||||
ssh "${SSH_OPTS[@]}" -O exit "${SSH_TARGET}" >/dev/null 2>&1 || true
|
||||
rm -rf -- "${SSH_CONTROL_DIR}"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
trap 'exit 130' INT
|
||||
trap 'exit 143' TERM
|
||||
|
||||
print_info "Connecting to OpenWrt router at ${SSH_TARGET} (port: ${SSH_PORT})..."
|
||||
|
||||
# Authenticate once and keep the master connection open until cleanup.
|
||||
if ! ssh -p "${SSH_PORT}" "${SSH_COMMON_OPTS[@]}" -o BatchMode=no -o ControlPersist=no -M -N -f "${SSH_TARGET}"; then
|
||||
print_error "Failed to connect to ${SSH_TARGET} via SSH. Please check network, IP, port, and SSH key/password."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! ssh "${SSH_OPTS[@]}" "${SSH_TARGET}" "command -v ip >/dev/null && command -v flock >/dev/null && test -r /usr/share/libubox/jshn.sh"; then
|
||||
print_error "Router is missing ip, flock or jshn. Standard OpenWrt BusyBox provides ip and flock; custom builds may need ip-tiny and flock. Install jshn if missing."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_info "Connection verified. Preparing remote directories..."
|
||||
|
||||
ssh "${SSH_OPTS[@]}" "${SSH_TARGET}" "
|
||||
mkdir -p /www/luci-static/resources/view/device-manager \
|
||||
/usr/share/luci/menu.d \
|
||||
/usr/share/rpcd/acl.d \
|
||||
/usr/lib/lua/luci/i18n \
|
||||
/usr/libexec/rpcd \
|
||||
/etc/uci-defaults \
|
||||
/etc/config
|
||||
"
|
||||
|
||||
print_info "Deploying shared frontend modules and stylesheet..."
|
||||
scp "${SCP_OPTS[@]}" -r \
|
||||
"${PROJECT_ROOT}/htdocs/luci-static/resources/device-manager" \
|
||||
"${SSH_TARGET}:/www/luci-static/resources/"
|
||||
|
||||
print_info "Deploying frontend view (devices.js)..."
|
||||
scp "${SCP_OPTS[@]}" \
|
||||
"${PROJECT_ROOT}/htdocs/luci-static/resources/view/device-manager/devices.js" \
|
||||
"${SSH_TARGET}:/www/luci-static/resources/view/device-manager/devices.js"
|
||||
|
||||
print_info "Deploying LuCI menu and rpcd ACL definitions..."
|
||||
scp "${SCP_OPTS[@]}" \
|
||||
"${PROJECT_ROOT}/root/usr/share/luci/menu.d/luci-app-device-manager.json" \
|
||||
"${SSH_TARGET}:/usr/share/luci/menu.d/luci-app-device-manager.json"
|
||||
|
||||
scp "${SCP_OPTS[@]}" \
|
||||
"${PROJECT_ROOT}/root/usr/share/rpcd/acl.d/luci-app-device-manager.json" \
|
||||
"${SSH_TARGET}:/usr/share/rpcd/acl.d/luci-app-device-manager.json"
|
||||
|
||||
# Native translations are installed as the matching luci-i18n package.
|
||||
# Remove files left by older development deployments without changing LuCI language.
|
||||
ssh "${SSH_OPTS[@]}" "${SSH_TARGET}" "
|
||||
rm -f /www/luci-static/resources/device-manager/i18n.js /www/luci-static/resources/device-manager/translations.js
|
||||
rm -f /usr/lib/lua/luci/i18n/device-manager-builtin.zh-cn.lmo /etc/uci-defaults/81_device_manager_i18n
|
||||
"
|
||||
|
||||
if [[ -f "${PROJECT_ROOT}/root/usr/libexec/rpcd/luci.device-manager" ]]; then
|
||||
print_info "Deploying rpcd helper backend script..."
|
||||
scp "${SCP_OPTS[@]}" \
|
||||
"${PROJECT_ROOT}/root/usr/libexec/rpcd/luci.device-manager" \
|
||||
"${SSH_TARGET}:/usr/libexec/rpcd/luci.device-manager"
|
||||
ssh "${SSH_OPTS[@]}" "${SSH_TARGET}" "chmod +x /usr/libexec/rpcd/luci.device-manager"
|
||||
fi
|
||||
|
||||
if [[ -f "${PROJECT_ROOT}/tools/initialize-config.sh" ]]; then
|
||||
print_info "Deploying development configuration initializer..."
|
||||
scp "${SCP_OPTS[@]}" \
|
||||
"${PROJECT_ROOT}/tools/initialize-config.sh" \
|
||||
"${SSH_TARGET}:/etc/uci-defaults/80_device_manager"
|
||||
fi
|
||||
|
||||
print_info "Ensuring configuration persistence (protecting existing user remarks)..."
|
||||
ssh "${SSH_OPTS[@]}" "${SSH_TARGET}" "
|
||||
set -e
|
||||
if [ ! -f /etc/config/device_manager ]; then
|
||||
echo 'Initializing default /etc/config/device_manager'
|
||||
else
|
||||
echo 'Existing /etc/config/device_manager preserved.'
|
||||
fi
|
||||
if [ -f /etc/uci-defaults/80_device_manager ]; then
|
||||
chmod +x /etc/uci-defaults/80_device_manager
|
||||
/etc/uci-defaults/80_device_manager
|
||||
rm -f /etc/uci-defaults/80_device_manager
|
||||
fi
|
||||
"
|
||||
|
||||
print_info "Clearing LuCI cache and restarting rpcd..."
|
||||
ssh "${SSH_OPTS[@]}" "${SSH_TARGET}" "
|
||||
rm -f /tmp/luci-indexcache* /tmp/luci-modulecache*
|
||||
/etc/init.d/rpcd restart
|
||||
"
|
||||
|
||||
print_success "Deployment completed successfully!"
|
||||
printf "\n"
|
||||
printf "${CYAN}================================================================${NC}\n"
|
||||
printf " ${GREEN}luci-app-device-manager${NC} is now active on your router.\n"
|
||||
printf "\n"
|
||||
printf " Access URL: ${YELLOW}http://%s/cgi-bin/luci/admin/network/device-manager${NC}\n" "${ROUTER_IP}"
|
||||
printf " Menu path: ${YELLOW}网络 -> 设备管理${NC}\n"
|
||||
printf "\n"
|
||||
printf " ${YELLOW}Note:${NC} If you are already logged in, please refresh the page\n"
|
||||
printf " with ${CYAN}Ctrl + Shift + R${NC} to clear browser JavaScript cache.\n"
|
||||
printf "${CYAN}================================================================${NC}\n"
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: MIT
|
||||
set -e
|
||||
|
||||
# Existing configuration, including an intentionally empty file, is authoritative.
|
||||
# Development deployment only; packages ship /etc/config/device_manager directly.
|
||||
[ -e /etc/config/device_manager ] && exit 0
|
||||
|
||||
touch /etc/config/device_manager
|
||||
trap 'uci -q revert device_manager || :; rm -f /etc/config/device_manager' EXIT
|
||||
uci -q batch <<'EOF'
|
||||
set device_manager.smart_home=group
|
||||
set device_manager.smart_home.name='Smart home'
|
||||
set device_manager.phone=group
|
||||
set device_manager.phone.name='Phones'
|
||||
set device_manager.computer=group
|
||||
set device_manager.computer.name='Computers'
|
||||
set device_manager.network=group
|
||||
set device_manager.network.name='Network devices'
|
||||
set device_manager.other=group
|
||||
set device_manager.other.name='Other devices'
|
||||
commit device_manager
|
||||
EOF
|
||||
trap - EXIT
|
||||
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Print a LuCI-style version from this source repository for standalone SDK builds."""
|
||||
import datetime
|
||||
import pathlib
|
||||
import subprocess
|
||||
|
||||
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def version():
|
||||
stamp, revision = subprocess.check_output(
|
||||
["git", "log", "-1", "--format=%ct %h", "--abbrev=7", "--", "."],
|
||||
cwd=ROOT, text=True).strip().split()
|
||||
moment = datetime.datetime.fromtimestamp(int(stamp), datetime.timezone.utc)
|
||||
return f"{moment:%y}.{moment.timetuple().tm_yday:03d}.{int(stamp) % 86400:05d}~{revision}"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(version())
|
||||
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Export only package sources into a LuCI checkout without modifying Git history."""
|
||||
import pathlib
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def prepare(directory):
|
||||
directory = pathlib.Path(directory).resolve()
|
||||
if not (directory / "luci.mk").is_file():
|
||||
raise ValueError("Expected a LuCI checkout containing luci.mk")
|
||||
target = directory / "applications/luci-app-device-manager"
|
||||
if target.exists():
|
||||
raise FileExistsError(f"Refusing to overwrite existing package: {target}")
|
||||
target.mkdir(parents=True)
|
||||
for name in ["htdocs", "root", "po"]:
|
||||
shutil.copytree(ROOT / name, target / name)
|
||||
for name in ["LICENSE"]:
|
||||
shutil.copyfile(ROOT / name, target / name)
|
||||
readme = (ROOT / "README.md").read_text()
|
||||
start = readme.index("## 安装 / Installation")
|
||||
end = readme.index("## 扫描范围 / Scan scope", start)
|
||||
readme = readme[:start] + (
|
||||
"## Installation\n\n"
|
||||
"On OpenWrt releases whose feeds include this application, install it\n"
|
||||
"from the configured package repositories.\n\n"
|
||||
"For systems using opkg:\n\n"
|
||||
"```sh\n"
|
||||
"opkg update\n"
|
||||
"opkg install luci-app-device-manager\n"
|
||||
"opkg install luci-i18n-device-manager-zh-cn\n"
|
||||
"```\n\n"
|
||||
"For systems using apk:\n\n"
|
||||
"```sh\n"
|
||||
"apk update\n"
|
||||
"apk add luci-app-device-manager\n"
|
||||
"apk add luci-i18n-device-manager-zh-cn\n"
|
||||
"```\n\n"
|
||||
"The Chinese language package is optional. Without a translation, LuCI\n"
|
||||
"displays the English source messages. Reload LuCI after installation\n"
|
||||
"and open **Network → Device Manager**.\n\n"
|
||||
"Dependencies are resolved by the package manager. Standard OpenWrt\n"
|
||||
"BusyBox supplies the required `ip` and `flock` applets. Custom firmware\n"
|
||||
"that disables these applets must enable them or install `ip-tiny`\n"
|
||||
"(or `ip-full`) and `flock` separately.\n\n"
|
||||
) + readme[end:]
|
||||
start = readme.index("## 构建和开发")
|
||||
end = readme.index("## 真实设备验收", start)
|
||||
readme = readme[:start] + (
|
||||
"## Development\n\n"
|
||||
"This application is built in the LuCI feed with the standard luci.mk.\n"
|
||||
"Independent SDK workflows, tests and deployment tools are maintained in\n"
|
||||
"[the source repository](https://github.com/xxi-arch/luci-app-device-manager).\n\n"
|
||||
) + readme[end:]
|
||||
(target / "README.md").write_text(readme)
|
||||
# Package-specific attribution and usage documentation, excluding release tooling.
|
||||
shutil.copytree(ROOT / "docs", target / "docs",
|
||||
ignore=shutil.ignore_patterns("upstream-submission.md", "validation.md"))
|
||||
makefile = (ROOT / "Makefile").read_text()
|
||||
start = makefile.index("# In-tree LuCI applications")
|
||||
end = makefile.index("# call BuildPackage", start)
|
||||
(target / "Makefile").write_text(makefile[:start] + "include ../../luci.mk\n\n" + makefile[end:])
|
||||
return target
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 2:
|
||||
sys.exit("Usage: prepare-upstream.py <luci-checkout>")
|
||||
print(prepare(sys.argv[1]))
|
||||
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Resolve an official release or snapshot SDK URL and checksum for the requested target."""
|
||||
import re
|
||||
import sys
|
||||
from html.parser import HTMLParser
|
||||
from urllib.request import urlopen
|
||||
|
||||
|
||||
class Links(HTMLParser):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.files = set()
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
if tag == "a":
|
||||
self.files.update(value for key, value in attrs if key == "href" and value)
|
||||
|
||||
|
||||
def resolve(version, target):
|
||||
if version != "SNAPSHOT" and not re.fullmatch(r"\d+\.\d+\.\d+(?:-rc\d+)?", version):
|
||||
raise ValueError("Expected SNAPSHOT or an OpenWrt release version, e.g. 24.10.0")
|
||||
if not re.fullmatch(r"[a-z0-9_-]+/[a-z0-9_-]+", target):
|
||||
raise ValueError("Expected an OpenWrt target/subtarget, e.g. x86/64")
|
||||
base = (f"https://downloads.openwrt.org/snapshots/targets/{target}/" if version == "SNAPSHOT"
|
||||
else f"https://downloads.openwrt.org/releases/{version}/targets/{target}/")
|
||||
with urlopen(base, timeout=30) as response:
|
||||
listing = response.read().decode("utf-8")
|
||||
links = Links()
|
||||
links.feed(listing)
|
||||
sdk_prefix = "openwrt-sdk-" if version == "SNAPSHOT" else f"openwrt-sdk-{re.escape(version)}-"
|
||||
pattern = rf"{sdk_prefix}[A-Za-z0-9_.+-]+\.Linux-x86_64\.tar\.(?:xz|zst)"
|
||||
matches = sorted(filename for filename in links.files if re.fullmatch(pattern, filename))
|
||||
if len(matches) != 1:
|
||||
raise ValueError(f"Expected one x86_64 host SDK at {base}; found {len(matches)}")
|
||||
filename = matches[0]
|
||||
with urlopen(base + "sha256sums", timeout=30) as response:
|
||||
checksums = response.read().decode("utf-8")
|
||||
for line in checksums.splitlines():
|
||||
match = re.fullmatch(r"([a-fA-F0-9]{64})\s+\*?(.+)", line)
|
||||
if match and match[2] == filename:
|
||||
return base + filename, match[1].lower()
|
||||
raise ValueError(f"No checksum found for {filename}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 3:
|
||||
sys.exit("Usage: resolve-sdk.py <release-version|SNAPSHOT> <target/subtarget>")
|
||||
sdk_url, sdk_sha256 = resolve(sys.argv[1], sys.argv[2])
|
||||
print(f"sdk_url={sdk_url}")
|
||||
print(f"sdk_sha256={sdk_sha256}")
|
||||
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Extract native LuCI messages and merge the PO catalogue using official tools."""
|
||||
import argparse
|
||||
import pathlib
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def extract(luci_dir):
|
||||
scanner = pathlib.Path(luci_dir).resolve() / "build/i18n-scan.pl"
|
||||
if not scanner.is_file():
|
||||
raise ValueError("Expected a LuCI checkout containing build/i18n-scan.pl")
|
||||
return subprocess.run(["perl", str(scanner), "htdocs", "root"], cwd=ROOT,
|
||||
check=True, capture_output=True, text=True).stdout
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--luci-dir", required=True, help="Official LuCI checkout")
|
||||
parser.add_argument("--check", action="store_true")
|
||||
args = parser.parse_args()
|
||||
template = ROOT / "po/templates/device-manager.pot"
|
||||
catalogue = ROOT / "po/zh_Hans/device-manager.po"
|
||||
content = extract(args.luci_dir)
|
||||
with tempfile.TemporaryDirectory(prefix="device-manager-i18n-") as directory:
|
||||
fresh = pathlib.Path(directory) / "device-manager.pot"
|
||||
fresh.write_text(content)
|
||||
if args.check:
|
||||
if template.read_text() != content:
|
||||
parser.error("Translation template is stale; regenerate with --luci-dir")
|
||||
subprocess.run(["msgcmp", "--use-fuzzy", str(catalogue), str(fresh)], check=True)
|
||||
subprocess.run(["msgfmt", "--check", "-o", str(pathlib.Path(directory) / "catalogue.mo"),
|
||||
str(catalogue)], check=True)
|
||||
else:
|
||||
template.write_text(content)
|
||||
subprocess.run(["msgmerge", "--update", "--no-fuzzy-matching", "--no-wrap",
|
||||
"--backup=none", str(catalogue), str(fresh)], check=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user