182 lines
4.9 KiB
Python
182 lines
4.9 KiB
Python
#!/usr/bin/env python3
|
|
|
|
import sys
|
|
|
|
import bussard.gen.types as t # for types
|
|
from bussard.reader import read
|
|
|
|
|
|
def format_time(num):
|
|
week = (7 * 24 * 60 * 60)
|
|
day = (24 * 60 * 60)
|
|
hour = (60 * 60)
|
|
minute = 60
|
|
|
|
if num % week == 0:
|
|
return f"{num//week}w"
|
|
elif num % day == 0:
|
|
return f"{num//day}d"
|
|
elif num % hour == 0:
|
|
return f"{num//hour}h"
|
|
elif num % minute == 0:
|
|
return f"{num//minute}m"
|
|
else:
|
|
return f"{num}s"
|
|
|
|
|
|
def format_comment(record):
|
|
return record.comment or "\n"
|
|
|
|
|
|
def format_record_name(record, cont=None, soa=None, name_width=None):
|
|
name = record.name
|
|
if name == soa.name:
|
|
name = "@"
|
|
if cont and name == cont.name:
|
|
name = " " * len(cont.name)
|
|
if name_width:
|
|
name = name.ljust(name_width)
|
|
return name
|
|
|
|
|
|
def format_record_ttl(record, ttl=None):
|
|
if ttl and record.ttl == ttl.ttl:
|
|
return ""
|
|
elif record.ttl:
|
|
return f"{record.ttl} "
|
|
else:
|
|
return ""
|
|
|
|
|
|
def format_record(record, cont=None, soa=None, ttl=None, name_width=None):
|
|
"""Given a single record, render it nicely."""
|
|
|
|
if isinstance(record, t.TTL):
|
|
return f"$TTL {format_time(record.ttl)}{format_comment(record)}"
|
|
|
|
elif isinstance(record, t.ORIGIN):
|
|
return f"$ORIGIN {record.name}{format_comment(record)}"
|
|
|
|
rname = format_record_name(record, cont=cont, soa=soa, name_width=name_width)
|
|
prefix = f"{rname} {format_record_ttl(record, ttl=ttl)}{record.type}"
|
|
|
|
if isinstance(record, t.SOA):
|
|
return f"""{prefix} SOA {record.mname} {record.rname} (
|
|
{record.serial: <10} ; serial
|
|
{format_time(record.refresh): <10} ; refresh after
|
|
{format_time(record.retry): <10} ; retry after
|
|
{format_time(record.expire): <10} ; expire after
|
|
{format_time(record.minimum): <10} ; negative cache
|
|
)"""
|
|
|
|
elif isinstance(record, t.A):
|
|
return f"""{prefix} A {record.address: <15}{format_comment(record)}"""
|
|
|
|
elif isinstance(record, t.AAAA):
|
|
return f"""{prefix} AAAA {record.address: <39}{format_comment(record)}"""
|
|
|
|
elif isinstance(record, t.CNAME):
|
|
return f"""{prefix} CNAME {record.cname}{format_comment(record)}"""
|
|
|
|
elif isinstance(record, t.MX):
|
|
return f"""{prefix} MX {record.preference} {record.exchange}{format_comment(record)}"""
|
|
|
|
elif isinstance(record, t.NS):
|
|
return f"""{prefix} NS {record.nsdname}{format_comment(record)}"""
|
|
|
|
elif isinstance(record, t.PTR):
|
|
return f"""{prefix} PTR {record.ptrdname}{format_comment(record)}"""
|
|
|
|
elif isinstance(record, t.TXT):
|
|
return f'''{prefix} TXT "{record.txt_data}"{format_comment(record)}'''
|
|
|
|
elif isinstance(record, t.SRV):
|
|
return f"""{prefix} SRV {record.priority} {record.weight} {record.port} {record.target}{format_comment(record)}"""
|
|
|
|
elif isinstance(record, t.RP):
|
|
return f"""{prefix} RP {record.mbox_dname} {record.txt_data}{format_comment(record)}"""
|
|
|
|
|
|
if __name__ == "__main__":
|
|
with open(sys.argv[1], "r") as f:
|
|
records = list(read(f.read()))
|
|
|
|
# Order records preferentially.
|
|
# $ORIGIN
|
|
# $TTL
|
|
# SOA
|
|
# $ORIGIN NS
|
|
# $ORIGIN MX
|
|
# then alphabetically by name.
|
|
# one space between groups.
|
|
|
|
origin = [r for r in records if isinstance(r, t.ORIGIN)]
|
|
if origin:
|
|
origin = origin[0]
|
|
else:
|
|
origin = None
|
|
|
|
ttl = [r for r in records if isinstance(r, t.TTL)]
|
|
if ttl:
|
|
ttl = ttl[0]
|
|
else:
|
|
ttl = None
|
|
|
|
soa = [r for r in records if isinstance(r, t.SOA)]
|
|
if soa:
|
|
soa = soa[0]
|
|
else:
|
|
soa = None
|
|
|
|
if soa and soa.name and not origin:
|
|
origin = t.ORIGIN(soa.name)
|
|
|
|
# Find the global nss and mxs
|
|
nss = [r for r in records if isinstance(r, t.NS) and r.name == origin.name]
|
|
mxs = [r for r in records if isinstance(r, t.MX) and r.name == origin.name]
|
|
|
|
# Sort the remaining records and comments
|
|
tail = [r for r in records
|
|
if (r != origin and r != ttl and r != soa and r not in nss and r not in mxs)]
|
|
|
|
def name_key(o):
|
|
if isinstance(o, str):
|
|
# It's a comment, sort by first word
|
|
return o.split()[0].replace(";", "").lower()
|
|
else:
|
|
# It's a record, return the name
|
|
return o.name
|
|
|
|
# Group chunks, preserving the original order.
|
|
chunk = []
|
|
chunks = [chunk]
|
|
for record in tail:
|
|
if record != "\n":
|
|
chunk.append(record)
|
|
elif chunk:
|
|
chunk = []
|
|
chunks.append(chunk)
|
|
|
|
# FIXME (arrdem 2020-02-01):
|
|
# Split chunks somehow???
|
|
# This is where the formater and linter diverge some.
|
|
|
|
# Now render
|
|
if origin.name != "@":
|
|
print(format_record(origin).strip())
|
|
if ttl:
|
|
print(format_record(ttl).strip())
|
|
print(format_record(soa, ttl=ttl, soa=soa).rstrip())
|
|
for ns in nss:
|
|
print(format_record(ns, cont=soa, ttl=ttl, soa=soa).rstrip())
|
|
for mx in mxs:
|
|
print(format_record(mx, cont=soa, ttl=ttl, soa=soa).rstrip())
|
|
for chunk in chunks:
|
|
if chunk:
|
|
width = max([len(r.name) if hasattr(r, "name") else 0 for r in chunk])
|
|
for record in chunk:
|
|
if isinstance(record, str):
|
|
print(record.rstrip())
|
|
else:
|
|
print(format_record(record, ttl=ttl, soa=soa, name_width=width).rstrip())
|
|
print()
|