just needs reserved prefix warnings implemented
This commit is contained in:
@@ -15,20 +15,187 @@ import (
|
||||
`subnetter/netsplit`
|
||||
)
|
||||
|
||||
/*
|
||||
tplClass4Iter should only be called if legacy info is enabled.
|
||||
It returns a tableLegacy4Sizer and a slice of tableLegacy4.
|
||||
// renderHdr renders a header. Note that the first line does *not* include the indent, but subsequent ones do.
|
||||
func renderHdr(titles []string, colSizes []uint8, indent string, plain bool, sb *strings.Builder) {
|
||||
|
||||
It takes no input.
|
||||
var idx int
|
||||
var width uint8
|
||||
var title string
|
||||
var lastTitleIdx int
|
||||
var tfmt *tableFormatter = tblFmts[plain]
|
||||
|
||||
if titles == nil || len(titles) == 0 || colSizes == nil || len(colSizes) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
lastTitleIdx = len(titles) - 1
|
||||
|
||||
// Top-most line/border.
|
||||
sb.WriteString(tfmt.TopLeftHdr)
|
||||
for idx, width = range colSizes {
|
||||
sb.WriteString(strings.Repeat(tfmt.TopFillHdr, int(width)))
|
||||
if idx == lastTitleIdx {
|
||||
sb.WriteString(tfmt.TopRightHdr)
|
||||
} else {
|
||||
sb.WriteString(tfmt.TopColSepHdr)
|
||||
}
|
||||
}
|
||||
sb.WriteString("\n")
|
||||
|
||||
// Title names.
|
||||
sb.WriteString(indent)
|
||||
sb.WriteString(tfmt.Left)
|
||||
for idx, title = range titles {
|
||||
width = colSizes[idx]
|
||||
if !tfmt.NoUpperTitle {
|
||||
title = strings.ToUpper(title)
|
||||
}
|
||||
if tfmt.NoBoldTitle {
|
||||
sb.WriteString(padStr(title, width))
|
||||
} else {
|
||||
sb.WriteString(color.InBold(padStr(title, width)))
|
||||
}
|
||||
if idx == lastTitleIdx {
|
||||
sb.WriteString(tfmt.Right)
|
||||
} else {
|
||||
sb.WriteString(tfmt.ColSepHdr)
|
||||
}
|
||||
}
|
||||
sb.WriteString("\n")
|
||||
|
||||
// Bottom header border.
|
||||
sb.WriteString(indent)
|
||||
sb.WriteString(tfmt.BottomLeftHdr)
|
||||
for idx, width = range colSizes {
|
||||
sb.WriteString(strings.Repeat(tfmt.BottomFillHdr, int(width)))
|
||||
if idx == lastTitleIdx {
|
||||
sb.WriteString(tfmt.BottomRightHdr)
|
||||
} else {
|
||||
sb.WriteString(tfmt.BottomColSepHdr)
|
||||
}
|
||||
}
|
||||
sb.WriteString("\n")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// renderRow renders a row. row should be a struct or non-nil struct ptr.
|
||||
func renderRow(row reflect.Value, colFields []int, colSizes []uint8, indent string, plain, isLast bool, sb *strings.Builder) {
|
||||
|
||||
var idx int
|
||||
var width uint8
|
||||
var fieldIdx int
|
||||
var valStr string
|
||||
var lastColIdx int
|
||||
var field reflect.Value
|
||||
var tfmt *tableFormatter = tblFmts[plain]
|
||||
|
||||
if colFields == nil || len(colFields) == 0 || colSizes == nil || len(colSizes) == 0 {
|
||||
return
|
||||
}
|
||||
if row.Kind() == reflect.Ptr && (row.IsNil() || row.Kind() != reflect.Struct) {
|
||||
return
|
||||
}
|
||||
row = reflect.Indirect(row)
|
||||
if row.Kind() != reflect.Struct {
|
||||
return
|
||||
}
|
||||
|
||||
lastColIdx = len(colFields) - 1
|
||||
|
||||
sb.WriteString(indent)
|
||||
sb.WriteString(tfmt.Left)
|
||||
for idx, fieldIdx = range colFields {
|
||||
width = colSizes[idx]
|
||||
field = row.Field(fieldIdx)
|
||||
switch field.Kind() {
|
||||
case reflect.String:
|
||||
valStr = field.String()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
valStr = strconv.FormatInt(field.Int(), 10)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
valStr = strconv.FormatUint(field.Uint(), 10)
|
||||
case reflect.Ptr:
|
||||
// *big.Int
|
||||
if field.IsNil() {
|
||||
continue
|
||||
}
|
||||
field = field.MethodByName("String").Call(nil)[0]
|
||||
valStr = field.String()
|
||||
}
|
||||
sb.WriteString(padStr(valStr, width))
|
||||
if idx == lastColIdx {
|
||||
sb.WriteString(tfmt.Right)
|
||||
} else {
|
||||
sb.WriteString(tfmt.ColSep)
|
||||
}
|
||||
}
|
||||
sb.WriteString("\n")
|
||||
|
||||
if !tfmt.SuppressLineSep || isLast {
|
||||
sb.WriteString(indent)
|
||||
if isLast {
|
||||
sb.WriteString(tfmt.LastLeft)
|
||||
} else {
|
||||
sb.WriteString(tfmt.LineLeft)
|
||||
}
|
||||
for idx, width = range colSizes {
|
||||
if isLast {
|
||||
sb.WriteString(strings.Repeat(tfmt.LastFill, int(width)))
|
||||
} else {
|
||||
sb.WriteString(strings.Repeat(tfmt.Fill, int(width)))
|
||||
}
|
||||
if idx == lastColIdx {
|
||||
if isLast {
|
||||
sb.WriteString(tfmt.LastRight)
|
||||
} else {
|
||||
sb.WriteString(tfmt.LineRight)
|
||||
}
|
||||
} else {
|
||||
if isLast {
|
||||
sb.WriteString(tfmt.LastSep)
|
||||
} else {
|
||||
sb.WriteString(tfmt.LineColSep)
|
||||
}
|
||||
}
|
||||
}
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// tplFmtBold renders text as bold-faced.
|
||||
func tplFmtBold(text string) (out string) {
|
||||
|
||||
out = color.InBold(text)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
tplTableLegacy4 renders a table of classes in the legacy "classed" IPv4 networking.
|
||||
|
||||
Note that indent is *not* applied to the first line.
|
||||
*/
|
||||
func tplClass4Iter() (legacySpec *tableLegacy4Ret, err error) {
|
||||
func tplTableLegacy4(indent string, plain bool) (out string, err error) {
|
||||
|
||||
// This whole thing feels dirty.
|
||||
// It's like adding a microcontroller to a rock.
|
||||
// But it works.
|
||||
var idx int
|
||||
var cls string
|
||||
var isLast bool
|
||||
var colFields []int
|
||||
var colSizes []uint8
|
||||
var colTitles []string
|
||||
var pfx *net.IPNet
|
||||
var row tableLegacy4
|
||||
var rows []tableLegacy4
|
||||
var rowVal reflect.Value
|
||||
var classNets []*netip.Prefix
|
||||
var netRange netipx.IPRange
|
||||
var sb *strings.Builder = new(strings.Builder)
|
||||
var v *netsplit.VLSMSplitter = &netsplit.VLSMSplitter{
|
||||
Ascending: false,
|
||||
PrefixLengths: []uint8{
|
||||
@@ -48,53 +215,124 @@ func tplClass4Iter() (legacySpec *tableLegacy4Ret, err error) {
|
||||
if classNets, _, err = v.Split(); err != nil {
|
||||
return
|
||||
}
|
||||
rows = make([]tableLegacy4, 5)
|
||||
|
||||
legacySpec = &tableLegacy4Ret{
|
||||
Sizer: &tableLegacy4Sizer{
|
||||
Class: 5, // "CLASS"
|
||||
CIDR: 4, // "BITS"
|
||||
Start: 5, // "START"
|
||||
End: 3, // "END"
|
||||
},
|
||||
Rows: make([]tableLegacy4, 5),
|
||||
}
|
||||
for idx, cls := range []string{
|
||||
for idx, cls = range []string{
|
||||
"A", "B", "C", "D", "E",
|
||||
} {
|
||||
legacySpec.Rows[idx] = tableLegacy4{
|
||||
rows[idx] = tableLegacy4{
|
||||
Class: cls,
|
||||
CIDR: classNets[idx].String(),
|
||||
NetCIDR: *classNets[idx],
|
||||
}
|
||||
netRange = netipx.RangeOfPrefix(legacySpec.Rows[idx].NetCIDR)
|
||||
legacySpec.Rows[idx].NetStart = netRange.From()
|
||||
legacySpec.Rows[idx].NetEnd = netRange.To()
|
||||
legacySpec.Rows[idx].Start = legacySpec.Rows[idx].NetStart.String()
|
||||
legacySpec.Rows[idx].End = legacySpec.Rows[idx].NetEnd.String()
|
||||
netRange = netipx.RangeOfPrefix(rows[idx].NetCIDR)
|
||||
rows[idx].NetStart = netRange.From()
|
||||
rows[idx].NetEnd = netRange.To()
|
||||
rows[idx].Start = rows[idx].NetStart.String()
|
||||
rows[idx].End = rows[idx].NetEnd.String()
|
||||
}
|
||||
|
||||
colFields, colTitles, colSizes = sizeStructs(rows)
|
||||
|
||||
// Header
|
||||
renderHdr(colTitles, colSizes, indent, plain, sb)
|
||||
|
||||
// Rows
|
||||
for idx, row = range rows {
|
||||
rowVal = reflect.ValueOf(row)
|
||||
isLast = idx == len(rows)-1
|
||||
renderRow(rowVal, colFields, colSizes, indent, plain, isLast, sb)
|
||||
}
|
||||
|
||||
out = sb.String()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
tplAddrIter takes a 4 or 6 for inet family/version and returns a tableAddrSizer and
|
||||
slice of tableAddr.
|
||||
tableAddr is sorted from smallest prefix/largest network to largest prefix/smallest network.
|
||||
tplTableMasks4 renders a table of netmasks for IPv4.
|
||||
IPv6 doesn't really use netmasks, instead relying almost entirely upon CIDR.
|
||||
|
||||
Note that indent is *not* applied to the first line.
|
||||
*/
|
||||
func tplAddrIter(ipVer uint8) (addrs *tableAddrRet, err error) {
|
||||
func tplTableMasks4(indent string, plain bool) (out string, err error) {
|
||||
|
||||
var dummyAddr netip.Addr
|
||||
var idx int
|
||||
var isLast bool
|
||||
var colFields []int
|
||||
var colSizes []uint8
|
||||
var colTitles []string
|
||||
var row tableMask4
|
||||
var rows []tableMask4
|
||||
var dummyNet *net.IPNet
|
||||
var l int
|
||||
var dummyAddr netip.Addr
|
||||
var rowVal reflect.Value
|
||||
var sb *strings.Builder = new(strings.Builder)
|
||||
|
||||
addrs = &tableAddrRet{
|
||||
Sizer: &tableAddrSizer{
|
||||
Prefix: 6, // "PREFIX"
|
||||
Bits: 4, // "BITS"
|
||||
Addresses: 9, // "ADDRESSES"
|
||||
Hosts: 5, // "HOSTS"
|
||||
},
|
||||
if dummyAddr, err = netip.ParseAddr("0.0.0.0"); err != nil {
|
||||
return
|
||||
}
|
||||
rows = make([]tableMask4, dummyAddr.BitLen()+1)
|
||||
|
||||
for idx = 0; idx <= dummyAddr.BitLen(); idx++ {
|
||||
rows[idx] = tableMask4{
|
||||
Prefix: fmt.Sprintf("/%d", idx),
|
||||
}
|
||||
if rows[idx].NetPrefix, err = dummyAddr.Prefix(idx); err != nil {
|
||||
return
|
||||
}
|
||||
dummyNet = netipx.PrefixIPNet(rows[idx].NetPrefix.Masked())
|
||||
rows[idx].Mask = dummyNet.Mask
|
||||
rows[idx].Netmask = netsplit.MaskFmt(
|
||||
dummyNet.Mask,
|
||||
"d", ".", "",
|
||||
1, 0,
|
||||
)
|
||||
rows[idx].Hex = dummyNet.Mask.String()
|
||||
rows[idx].Dec = binary.BigEndian.Uint32(dummyNet.Mask)
|
||||
rows[idx].Bin = netsplit.MaskFmt(
|
||||
dummyNet.Mask,
|
||||
"08b", ".", "",
|
||||
1, 0,
|
||||
)
|
||||
}
|
||||
|
||||
colFields, colTitles, colSizes = sizeStructs(rows)
|
||||
|
||||
// Header
|
||||
renderHdr(colTitles, colSizes, indent, plain, sb)
|
||||
|
||||
// Rows
|
||||
for idx, row = range rows {
|
||||
rowVal = reflect.ValueOf(row)
|
||||
isLast = idx == len(rows)-1
|
||||
renderRow(rowVal, colFields, colSizes, indent, plain, isLast, sb)
|
||||
}
|
||||
|
||||
out = sb.String()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
tplTableNotes renders a table of prefix notes IP version/inet family ipVer (4 or 6).
|
||||
|
||||
Note that indent is *not* applied to the first line.
|
||||
*/
|
||||
func tplTableNotes(ipVer uint8, indent string, plain bool) (out string, err error) {
|
||||
|
||||
var idx int
|
||||
var isLast bool
|
||||
var ok bool
|
||||
var note string
|
||||
var colFields []int
|
||||
var colSizes []uint8
|
||||
var colTitles []string
|
||||
var row tablePrefixNote
|
||||
var rows []tablePrefixNote
|
||||
var dummyAddr netip.Addr
|
||||
var rowVal reflect.Value
|
||||
var sb *strings.Builder = new(strings.Builder)
|
||||
|
||||
switch ipVer {
|
||||
case 4:
|
||||
@@ -109,339 +347,185 @@ func tplAddrIter(ipVer uint8) (addrs *tableAddrRet, err error) {
|
||||
err = errBadNet
|
||||
return
|
||||
}
|
||||
rows = make([]tablePrefixNote, 0, dummyAddr.BitLen()+1)
|
||||
|
||||
// Before we size, we generate the tableAddrs.
|
||||
addrs.Rows = make([]tableAddr, dummyAddr.BitLen()+1)
|
||||
for i := 0; i <= dummyAddr.BitLen(); i++ {
|
||||
addrs.Rows[i] = tableAddr{
|
||||
Prefix: uint8(i),
|
||||
Bits: uint8(dummyAddr.BitLen() - i),
|
||||
for idx = 0; idx <= dummyAddr.BitLen(); idx++ {
|
||||
if note, ok = netNotes[ipVer][uint8(idx)]; !ok {
|
||||
continue
|
||||
}
|
||||
if addrs.Rows[i].NetPrefix, err = dummyAddr.Prefix(i); err != nil {
|
||||
row = tablePrefixNote{
|
||||
Prefix: fmt.Sprintf("/%d", idx),
|
||||
Note: note,
|
||||
}
|
||||
if row.NetPrefix, err = dummyAddr.Prefix(idx); err != nil {
|
||||
return
|
||||
}
|
||||
dummyNet = netipx.PrefixIPNet(addrs.Rows[i].NetPrefix.Masked())
|
||||
addrs.Rows[i].Addresses = mapcidr.CountIPsInCIDR(true, true, dummyNet)
|
||||
addrs.Rows[i].Hosts = mapcidr.CountIPsInCIDR(false, false, dummyNet)
|
||||
rows = append(rows, row)
|
||||
}
|
||||
|
||||
// Now the sizer. The padding itself is handled in different logic, just need the length of the longest value as a string.
|
||||
for _, addr := range addrs.Rows {
|
||||
// I *abhor* walrus operators in anything but loops.
|
||||
l = len(strconv.Itoa(int(addr.Prefix)))
|
||||
if int(addrs.Sizer.Prefix) < l {
|
||||
addrs.Sizer.Prefix = uint8(l)
|
||||
}
|
||||
l = len(strconv.Itoa(int(addr.Bits)))
|
||||
if int(addrs.Sizer.Bits) < l {
|
||||
addrs.Sizer.Bits = uint8(l)
|
||||
}
|
||||
// Use the full numeric length.
|
||||
l = len(addr.Addresses.String())
|
||||
if int(addrs.Sizer.Addresses) < l {
|
||||
addrs.Sizer.Addresses = uint8(l)
|
||||
}
|
||||
l = len(addr.Hosts.String())
|
||||
if int(addrs.Sizer.Hosts) < l {
|
||||
addrs.Sizer.Hosts = uint8(l)
|
||||
}
|
||||
colFields, colTitles, colSizes = sizeStructs(rows)
|
||||
|
||||
// Header
|
||||
renderHdr(colTitles, colSizes, indent, plain, sb)
|
||||
|
||||
// Rows
|
||||
for idx, row = range rows {
|
||||
rowVal = reflect.ValueOf(row)
|
||||
isLast = idx == len(rows)-1
|
||||
renderRow(rowVal, colFields, colSizes, indent, plain, isLast, sb)
|
||||
}
|
||||
|
||||
out = sb.String()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
tplMaskIter4 returns a slice of IPv4 netmasks and returns a slice of tableMask4.
|
||||
Sorted from smallest prefix/largest network to largest prefix/smallest network.
|
||||
tplTablePrefixes renders a table of prefixes for IP version/inet family ipVer (4 or 6).
|
||||
|
||||
Note that indent is *not* applied to the first line.
|
||||
*/
|
||||
func tplMaskIter4() (masks *tableMask4Ret, err error) {
|
||||
func tplTablePrefixes(ipVer uint8, indent string, plain bool) (out string, err error) {
|
||||
|
||||
var dummyAddr netip.Addr
|
||||
var pfx netip.Prefix
|
||||
var idx int
|
||||
var isLast bool
|
||||
var colFields []int
|
||||
var colSizes []uint8
|
||||
var colTitles []string
|
||||
var row tablePrefix
|
||||
var rows []tablePrefix
|
||||
var dummyNet *net.IPNet
|
||||
var l int
|
||||
var dummyAddr netip.Addr
|
||||
var rowVal reflect.Value
|
||||
var sb *strings.Builder = new(strings.Builder)
|
||||
|
||||
masks = &tableMask4Ret{
|
||||
Sizer: &tableMask4Sizer{
|
||||
Prefix: 6, // "PREFIX"
|
||||
Netmask: 7, // "NETMASK"
|
||||
Hex: 3, // "HEX"
|
||||
Dec: 3, // "DEC"
|
||||
Bin: 3, // "BIN"
|
||||
},
|
||||
}
|
||||
|
||||
if dummyAddr, err = netip.ParseAddr("0.0.0.0"); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
masks.Rows = make([]tableMask4, dummyAddr.BitLen()+1)
|
||||
for i := 0; i <= dummyAddr.BitLen(); i++ {
|
||||
if pfx, err = dummyAddr.Prefix(i); err != nil {
|
||||
switch ipVer {
|
||||
case 4:
|
||||
if dummyAddr, err = netip.ParseAddr("0.0.0.0"); err != nil {
|
||||
return
|
||||
}
|
||||
dummyNet = netipx.PrefixIPNet(pfx.Masked())
|
||||
masks.Rows[i] = tableMask4{
|
||||
Prefix: uint8(i),
|
||||
Netmask: netsplit.MaskFmt(
|
||||
dummyNet.Mask,
|
||||
"d", ".", "",
|
||||
1, 0,
|
||||
),
|
||||
Hex: dummyNet.Mask.String(),
|
||||
Dec: binary.BigEndian.Uint32(dummyNet.Mask),
|
||||
Bin: netsplit.MaskFmt(
|
||||
dummyNet.Mask,
|
||||
"08b", ".", "",
|
||||
1, 0,
|
||||
),
|
||||
Mask: dummyNet.Mask,
|
||||
case 6:
|
||||
if dummyAddr, err = netip.ParseAddr("::"); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Now the sizer.
|
||||
for _, mask := range masks.Rows {
|
||||
l = len(strconv.Itoa(int(mask.Prefix)))
|
||||
if int(masks.Sizer.Prefix) < l {
|
||||
masks.Sizer.Prefix = uint8(l)
|
||||
}
|
||||
l = len(mask.Netmask)
|
||||
if int(masks.Sizer.Netmask) < l {
|
||||
masks.Sizer.Netmask = uint8(l)
|
||||
}
|
||||
l = len(mask.Hex)
|
||||
if int(masks.Sizer.Hex) < l {
|
||||
masks.Sizer.Hex = uint8(l)
|
||||
}
|
||||
l = len(strconv.FormatUint(uint64(mask.Dec), 10))
|
||||
if int(masks.Sizer.Dec) < l {
|
||||
masks.Sizer.Dec = uint8(l)
|
||||
}
|
||||
l = len(mask.Bin)
|
||||
if int(masks.Sizer.Bin) < l {
|
||||
masks.Sizer.Bin = uint8(l)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// do not include in template funcs; used externally
|
||||
func hdrRender(hdrVal reflect.Value, indent string, plain bool) (out string) {
|
||||
|
||||
var val reflect.Value
|
||||
var field reflect.StructField
|
||||
var fieldVal reflect.Value
|
||||
var colLen uint8
|
||||
var colTitle string
|
||||
var lastField int
|
||||
var valType reflect.Type
|
||||
var tfmt *tableFormatter = tblFmts[plain]
|
||||
var sb *strings.Builder = new(strings.Builder)
|
||||
|
||||
val = hdrVal
|
||||
valType = val.Type()
|
||||
|
||||
// Avoid the edge case where a struct's last field is skipped rendering
|
||||
for i := val.NumField(); i > 0; i-- {
|
||||
field = valType.Field(i - 1)
|
||||
if field.Tag.Get("render") == "-" {
|
||||
continue
|
||||
}
|
||||
lastField = i
|
||||
break
|
||||
}
|
||||
|
||||
// Top-most line.
|
||||
sb.WriteString(indent)
|
||||
sb.WriteString(tfmt.TopLeftHdr)
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
field = valType.Field(i)
|
||||
if field.Tag.Get("render") == "-" {
|
||||
continue
|
||||
}
|
||||
fieldVal = val.Field(i)
|
||||
colLen = uint8(fieldVal.Uint())
|
||||
sb.WriteString(strings.Repeat(tfmt.TopFillHdr, int(colLen)+fixedPad))
|
||||
if i == lastField {
|
||||
sb.WriteString(tfmt.TopRightHdr)
|
||||
} else {
|
||||
sb.WriteString(tfmt.TopColSepHdr)
|
||||
}
|
||||
}
|
||||
sb.WriteString("\n")
|
||||
|
||||
// Column titles
|
||||
sb.WriteString(indent)
|
||||
sb.WriteString(tfmt.Left)
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
field = valType.Field(i)
|
||||
if field.Tag.Get("render") == "-" {
|
||||
continue
|
||||
}
|
||||
fieldVal = val.Field(i)
|
||||
colLen = uint8(fieldVal.Uint()) + uint8(fixedPad)
|
||||
colTitle = field.Name
|
||||
if !tfmt.NoUpperTitle {
|
||||
colTitle = strings.ToUpper(colTitle)
|
||||
}
|
||||
if !tfmt.NoBoldTitle {
|
||||
sb.WriteString(color.InBold(padStr(colTitle, colLen)))
|
||||
} else {
|
||||
sb.WriteString(padStr(colTitle, colLen))
|
||||
}
|
||||
if i == lastField {
|
||||
sb.WriteString(tfmt.Right)
|
||||
} else {
|
||||
sb.WriteString(tfmt.ColSepHdr)
|
||||
}
|
||||
}
|
||||
sb.WriteString("\n")
|
||||
|
||||
// Header bottom line; headers always include bottom separators.
|
||||
sb.WriteString(indent)
|
||||
sb.WriteString(tfmt.BottomLeftHdr)
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
field = valType.Field(i)
|
||||
if field.Tag.Get("render") == "-" {
|
||||
continue
|
||||
}
|
||||
fieldVal = val.Field(i)
|
||||
colLen = uint8(fieldVal.Uint())
|
||||
sb.WriteString(strings.Repeat(tfmt.BottomFillHdr, int(colLen)+fixedPad))
|
||||
if i == lastField {
|
||||
sb.WriteString(tfmt.BottomRightHdr)
|
||||
} else {
|
||||
sb.WriteString(tfmt.BottomColSepHdr)
|
||||
}
|
||||
}
|
||||
sb.WriteString("\n")
|
||||
|
||||
out = sb.String()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// do not include in template funcs; used externally
|
||||
func hdrLineRender(hdrVal reflect.Value, indent string, plain bool, rowIdx int, numRows int) (out string) {
|
||||
|
||||
var val reflect.Value
|
||||
var field reflect.StructField
|
||||
var fieldVal reflect.Value
|
||||
var colLen uint8
|
||||
var lastField int
|
||||
var isLastLine bool
|
||||
var valType reflect.Type
|
||||
var tfmt *tableFormatter = tblFmts[plain]
|
||||
var sb *strings.Builder = new(strings.Builder)
|
||||
|
||||
isLastLine = rowIdx == (numRows - 1)
|
||||
if !isLastLine && tfmt.SuppressLineSep {
|
||||
default:
|
||||
err = errBadNet
|
||||
return
|
||||
}
|
||||
rows = make([]tablePrefix, dummyAddr.BitLen()+1)
|
||||
|
||||
val = hdrVal
|
||||
valType = val.Type()
|
||||
lastField = valType.NumField() - 1
|
||||
|
||||
for i := val.NumField(); i >= 0; i-- {
|
||||
field = valType.Field(i - 1)
|
||||
if field.Tag.Get("render") == "-" {
|
||||
continue
|
||||
for idx = 0; idx <= dummyAddr.BitLen(); idx++ {
|
||||
rows[idx] = tablePrefix{
|
||||
Prefix: fmt.Sprintf("/%d", idx),
|
||||
Bits: uint8(dummyAddr.BitLen() - idx),
|
||||
}
|
||||
lastField = i
|
||||
break
|
||||
if rows[idx].NetPrefix, err = dummyAddr.Prefix(idx); err != nil {
|
||||
return
|
||||
}
|
||||
dummyNet = netipx.PrefixIPNet(rows[idx].NetPrefix.Masked())
|
||||
rows[idx].Addresses = mapcidr.CountIPsInCIDR(true, true, dummyNet)
|
||||
rows[idx].Hosts = mapcidr.CountIPsInCIDR(false, false, dummyNet)
|
||||
}
|
||||
|
||||
sb.WriteString(indent)
|
||||
if isLastLine {
|
||||
sb.WriteString(tfmt.LastLeft)
|
||||
} else {
|
||||
sb.WriteString(tfmt.LineLeft)
|
||||
colFields, colTitles, colSizes = sizeStructs(rows)
|
||||
|
||||
// Header
|
||||
renderHdr(colTitles, colSizes, indent, plain, sb)
|
||||
|
||||
// Rows
|
||||
for idx, row = range rows {
|
||||
rowVal = reflect.ValueOf(row)
|
||||
isLast = idx == len(rows)-1
|
||||
renderRow(rowVal, colFields, colSizes, indent, plain, isLast, sb)
|
||||
}
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
field = valType.Field(i)
|
||||
if field.Tag.Get("render") == "-" {
|
||||
continue
|
||||
}
|
||||
fieldVal = val.Field(i)
|
||||
colLen = uint8(fieldVal.Uint())
|
||||
if isLastLine {
|
||||
sb.WriteString(strings.Repeat(tfmt.LastFill, int(colLen)+fixedPad))
|
||||
} else {
|
||||
sb.WriteString(strings.Repeat(tfmt.Fill, int(colLen)+fixedPad))
|
||||
}
|
||||
if i == lastField {
|
||||
if isLastLine {
|
||||
sb.WriteString(tfmt.LastRight)
|
||||
} else {
|
||||
sb.WriteString(tfmt.LineRight)
|
||||
}
|
||||
} else {
|
||||
if isLastLine {
|
||||
sb.WriteString(tfmt.LastSep)
|
||||
} else {
|
||||
sb.WriteString(tfmt.LineColSep)
|
||||
}
|
||||
}
|
||||
}
|
||||
sb.WriteString("\n")
|
||||
|
||||
out = sb.String()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// do not include in template funcs; used externally
|
||||
func rowRender(val reflect.Value, sizerVal reflect.Value, indent string, plain bool) (out string) {
|
||||
// rows should be a slice of structs/struct ptrs of all the same type (you're going to get errors/panics otherwise).
|
||||
func sizeStructs(rows any) (colFieldIdx []int, colTitles []string, colSizes []uint8) {
|
||||
|
||||
var title string
|
||||
var size int
|
||||
var numFields int
|
||||
var colIdx int
|
||||
var fieldIdx int
|
||||
var valLen int
|
||||
var valStr string
|
||||
var rowVals []reflect.Value
|
||||
var val reflect.Value
|
||||
var valType reflect.Type
|
||||
var field reflect.StructField
|
||||
var fieldVal reflect.Value
|
||||
var colLen uint8
|
||||
var sizerName string
|
||||
var sizerField reflect.Value
|
||||
var callVal string
|
||||
var valType reflect.Type = val.Type()
|
||||
var tfmt *tableFormatter = tblFmts[plain]
|
||||
var sb *strings.Builder = new(strings.Builder)
|
||||
|
||||
sb.WriteString(indent)
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
// Here be dragons. This is *NOT SAFE* for generic (heh) consumption.
|
||||
val = reflect.ValueOf(rows)
|
||||
if val.IsNil() || val.Type().Kind() != reflect.Slice || val.Len() == 0 {
|
||||
return
|
||||
}
|
||||
rowVals = make([]reflect.Value, val.Len())
|
||||
for idx := 0; idx < val.Len(); idx++ {
|
||||
rowVals[idx] = val.Index(idx)
|
||||
}
|
||||
|
||||
// I *think* this could potentially cause a panic if rows[0] is nil, BUT this func can't be used externally so it's Fine(TM).
|
||||
// Usage of it is tightly scoped.
|
||||
valType = reflect.Indirect(rowVals[0]).Type()
|
||||
if valType.Kind() != reflect.Struct {
|
||||
return
|
||||
}
|
||||
numFields = reflect.Indirect(rowVals[0]).NumField()
|
||||
colTitles = make([]string, 0, numFields)
|
||||
colSizes = make([]uint8, 0, numFields)
|
||||
colFieldIdx = make([]int, 0, numFields)
|
||||
|
||||
// Populate from the struct type first.
|
||||
for i := 0; i < numFields; i++ {
|
||||
field = valType.Field(i)
|
||||
if field.Tag.Get("render") == "-" {
|
||||
continue
|
||||
}
|
||||
sb.WriteString(tfmt.Left)
|
||||
fieldVal = val.Field(i)
|
||||
sizerName = field.Tag.Get("renderSizeName")
|
||||
if sizerName == "" {
|
||||
sizerName = field.Name
|
||||
title = field.Tag.Get("renderTitle")
|
||||
if title == "" {
|
||||
title = field.Name
|
||||
}
|
||||
sizerField = sizerVal.FieldByName(sizerName)
|
||||
colLen = uint8(sizerField.Uint()) + uint8(fixedPad)
|
||||
switch fieldVal.Kind() {
|
||||
// This is tailored specifically to this implementation.
|
||||
case reflect.String:
|
||||
sb.WriteString(fieldVal.String())
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
sb.WriteString(padStr(fmt.Sprintf("%d", fieldVal.Int()), colLen))
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
sb.WriteString(padStr(fmt.Sprintf("%d", fieldVal.Uint()), colLen))
|
||||
case reflect.Ptr:
|
||||
// It's a *big.Int.
|
||||
if fieldVal.IsNil() {
|
||||
sb.WriteString(padStr(strings.Repeat(padChars, int(colLen)), colLen))
|
||||
} else {
|
||||
// TIL you can even *do* this in reflection.
|
||||
size = len(title) + (fixedPad * len(padChars))
|
||||
colTitles = append(colTitles, title)
|
||||
colSizes = append(colSizes, uint8(size))
|
||||
colFieldIdx = append(colFieldIdx, i)
|
||||
}
|
||||
// colTitles is done.
|
||||
|
||||
// check each field in each row to see if its value's size is greater than the current.
|
||||
for _, row := range rowVals {
|
||||
val = reflect.Indirect(row)
|
||||
if val.Kind() != reflect.Struct {
|
||||
continue
|
||||
}
|
||||
for colIdx, fieldIdx = range colFieldIdx {
|
||||
fieldVal = val.Field(fieldIdx)
|
||||
// The row struct types only implement these primitives.
|
||||
switch fieldVal.Kind() {
|
||||
case reflect.String:
|
||||
valStr = fieldVal.String()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
valStr = strconv.FormatInt(fieldVal.Int(), 10)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
valStr = strconv.FormatUint(fieldVal.Uint(), 10)
|
||||
case reflect.Ptr:
|
||||
// *big.Int. No need to/don't fieldVal.Indirect(); .String() is a ptr method.
|
||||
// Sidenote, I didn't even know until today that reflection can *do* this.
|
||||
fieldVal = fieldVal.MethodByName("String").Call(nil)[0]
|
||||
callVal = fieldVal.String()
|
||||
sb.WriteString(padStr(callVal, colLen))
|
||||
valStr = fieldVal.String()
|
||||
}
|
||||
valLen = len(valStr) + (fixedPad * len(padChars))
|
||||
if valLen > int(colSizes[colIdx]) {
|
||||
colSizes[colIdx] = uint8(valLen)
|
||||
}
|
||||
}
|
||||
}
|
||||
sb.WriteString("\n")
|
||||
|
||||
out = sb.String()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user