[Git][NTPsec/ntpsec][master] Added string based formatters and associated tests.
Eric S. Raymond
gitlab at mg.gitlab.com
Tue Apr 11 16:32:10 UTC 2017
Eric S. Raymond pushed to branch master at NTPsec / ntpsec
Commits:
5e95e918 by Ian Bruene at 2017-04-11T09:32:52-05:00
Added string based formatters and associated tests.
- - - - -
2 changed files:
- pylib/util.py
- tests/pylib/test_util.py
Changes:
=====================================
pylib/util.py
=====================================
--- a/pylib/util.py
+++ b/pylib/util.py
@@ -93,6 +93,33 @@ def zerowiggle(ooms):
return 10 ** -ooms
+def stringfiltcooker(data):
+ "Cooks a filt* string of space seperated numbers, expects milliseconds"
+ parts = data.split()
+ oomcount = {}
+ # Find out what the 'natural' unit of each value is
+ for part in parts:
+ value, oom = scalestring(part)
+ oomcount[oom] = oomcount.get(oom, 0) + 1
+ # Find the most common unit
+ mostcommon = None
+ highestcount = 0
+ for key in oomcount.keys():
+ count = oomcount[key]
+ if count > highestcount:
+ mostcommon = key
+ highestcount = count
+ newunit = UNITS_SEC[mostcommon + UNIT_MS]
+ # Shift all values to the new unit
+ cooked = []
+ for part in parts:
+ part = rescalestring(part, mostcommon)
+ fitted = fitinfield(part, 7)
+ cooked.append(fitted)
+ rendered = " ".join(cooked) + " " + newunit
+ return rendered
+
+
def filtcooker(data):
"Cooks the string of space seperated numbers with units"
parts = data.split()
@@ -181,6 +208,165 @@ def oomsbetweenunits(a, b):
return abs((a - b) * 3)
+def breaknumberstring(value):
+ "Breaks a number string into (aboveDecimal, belowDecimal, isNegative?)"
+ if value[0] == "-":
+ value = value[1:]
+ negative = True
+ else:
+ negative = False
+ if "." in value:
+ above, below = value.split(".")
+ else:
+ above = value
+ below = ""
+ return (above, below, negative)
+
+
+def gluenumberstring(above, below, isnegative):
+ "Glues together parts of a number string"
+ if above == "":
+ above = "0"
+ if len(below) > 0:
+ newvalue = ".".join((above, below))
+ else:
+ newvalue = above
+ if isnegative is True:
+ newvalue = "-" + newvalue
+ return newvalue
+
+
+def rescalestring(value, unitsscaled):
+ "Rescale a number string by a given number of units"
+ whole, dec, negative = breaknumberstring(value)
+ if unitsscaled == 0:
+ if whole == "": # render .1 as 0.1
+ whole = "0"
+ value = gluenumberstring(whole, dec, negative)
+ return value
+ hilen = len(whole)
+ lolen = len(dec)
+ digitsmoved = abs(unitsscaled * 3)
+ if unitsscaled > 0: # Scale to a larger unit
+ if hilen < digitsmoved: # Scaling beyond the digits, pad it out
+ padcount = digitsmoved - hilen
+ newwhole = "0"
+ newdec = ("0" * padcount) + whole + dec
+ else: # Scaling in the digits, no need to pad
+ choppoint = -digitsmoved
+ newdec = whole[choppoint:] + dec
+ newwhole = whole[:choppoint]
+ if newwhole == "":
+ newwhole = "0"
+ elif unitsscaled < 0: # scale to a smaller unit
+ if lolen < digitsmoved: # Scaling beyone the digits, pad it out
+ padcount = digitsmoved - lolen
+ newwhole = whole + dec + ("0" * padcount)
+ newdec = ""
+ else:
+ newwhole = whole + dec[:digitsmoved]
+ newdec = dec[digitsmoved:]
+ newvalue = gluenumberstring(newwhole, newdec, negative)
+ return newvalue
+
+
+def scalestring(value):
+ "Scales a number string to fit in the range 1.0-999.9"
+ whole, dec, negative = breaknumberstring(value)
+ hilen = len(whole)
+ if (hilen == 0) or isstringzero(whole): # Need to shift to smaller units
+ i = 0
+ lolen = len(dec)
+ while i < lolen: # need to find the actual digits
+ if dec[i] != "0":
+ break
+ i += 1
+ if i == lolen: # didn't find anything, this number must equal zero
+ newwhole = whole
+ newdec = dec
+ negative = False # filter our -0.000
+ unitsmoved = 0
+ else:
+ lounits = (i // 3) + 1 # always need to shift one more unit
+ movechars = lounits * 3
+ newwhole = dec[:movechars].lstrip('0')
+ newdec = dec[movechars:]
+ unitsmoved = -lounits
+ else: # Shift to larger units
+ hiunits = hilen // 3 # How many we have, not how many to move
+ hidigits = hilen % 3
+ if hidigits == 0: # full unit above the decimal
+ hiunits -= 1 # the unit above the decimal doesn't count
+ hidigits = 3
+ newwhole = whole[:hidigits]
+ newdec = whole[hidigits:] + dec
+ unitsmoved = hiunits
+ newvalue = gluenumberstring(newwhole, newdec, negative)
+ return (newvalue, unitsmoved)
+
+
+def fitinfield(value, fieldsize):
+ "Attempt to fit value into a field, preserving as much data as possible"
+ vallen = len(value)
+ if fieldsize is None:
+ newvalue = value
+ elif vallen == fieldsize: # Goldilocks!
+ newvalue = value
+ elif vallen < fieldsize: # Extra room, pad it out
+ pad = " " * (fieldsize - vallen)
+ newvalue = pad + value
+ else: # Insufficient room, crop as few digits as possible
+ above, below, neg = breaknumberstring(value)
+ diff = vallen - fieldsize
+ bellen = len(below)
+ croplen = min(bellen, diff) # Never crop above the decimal point
+ newvalue = value[:-croplen]
+ return newvalue
+
+
+def cropprecision(value, ooms):
+ if "." not in value: # No decimals, nothing to crop
+ return value
+ if ooms == 0: # We are at the baseunit, crop it all
+ return value.split(".")[0]
+ dstart = value.find(".") + 1
+ dsize = len(value) - dstart
+ precision = min(ooms, dsize)
+ cropcount = dsize - precision
+ if cropcount > 0:
+ value = value[:-cropcount]
+ return value
+
+
+def isstringzero(value):
+ for i in value:
+ if i not in ("-", ".", "0"):
+ return False
+ return True
+
+
+def unitify(value, unitgroup, startingunit, baseunit=0,
+ strip=False, width=8):
+ "Formats a numberstring with relevant units. Attemps to fit in width."
+ if isstringzero(value) is True: # display highest precision zero
+ base = unitgroup[baseunit]
+ if strip is False:
+ newvalue = fitinfield("0", width - len(base)) + base
+ return newvalue
+ ooms = oomsbetweenunits(startingunit, baseunit)
+ newvalue = cropprecision(value, ooms)
+ newvalue, unitsmoved = scalestring(newvalue)
+ unitget = startingunit + unitsmoved
+ if 0 <= unitget < len(unitgroup): # We have a unit
+ unit = unitgroup[unitget]
+ newvalue = fitinfield(newvalue, width - len(unit)) + unit
+ else: # don't have a replacement unit, use original
+ newvalue = value + unitgroup[startingunit]
+ if strip is True:
+ newvalue = newvalue.strip()
+ return newvalue
+
+
def unitformatter(f, unitgroup, startingunit, baseunit=None,
strip=False, width=8):
"Formatting for unit associated values in N characters."
=====================================
tests/pylib/test_util.py
=====================================
--- a/tests/pylib/test_util.py
+++ b/tests/pylib/test_util.py
@@ -152,5 +152,188 @@ class TestPylibUtilMethods(unittest.TestCase):
self.assertEqual(f(10000.1, usec, ntp.util.UNIT_KS),
"10000.1ks") # Value outside of unit ranges
+ def test_scalestring(self):
+ f = ntp.util.scalestring
+
+ # Scale all decimals
+ self.assertEqual(f("0.042"), ("42", -1))
+ # Typical length, positive value, no scaling
+ self.assertEqual(f("1.23450"), ("1.23450", 0))
+ # Ditto, negative
+ self.assertEqual(f("-1.23450"), ("-1.23450", 0))
+ # Long, positive value, no scaling
+ self.assertEqual(f("1.234567890123456"), ("1.234567890123456", 0))
+ # ditto, negative
+ self.assertEqual(f("-1.234567890123456"), ("-1.234567890123456", 0))
+ # Zero
+ self.assertEqual(f("0.000000"), ("0.000000", 0))
+ # Negative zero
+ self.assertEqual(f("-0.000"), ("0.000", 0))
+ # Large, positive, non scaled
+ self.assertEqual(f("987.654"), ("987.654", 0))
+ # ditto, negative
+ self.assertEqual(f("-987.654"), ("-987.654", 0))
+ # Scaled to larger unit, positive
+ self.assertEqual(f("12345.67890"), ("12.34567890", 1))
+ # ditto, negative
+ self.assertEqual(f("-12345.67890"), ("-12.34567890", 1))
+ # Scaled to smaller unit, position
+ self.assertEqual(f("0.1234567890"), ("123.4567890", -1))
+ # ditto, negative
+ self.assertEqual(f("-0.1234567890"), ("-123.4567890", -1))
+ # Bizzare 1
+ self.assertEqual(f("-000.000012345678900987654321"),
+ ("-12.345678900987654321", -2))
+ # Bizzare 2
+ self.assertEqual(f("1234567890987654321000.00000000000042"),
+ ("1.23456789098765432100000000000000042", 7))
+ # Bizzare 3
+ self.assertEqual(f("00000000.000000000000"),
+ ("00000000.000000000000", 0))
+
+ def test_rescalestring(self):
+ f = ntp.util.rescalestring
+
+ # No-op
+ self.assertEqual(f("1000.42", 0), "1000.42")
+ # Scale to higher unit
+ self.assertEqual(f("123.456", 1), "0.123456")
+ # ditto, negative
+ self.assertEqual(f("-123.456", 1), "-0.123456")
+ # Scale to higher unit, beyond available digits
+ self.assertEqual(f("12.34", 2), "0.00001234")
+ # ditto, negative
+ self.assertEqual(f("-12.34", 2), "-0.00001234")
+ # Scale to lower unit
+ self.assertEqual(f("1.23456", -1), "1234.56")
+ # ditto, negative
+ self.assertEqual(f("-1.23456", -1), "-1234.56")
+ # Scale to lower unit, beyond available digits
+ self.assertEqual(f("1.23456", -2), "1234560")
+ # ditto, negative
+ self.assertEqual(f("-1.23456", -2), "-1234560")
+
+ def test_breaknumberstring(self):
+ f = ntp.util.breaknumberstring
+
+ # No decimals, positive
+ self.assertEqual(f("1234567890"), ("1234567890", "", False))
+ # ditto, negative
+ self.assertEqual(f("-1234567890"), ("1234567890", "", True))
+ # No whole, positive
+ self.assertEqual(f(".12345"), ("", "12345", False))
+ # ditto, negative
+ self.assertEqual(f("-.12345"), ("", "12345", True))
+ # Both sides, position
+ self.assertEqual(f("123.456"), ("123", "456", False))
+ # ditto, negative
+ self.assertEqual(f("-123.456"), ("123", "456", True))
+
+ def test_gluenumberstring(self):
+ f = ntp.util.gluenumberstring
+
+ # No decimals, positive
+ self.assertEqual(f("123", "", False), "123")
+ # ditto, negative
+ self.assertEqual(f("123", "", True), "-123")
+ # No whole, positive
+ self.assertEqual(f("", "456", False), "0.456")
+ # ditto, negative
+ self.assertEqual(f("", "456", True), "-0.456")
+ # Both sides, positive
+ self.assertEqual(f("123", "456", False), "123.456")
+ # ditto, negative
+ self.assertEqual(f("123", "456", True), "-123.456")
+
+ def test_fitinfield(self):
+ f = ntp.util.fitinfield
+
+ # Field too small, crop decimals
+ self.assertEqual(f("123.456", 5), "123.4")
+ # ditto, negative
+ self.assertEqual(f("-123.456", 5), "-123.")
+ # Field too small, blow field
+ self.assertEqual(f("12345.678", 4), "12345.")
+ # Goldilocks
+ self.assertEqual(f("123.456", 7), "123.456")
+ # Field too big
+ self.assertEqual(f("123.456", 10), " 123.456")
+
+ def test_cropprecision(self):
+ f = ntp.util.cropprecision
+
+ # No decimals
+ self.assertEqual(f("1234", 6), "1234")
+ # Decimals, no crop
+ self.assertEqual(f("12.3456", 6), "12.3456")
+ # Decimals, crop
+ self.assertEqual(f("12.3456", 3), "12.345")
+ # At baseunit
+ self.assertEqual(f("1.234", 0), "1")
+
+ def test_isstringzero(self):
+ f = ntp.util.isstringzero
+
+ # Non-zero
+ self.assertEqual(f("0.0000001"), False)
+ # Zero
+ self.assertEqual(f("-0.00"), True)
+
+ def test_unitify(self):
+ f = ntp.util.unitify
+ nu = ntp.util
+
+ # Zero
+ self.assertEqual(f("0.000", nu.UNITS_SEC, nu.UNIT_MS),
+ " 0ns")
+ # Standard, width=8
+ self.assertEqual(f("1.234", nu.UNITS_SEC, nu.UNIT_MS),
+ " 1.234ms")
+ # ditto, negative
+ self.assertEqual(f("-1.234", nu.UNITS_SEC, nu.UNIT_MS),
+ "-1.234ms")
+ # Scale to larger unit, width=8
+ self.assertEqual(f("1234.5", nu.UNITS_SEC, nu.UNIT_MS),
+ " 1.2345s")
+ # ditto, negative
+ self.assertEqual(f("-1234.5", nu.UNITS_SEC, nu.UNIT_MS),
+ "-1.2345s")
+ # Scale to smaller unit, width=8
+ self.assertEqual(f("0.01234", nu.UNITS_SEC, nu.UNIT_MS),
+ u" 12.34\u03bcs")
+ # ditto, negative
+ self.assertEqual(f("-0.01234", nu.UNITS_SEC, nu.UNIT_MS),
+ u"-12.34\u03bcs")
+ # At baseunit
+ self.assertEqual(f("12.0", nu.UNITS_SEC, nu.UNIT_NS),
+ " 12ns")
+ # Scale to baseunit
+ self.assertEqual(f(".042", nu.UNITS_SEC, nu.UNIT_US),
+ " 42ns")
+ # Below baseunit
+ self.assertEqual(f("23.42", nu.UNITS_SEC, nu.UNIT_NS),
+ " 23ns")
+ # Different units
+ self.assertEqual(f("12.345", nu.UNITS_PPX, nu.UNIT_PPM),
+ "12.34ppm")
+ # Strip
+ self.assertEqual(f("1.23", nu.UNITS_SEC, nu.UNIT_MS, strip=True),
+ "1.23ms")
+ # Different width
+ self.assertEqual(f("1.234", nu.UNITS_SEC, nu.UNIT_MS, width=12),
+ " 1.234ms")
+
+ def test_stringfiltcooker(self):
+ # No scale
+ self.assertEqual(ntp.util.stringfiltcooker(
+ "1.02 34.5 0.67835 -23.0 9 6.7 1.00 .1"),
+ " 1.02 34.5 0.67835 -23.0 9 6.7 1.00 0.1 ms"
+ )
+ # Scale
+ self.assertEqual(ntp.util.stringfiltcooker(
+ "1000.02 3400.5 0.67835 -23.0 9001 6.7 1.00 1234"),
+ "1.00002 3.4005 0.00067 -0.0230 9.001 0.0067 0.00100 1.234 s"
+ )
+
if __name__ == '__main__':
unittest.main()
View it on GitLab: https://gitlab.com/NTPsec/ntpsec/commit/5e95e9186322284845ffa8c6c3c25bb079b046c0
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.ntpsec.org/pipermail/vc/attachments/20170411/58b785ee/attachment.html>
More information about the vc
mailing list