[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