Skip to content

Commit

Permalink
refactor: simplify the initialization of format
Browse files Browse the repository at this point in the history
  • Loading branch information
JinnLynn committed Jun 10, 2024
1 parent b5879d8 commit 18a0c5f
Show file tree
Hide file tree
Showing 12 changed files with 202 additions and 177 deletions.
153 changes: 112 additions & 41 deletions src/genpac/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from pprint import pprint # noqa: F401
from collections import OrderedDict
import json
import traceback

import requests
from requests.structures import CaseInsensitiveDict
Expand All @@ -26,6 +27,14 @@
'https://raw.githubusercontent.com/gfwlist/gfwlist/master/gfwlist.txt'


def register_option(parser, options, flag, *args,
conv=None, default=None, **kwargs):
flag = flag.lstrip('-').replace('_', '-')
if kwargs:
parser.add_argument(f'--{flag}', *args, **kwargs)
options[flag] = dict(conv=conv, default=default)


class GenPAC(object):
# 格式化器列表
_formaters = OrderedDict()
Expand All @@ -34,7 +43,6 @@ def __init__(self, config_file=None, argv_enabled=True):
self.config_file = config_file
self.argv_enabled = argv_enabled

self.default_opts = {}
self.init_dest = None
self.jobs = []
self.extra_rules = []
Expand All @@ -53,6 +61,86 @@ def walk_formaters(cls, attr, *args, **kargs):
for fmter in cls._formaters.values():
getattr(fmter['cls'], attr)(*args, **kargs)

def init_options(self):
options = {}
parser = argparse.ArgumentParser(
prog='genpac',
formatter_class=argparse.RawTextHelpFormatter,
description='获取gfwlist生成多种格式的翻墙工具配置文件, '
'支持自定义规则',
epilog=get_resource_data('res/rule-syntax.txt'),
argument_default=argparse.SUPPRESS,
add_help=False)
parser.add_argument(
'-v', '--version', action='version',
version=f'%(prog)s {get_version()}',
help='版本信息')
parser.add_argument(
'-h', '--help', action='help',
help='帮助信息')
parser.add_argument(
'--init', nargs='?', const=True, default=False, metavar='PATH',
help='初始化配置和用户规则文件')

group = parser.add_argument_group(
title='通用参数')

register_option(group, options, 'format', conv=conv_lower,
type=lambda s: s.lower(), choices=GenPAC._formaters.keys(),
help='生成格式, 只有指定了格式, 相应格式的参数才作用')

register_option(group, options, 'gfwlist-url', default=_GFWLIST_URL,
metavar='URL',
help='gfwlist网址,无此参数或URL为空则使用默认地址, URL为-则不在线获取')
register_option(group, options, 'gfwlist-local', conv=conv_path,
metavar='FILE',
help='本地gfwlist文件地址, 当在线地址获取失败时使用')
register_option(group, options, 'gfwlist-update-local', conv=conv_bool,
action='store_true',
help='当在线gfwlist成功获取且--gfwlist-local参数存在时, '
'更新gfwlist-local内容')
register_option(group, options, 'gfwlist-disabled', conv=conv_bool,
action='store_true', help='禁用gfwlist')
register_option(group, options, 'gfwlist-decoded-save', conv=conv_path,
metavar='FILE',
help='保存解码后的gfwlist, 仅用于测试')

register_option(group, options, 'user-rule', conv=conv_list,
action='append', metavar='RULE',
help='自定义规则, 允许重复使用或在单个参数中使用`,`分割多个规则,如:\n'
' --user-rule="@@sina.com" --user-rule="||youtube.com"\n'
' --user-rule="@@sina.com,||youtube.com"')
register_option(group, options, 'user-rule-from', conv=[conv_list, conv_path],
action='append', metavar='FILE',
help='从文件中读取自定义规则, 使用方法如--user-rule')
register_option(group, options, 'output', '-o',
metavar='FILE',
help='输出到文件, 无此参数或FILE为-, 则输出到stdout')
register_option(group, options, 'config-from', '-c', conv=conv_path,
metavar='FILE',
help='从文件中读取配置信息')
register_option(group, options, 'template', conv=conv_path,
metavar='FILE', help='自定义模板文件')

register_option(group, options, 'proxy',
metavar='PROXY',
help='在线获取外部数据时的代理, 如果可正常访问外部地址, 则无必要使用该选项\n'
'格式: [PROTOCOL:https://][USERNAME:PASSWORD@]HOST:PORT \n'
'其中协议、用户名、密码可选, 默认协议 http, 支持协议: http socks5 socks4 socks 如:\n'
' 127.0.0.1:8080\n'
' SOCKS5:https://127.0.0.1:1080\n'
' SOCKS5:https://username:[email protected]:1080\n')

register_option(group, options, 'etag-cache', conv=conv_bool,
action='store_true',
help='获取外部文件时是否使用If-None-Match头进行缓存检查')

register_option(group, options, '_order', conv=int, default=0)

self.__class__.walk_formaters('prepare', parser)

return parser, options

def parse_args(self):
# 如果某选项同时可以在配置文件和命令行中设定,则必须使default=None
# 以避免命令行中即使没指定该参数,也会覆盖配置文件中的值
Expand Down Expand Up @@ -129,7 +217,7 @@ def parse_args(self):
'--etag-cache', action='store_true', help='获取外部文件时是否使用If-None-Match头进行缓存检查'
)

self.__class__.walk_formaters('arguments', parser)
self.__class__.walk_formaters('init', parser)
return parser.parse_args()

def read_config(self, config_file):
Expand All @@ -156,11 +244,8 @@ def update_opt(self, args, cfgs, key,
if hasattr(args, dest):
v = getattr(args, dest)
else:
replaced = kwargs.get('replaced')
if key in cfgs:
v = cfgs[key]
elif replaced and replaced in cfgs:
v = cfgs[replaced]
else:
v = default

Expand All @@ -173,43 +258,27 @@ def update_opt(self, args, cfgs, key,
return dest, v

def parse_options(self):
args = self.parse_args() if self.argv_enabled else Namespace()
parser, opts = self.init_options()
args = parser.parse_args() if self.argv_enabled else Namespace()
# args = self.parse_args() if self.argv_enabled else Namespace()
self.init_dest = args.init if hasattr(args, 'init') else None
config_file = args.config_from if hasattr(args, 'config_from') else \
self.config_file

cfgs, self.default_opts = self.read_config(config_file)

opts = {}
opts['format'] = {'conv': conv_lower}

opts['gfwlist-url'] = {'default': _GFWLIST_URL}
opts['gfwlist-local'] = {'conv': conv_path}
opts['gfwlist-disabled'] = {'conv': conv_bool}
opts['gfwlist-update-local'] = {'conv': conv_bool}
opts['gfwlist-decoded-save'] = {'conv': conv_path}

opts['user-rule'] = {'conv': conv_list}
opts['user-rule-from'] = {'conv': [conv_list, conv_path]}

opts['output'] = {}
opts['template'] = {'conv': conv_path}
opts['proxy'] = {}
opts['etag-cache'] = {'conv': conv_bool}

opts['_order'] = {'conv': int, 'default': 0}

self.walk_formaters('config', opts)
for fmter in self.__class__._formaters.values():
opts.update(fmter['cls']._options)

self.clear_jobs()

job_cfgs, common_cfgs = self.read_config(config_file)

# 当配置没有[job]节点且参数没有--format 指定默认pac 可向前兼容
if not hasattr(args, 'format') and len(cfgs) == 1 and cfgs[0] == {}:
cfgs[0]['format'] = 'pac'
if not hasattr(args, 'format') and len(job_cfgs) == 1 and job_cfgs[0] == {}:
job_cfgs[0]['format'] = 'pac'

cfgs.extend(self.extra_jobs)
for c in cfgs:
cfg = self.default_opts.copy()
job_cfgs.extend(self.extra_jobs)
for c in job_cfgs:
cfg = common_cfgs.copy()
cfg.update(c)
job = Namespace.from_dict(
dict([(k, v) for k, v in cfg.items() if k in opts]))
Expand Down Expand Up @@ -242,6 +311,7 @@ def generate_all(self):
logger.debug(f'Job done: {job.format} => {job.output}')

def generate(self, job):
# logger.debug(f'Job Start: {job}')
if not job.format:
raise FatalError('生成的格式不能为空, 请检查参数--format或配置format.')
if job.format not in self._formaters:
Expand Down Expand Up @@ -490,6 +560,15 @@ def clear_cache(cls):
cls._cache.clear()


def run():
try:
gp = GenPAC()
gp.run()
except Exception as e:
logger.error(traceback.format_exc())
exit_error(e)


# 解析规则
# 参数 precise 影响返回格式
# precise = False 时 返回域名
Expand All @@ -501,14 +580,6 @@ def parse_rules(rules, precise=False):
return _parse_rule_precise(rules) if precise else _parse_rule(rules)


def run():
try:
gp = GenPAC()
gp.run()
except Exception as e:
exit_error(e)


# 普通解析
def _parse_rule(rules):
direct_lst = []
Expand Down
15 changes: 9 additions & 6 deletions src/genpac/format/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from os import path
import glob

from ..core import GenPAC
from ..core import GenPAC, register_option
from .. import TemplateFile, parse_rules
from ..util import error, replace_all, Namespace

Expand All @@ -26,6 +26,9 @@ def decorator(fmt_cls):
class FmtBase(object):
_name = ''
_desc = None
_parser = None
_options = {}

_default_tpl = None

def __init__(self, *args, **kwargs):
Expand All @@ -38,13 +41,13 @@ def __init__(self, *args, **kwargs):
kwargs.get('gfwlist_rules') or [])

@classmethod
def arguments(cls, parser):
return parser.add_argument_group(title=cls._name.upper(),
description=cls._desc or '')
def prepare(cls, parser):
cls._parser = parser.add_argument_group(title=cls._name.upper(),
description=cls._desc or '')

@classmethod
def config(cls, options):
pass
def register_option(cls, *args, **kwargs):
register_option(cls._parser, cls._options, *args, **kwargs)

def pre_generate(self):
return True
Expand Down
15 changes: 5 additions & 10 deletions src/genpac/format/copy.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,17 @@


@formater('copy', desc="IP地址列表")
class FMTCopy(FmtBase):
class FmtCopy(FmtBase):
_FORCE_IGNORE_GFWLIST = True

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

@classmethod
def arguments(cls, parser):
group = super().arguments(parser)
group.add_argument('--copy-from', metavar='SRC',
help='来源, 网址或文件路径')
return group

@classmethod
def config(cls, options):
options['copy-from'] = {}
def prepare(cls, parser):
super().prepare(parser)
cls.register_option('copy-from',
metavar='SRC', help='来源, 网址或文件路径')

def generate(self, replacements):
content = ''
Expand Down
25 changes: 9 additions & 16 deletions src/genpac/format/deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,22 +38,15 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

@classmethod
def arguments(cls, parser):
group = super().arguments(parser)
group.add_argument(
'--wingy-adapter-opts', metavar='OPTS',
help='adapter选项, 选项间使用`,`分割, 多个adapter使用`;`分割, 如:\n'
' id:ap1,type:http,host:127.0.0.1,port:8080;'
'id:ap2,type:socks5,host:127.0.0.1,port:3128')
group.add_argument(
'--wingy-rule-adapter-id', metavar='ID',
help='生成规则使用的adapter ID')
return group

@classmethod
def config(cls, options):
options['wingy-adapter-opts'] = {}
options['wingy-rule-adapter-id'] = {}
def prepare(cls, parser):
super().prepare(parser)
cls.register_option('wingy-adapter-opts',
metavar='OPTS',
help='adapter选项, 选项间使用`,`分割, 多个adapter使用`;`分割, 如:\n'
' id:ap1,type:http,host:127.0.0.1,port:8080;'
'id:ap2,type:socks5,host:127.0.0.1,port:3128')
cls.register_option('wingy-rule-adapter-id',
metavar='ID', help='生成规则使用的adapter ID')

def generate(self, replacements):
fmt = '{:>8}'.format(' ')
Expand Down
20 changes: 18 additions & 2 deletions src/genpac/format/dnsmasq.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,40 @@
import itertools

from .base import FmtBase, formater
from ..util import conv_list
from .base import FmtBase, formater

_TPL = '''
#! __GENPAC__
__DNSMASQ__
#! Generated: __GENERATED__
#! GFWList: __GFWLIST_DETAIL__
'''
_DEF_DNS = '127.0.0.1#53'


@formater('dnsmasq', desc='Dnsmasq配合iptables/ipset、nftables/nftset可实现基于域名的透明代理.')
class FmtDnsmasq(FmtBase):
_default_tpl = _TPL
_default_dns = '127.0.0.1#53'

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

@classmethod
def prepare(cls, parser):
super().prepare(parser)
cls.register_option('dnsmasq-dns', default=_DEF_DNS,
metavar='DNS',
help='生成规则域名查询使用的DNS服务器,格式: HOST#PORT\n'
f'默认: {_DEF_DNS}')
cls.register_option('dnsmasq-ipset', conv=conv_list,
action='append', metavar='IPSET',
help='使用ipset, 允许重复或使用`,`分割多个, \n'
'如: GFWLIST,GFWLIST6')
cls.register_option('dnsmasq-nftset', conv=conv_list,
action="append", metavar='NFTSET',
help='使用ntfset, 允许重复或使用`,`分割多个, \n'
'如: 4#GFWLIST,6#GFWLIST6')

@classmethod
def arguments(cls, parser):
group = super().arguments(parser)
Expand Down
Loading

0 comments on commit 18a0c5f

Please sign in to comment.