source/projects/bussard/src/python/bussard/bfmt
Reid 'arrdem' McKenzie c451d4cb00 Add bussard as-is
2021-08-03 08:43:06 -06:00

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()