From 450ffb89ae8dd53f7b93ecfd8872e79b560a1f87 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 7 Aug 2026 11:36:45 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20=5Freplace=5For=5Fappend?= =?UTF-8?q?=5Foption=20=E5=87=BD=E6=95=B0=E7=9A=84=E5=A4=8D=E6=9D=82?= =?UTF-8?q?=E5=8C=B9=E9=85=8D=E9=80=BB=E8=BE=91bug=EF=BC=8C=E7=AE=80?= =?UTF-8?q?=E5=8C=96=E5=AE=9E=E7=8E=B0=E7=A1=AE=E4=BF=9D=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E6=9B=BF=E6=8D=A2=E6=AD=A3=E7=A1=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.py | 181 ++++++++++++++++++++++----------------------------------- 1 file changed, 69 insertions(+), 112 deletions(-) diff --git a/app.py b/app.py index d8bf6db..2348308 100644 --- a/app.py +++ b/app.py @@ -1111,78 +1111,69 @@ def _parse_recursion(content): def _replace_or_append_option(content, directive, new_block): - """Replace an existing 'directive { ... };' or 'directive ;' in an - options block with new_block, or append it after the opening 'options {' if - not present. - - `new_block` is the full directive text (without leading/trailing newline). - Handles: - - brace-delimited: 'forwarders { ... };' / 'allow-recursion { ... };' - - value-terminated: 'dnssec-validation auto;' / 'recursion yes;' - Nested braces are matched by counting depth. Comments ('//', '#', '/*...*/') - are skipped so the directive name doesn't match inside comment text. + """Replace an existing directive or append it inside the options block. + + Simple and reliable version: + 1. First try to match brace-delimited directives: 'directive { ... };' + 2. Then try to match value-terminated directives: 'directive ;' + 3. If not found, append after 'options {' opening line """ - # Build a mask of which character ranges are inside comments. We treat the - # whole file linearly: - # - '//' to end-of-line: line comment (BIND and shell style) - # - '#' to end-of-line: line comment (BIND 9.18+ accepts # too) - # - '/* ... */': block comment - n = len(content) - in_comment = [False] * n # True at position i = i is inside a comment - - i = 0 - while i < n: - c = content[i] - # End of block comment - if in_comment[i] is False and i + 1 < n and c == '/' and content[i + 1] == '*': - j = i + 2 - depth = 1 - while j < n and depth > 0: - if j + 1 < n and content[j] == '*' and content[j + 1] == '/': - depth -= 1 - j += 2 - else: - j += 1 - for k in range(i, j): - in_comment[k] = True - i = j - continue - # Line comment // ... \n - if in_comment[i] is False and i + 1 < n and c == '/' and content[i + 1] == '/': - j = i - while j < n and content[j] != '\n': - in_comment[j] = True - j += 1 - i = j - continue - # Line comment # ... \n (skip only if at start of token; be conservative - # and treat any '#' preceded by whitespace or start-of-line as a comment) - if in_comment[i] is False and c == '#': - # Only treat as comment if preceded by whitespace or start-of-line - prev_ok = (i == 0) or content[i - 1] in ' \t' - if prev_ok: - j = i - while j < n and content[j] != '\n': - in_comment[j] = True - j += 1 - i = j - continue - i += 1 - - pattern = re.compile( - r'(? 0: + if content[i] == '{': + depth += 1 + elif content[i] == '}': + depth -= 1 + i += 1 + + if depth == 0: + # Found matching }, now find the trailing ; + end = i + while end < len(content) and content[end] in ' \t\n': + end += 1 + if end < len(content) and content[end] == ';': + end += 1 + + # Find indentation from original line + line_start = content.rfind('\n', 0, start) + 1 + indent = '' + for ch in content[line_start:start]: + if ch in ' \t': + indent += ch + else: + break + + indented = '\n'.join( + (indent + line) if line else line + for line in new_block.split('\n') + ) + return content[:start] + indented + content[end:] + + # Pattern 2: value-terminated (simple values without braces) + pattern_value = re.compile( + r'(?= len(content): - continue - - if content[i] == '{': - depth = 0 - for j in range(i, len(content)): - # Skip over comments inside the brace body - if in_comment[j]: - continue - c = content[j] - if c == '{': - depth += 1 - elif c == '}': - depth -= 1 - if depth == 0: - end = j + 1 - while end < len(content) and content[end] in ' \t\n': - end += 1 - if end < len(content) and content[end] == ';': - end += 1 - indented = '\n'.join( - (indent + line) if line else line - for line in new_block.split('\n') - ) - return content[:start] + indented + content[end:] - continue - else: - end = i - while end < len(content) and content[end] != ';': - end += 1 - if end < len(content): - end += 1 - indented = '\n'.join( - (indent + line) if line else line - for line in new_block.split('\n') - ) - return content[:start] + indented + content[end:] - + + indented = '\n'.join( + (indent + line) if line else line + for line in new_block.split('\n') + ) + return content[:start] + indented + content[end:] + # Not found — append after the opening "options {" line opt_match = re.search(r'options\s*\{', content) if opt_match: @@ -1243,6 +1198,8 @@ def _replace_or_append_option(content, directive, new_block): indented = '\n'.join(' ' + line if line else line for line in new_block.split('\n')) indented = '\n' + indented + '\n' return content[:insert_at] + indented + content[insert_at:] + + # No options block at all — create one return 'options {\n' + new_block + '\n};\n'