| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- # -*- coding:utf-8 -*-
- """
- Configuration loader for DDNS command-line interface.
- @author: NewFuture
- """
- from argparse import Action, ArgumentParser, RawTextHelpFormatter, SUPPRESS
- from logging import getLevelName
- from os import path as os_path
- import platform
- import sys
- from .file import save_config
- __all__ = ["load_config", "str_bool"]
- def str_bool(v):
- # type: (str | bool | None | int | float | list) -> bool | str
- """
- parse string to boolean
- """
- if isinstance(v, bool):
- return v
- if v is None:
- return False
- if not isinstance(v, str) and not type(v).__name__ == "unicode":
- # For non-string types, convert to string first
- return bool(v)
- if v.lower() in ("yes", "true", "t", "y", "1"):
- return True
- elif v.lower() in ("no", "false", "f", "n", "0"):
- return False
- else:
- return v
- def log_level(value):
- """
- parse string to log level
- or getattr(logging, value.upper())
- """
- return getLevelName(value if isinstance(value, int) else value.upper())
- def _get_system_info_str():
- system = platform.system()
- release = platform.release()
- machine = platform.machine()
- arch = platform.architecture()
- return "{}-{} {} {}".format(system, release, machine, arch)
- def _get_python_info_str():
- version = platform.python_version()
- branch, py_build_date = platform.python_build()
- return "Python-{} {} ({})".format(version, branch, py_build_date)
- class ExtendAction(Action):
- """
- 兼容 Python <3.8 的 extend action
- """
- def __call__(self, parser, namespace, values, option_string=None):
- items = getattr(namespace, self.dest, None)
- if items is None:
- items = []
- # values 可能是单个值或列表
- if isinstance(values, list):
- items.extend(values)
- else:
- items.append(values)
- setattr(namespace, self.dest, items)
- class NewConfigAction(Action):
- """生成配置文件并退出程序"""
- def __call__(self, parser, namespace, values, option_string=None):
- # 获取配置文件路径
- if values and values != "true":
- config_path = str(values) # type: str
- else:
- config_path = getattr(namespace, "config", "config.json") # type: str
- if os_path.exists(config_path):
- sys.stderr.write("The default %s already exists!\n" % config_path)
- sys.stdout.write("Please use `--new-config=%s` to specify a new config file.\n" % config_path)
- sys.exit(1)
- # 获取当前已解析的参数
- current_config = {k: v for k, v in vars(namespace).items() if v is not None}
- # 保存配置文件
- save_config(config_path, current_config)
- sys.stdout.write("%s is generated.\n" % config_path)
- sys.exit(0)
- def load_config(description, doc, version, date):
- # type: (str, str, str, str) -> dict
- """
- 解析命令行参数并返回配置字典。
- Args:
- description (str): 程序描述
- doc (str): 程序文档
- version (str): 程序版本
- date (str): 构建日期
- Returns:
- dict: 配置字典
- """
- parser = ArgumentParser(description=description, epilog=doc, formatter_class=RawTextHelpFormatter)
- sysinfo = _get_system_info_str()
- pyinfo = _get_python_info_str()
- version_str = "v{} ({})\n{}\n{}".format(version, date, pyinfo, sysinfo)
- log_levels = [
- "CRITICAL", # 50
- "ERROR", # 40
- "WARNING", # 30
- "INFO", # 20
- "DEBUG", # 10
- "NOTSET", # 0
- ]
- parser.add_argument("-v", "--version", action="version", version=version_str)
- parser.add_argument(
- "-c",
- "--config",
- nargs="*",
- action=ExtendAction,
- metavar="FILE",
- help="load config file [配置文件路径, 可多次指定]",
- )
- parser.add_argument("--debug", action="store_true", help="debug mode [开启调试模式]")
- parser.add_argument(
- "--new-config", metavar="FILE", action=NewConfigAction, nargs="?", help="generate new config [生成配置文件]"
- )
- # 参数定义
- parser.add_argument(
- "--dns",
- help="DNS provider [DNS服务提供商]",
- choices=[
- "51dns",
- "alidns",
- "aliesa",
- "callback",
- "cloudflare",
- "debug",
- "dnscom",
- "dnspod_com",
- "dnspod",
- "edgeone",
- "he",
- "huaweidns",
- "namesilo",
- "noip",
- "tencentcloud",
- ],
- )
- parser.add_argument("--id", help="API ID or email [对应账号ID或邮箱]")
- parser.add_argument("--token", help="API token or key [授权凭证或密钥]")
- parser.add_argument("--endpoint", help="API endpoint URL [API端点URL]")
- parser.add_argument(
- "--index4",
- nargs="*",
- action=ExtendAction,
- metavar="RULE",
- help="IPv4 rules [获取IPv4方式, 多次可配置多规则]",
- )
- parser.add_argument(
- "--index6",
- nargs="*",
- action=ExtendAction,
- metavar="RULE",
- help="IPv6 rules [获取IPv6方式, 多次可配置多规则]",
- )
- parser.add_argument(
- "--ipv4",
- nargs="*",
- action=ExtendAction,
- metavar="DOMAIN",
- help="IPv4 domains [IPv4域名列表, 可配置多个域名]",
- )
- parser.add_argument(
- "--ipv6",
- nargs="*",
- action=ExtendAction,
- metavar="DOMAIN",
- help="IPv6 domains [IPv6域名列表, 可配置多个域名]",
- )
- parser.add_argument("--ttl", type=int, help="DNS TTL(s) [设置域名解析过期时间]")
- parser.add_argument("--line", help="DNS line/route [DNS线路设置,如电信、联通、移动等]")
- parser.add_argument(
- "--proxy",
- nargs="*",
- action=ExtendAction,
- help="HTTP proxy [设置http代理,可配多个代理连接]",
- )
- parser.add_argument(
- "--cache",
- type=str_bool,
- nargs="?",
- const=True,
- help="set cache [启用缓存开关,或传入保存路径]",
- )
- parser.add_argument(
- "--no-cache",
- dest="cache",
- action="store_const",
- const=False,
- help="disable cache [关闭缓存等效 --cache=false]",
- )
- parser.add_argument(
- "--ssl",
- type=str_bool,
- nargs="?",
- const=True,
- help="SSL certificate verification [SSL证书验证方式]: "
- "true(强制验证), false(禁用验证), auto(自动降级), /path/to/cert.pem(自定义证书)",
- )
- parser.add_argument(
- "--no-ssl",
- dest="ssl",
- action="store_const",
- const=False,
- help="disable SSL verify [禁用验证, 等效 --ssl=false]",
- )
- parser.add_argument("--log_file", metavar="FILE", help="log file [日志文件,默认标准输出]")
- parser.add_argument("--log.file", "--log-file", dest="log_file", help=SUPPRESS) # 隐藏参数
- parser.add_argument("--log_level", type=log_level, metavar="|".join(log_levels), help=None)
- parser.add_argument("--log.level", "--log-level", dest="log_level", type=log_level, help=SUPPRESS) # 隐藏参数
- parser.add_argument("--log_format", metavar="FORMAT", help="set log format [日志格式]")
- parser.add_argument("--log.format", "--log-format", dest="log_format", help=SUPPRESS) # 隐藏参数
- parser.add_argument("--log_datefmt", metavar="FORMAT", help="set log date format [日志时间格式]")
- parser.add_argument("--log.datefmt", "--log-datefmt", dest="log_datefmt", help=SUPPRESS) # 隐藏参数
- args = parser.parse_args()
- is_debug = getattr(args, "debug", False)
- if is_debug:
- # 如果启用调试模式,则强制设置日志级别为 DEBUG
- args.log_level = log_level("DEBUG")
- if args.cache is None:
- args.cache = False # 禁用缓存
- # 将 Namespace 对象转换为字典并直接返回
- config = vars(args)
- return {k: v for k, v in config.items() if v is not None} # 过滤掉 None 值的配置项
|