Tweaked tracebd.py in a couple of ways, adopted bdgeom/--off/-n

- Tried to do the rescaling a bit better with truncating divisions, so
  there shouldn't be weird cross-pixel updates when things aren't well
  aligned.

- Adopted optional -B<block_size>x<block_count> flag for explicitly
  specifying the block-device geometry in a way that is compatible with
  other scripts. Should adopt this more places.

- Adopted optional <block>.<off> argument for start of range. This
  should match dbgblock.py.

- Adopted '-' for noop/zero-wear.

- Renamed a few internal things.

- Dropped subscript chars for wear, this didn't really add anything and
  can be accomplished by specifying the --wear-chars explicitly.

Also changed dbgblock.py to match, this mostly affects the --off/-n/--size
flags. For example, these are all the same:

  ./scripts/dbgblock.py disk -B4096 --off=10 --size=5
  ./scripts/dbgblock.py disk -B4096 --off=10 -n5
  ./scripts/dbgblock.py disk -B4096 --off=10,15
  ./scripts/dbgblock.py disk -B4096 -n10,15
  ./scripts/dbgblock.py disk -B4096 0.10 -n5

Also also adopted block-device geometry argument across scripts, where
the -B flag can optionally be a full <block_size>x<block_count> geometry:

  ./scripts/tracebd.py disk -B4096x256

Though this is mostly unused outside of tracebd.py right now. It will be
useful for anything that formats littlefs (littlefs-fuse?) and allowing
the format everywhere is a bit of a nice convenience.
This commit is contained in:
Christopher Haster
2023-10-27 00:39:38 -05:00
parent e6d736dba9
commit 46b78de500
6 changed files with 479 additions and 215 deletions
+57 -12
View File
@@ -3,10 +3,34 @@
import os import os
# some ways of block geometry representations
# 512 -> 512
# 512x16 -> (512, 16)
# 0x200x10 -> (512, 16)
def bdgeom(s):
s = s.strip()
b = 10
if s.startswith('0x') or s.startswith('0X'):
s = s[2:]
b = 16
elif s.startswith('0o') or s.startswith('0O'):
s = s[2:]
b = 8
elif s.startswith('0b') or s.startswith('0B'):
s = s[2:]
b = 2
if 'x' in s:
s, s_ = s.split('x', 1)
return (int(s, b), int(s_, b))
else:
return int(s, b)
# parse some rbyd addr encodings # parse some rbyd addr encodings
# 0xa -> [0xa] # 0xa -> [0xa]
# 0xa.b -> ([0xa], b) # 0xa.c -> [(0xa, 0xc)]
# 0x{a,b} -> [0xa, 0xb] # 0x{a,b} -> [0xa, 0xb]
# 0x{a,b}.c -> [(0xa, 0xc), (0xb, 0xc)]
def rbydaddr(s): def rbydaddr(s):
s = s.strip() s = s.strip()
b = 10 b = 10
@@ -51,8 +75,15 @@ def xxd(data, width=16):
def main(disk, block=None, *, def main(disk, block=None, *,
block_size=None, block_size=None,
seek=None, block_count=None,
off=None,
size=None): size=None):
# is bd geometry specified?
if isinstance(block_size, tuple):
block_size, block_count_ = block_size
if block_count is None:
block_count = block_count_
# flatten block, default to block 0 # flatten block, default to block 0
if not block: if not block:
block = [0] block = [0]
@@ -72,10 +103,16 @@ def main(disk, block=None, *,
# block may also encode an offset # block may also encode an offset
block, off, size = ( block, off, size = (
block[0] if isinstance(block, tuple) else block, block[0] if isinstance(block, tuple) else block,
seek if seek is not None off[0] if isinstance(off, tuple)
else off if off is not None
else size[0] if isinstance(size, tuple) and len(size) > 1
else block[1] if isinstance(block, tuple) else block[1] if isinstance(block, tuple)
else None, else None,
size if size is not None else block_size) size[1] - size[0] if isinstance(size, tuple) and len(size) > 1
else size[0] if isinstance(size, tuple)
else size if size is not None
else off[1] - off[0] if isinstance(off, tuple) and len(off) > 1
else block_size)
print('block %s, size %d' % ( print('block %s, size %d' % (
'0x%x.%x' % (block, off) '0x%x.%x' % (block, off)
@@ -107,16 +144,24 @@ if __name__ == "__main__":
help="Block address.") help="Block address.")
parser.add_argument( parser.add_argument(
'-B', '--block-size', '-B', '--block-size',
type=lambda x: int(x, 0), type=bdgeom,
help="Block size in bytes.") help="Block size/geometry in bytes.")
parser.add_argument( parser.add_argument(
'-s', '--seek', '--block-count',
type=lambda x: int(x, 0), type=lambda x: int(x, 0),
help="Start at this offset.") help="Block count in blocks.")
parser.add_argument(
'--off',
type=lambda x: tuple(
int(x, 0) if x.strip() else None
for x in x.split(',')),
help="Show a specific offset, may be a range.")
parser.add_argument( parser.add_argument(
'-n', '--size', '-n', '--size',
type=lambda x: int(x, 0), type=lambda x: tuple(
help="Show this many bytes.") int(x, 0) if x.strip() else None
for x in x.split(',')),
help="Show this many bytes, may be a range.")
sys.exit(main(**{k: v sys.exit(main(**{k: v
for k, v in vars(parser.parse_intermixed_args()).items() for k, v in vars(parser.parse_intermixed_args()).items()
if v is not None})) if v is not None}))
+39 -4
View File
@@ -51,10 +51,34 @@ TAG_GT = 0x2000
TAG_R = 0x1000 TAG_R = 0x1000
# some ways of block geometry representations
# 512 -> 512
# 512x16 -> (512, 16)
# 0x200x10 -> (512, 16)
def bdgeom(s):
s = s.strip()
b = 10
if s.startswith('0x') or s.startswith('0X'):
s = s[2:]
b = 16
elif s.startswith('0o') or s.startswith('0O'):
s = s[2:]
b = 8
elif s.startswith('0b') or s.startswith('0B'):
s = s[2:]
b = 2
if 'x' in s:
s, s_ = s.split('x', 1)
return (int(s, b), int(s_, b))
else:
return int(s, b)
# parse some rbyd addr encodings # parse some rbyd addr encodings
# 0xa -> [0xa] # 0xa -> [0xa]
# 0xa.b -> ([0xa], b) # 0xa.c -> [(0xa, 0xc)]
# 0x{a,b} -> [0xa, 0xb] # 0x{a,b} -> [0xa, 0xb]
# 0x{a,b}.c -> [(0xa, 0xc), (0xb, 0xc)]
def rbydaddr(s): def rbydaddr(s):
s = s.strip() s = s.strip()
b = 10 b = 10
@@ -525,6 +549,7 @@ class Rbyd:
def main(disk, roots=None, *, def main(disk, roots=None, *,
block_size=None, block_size=None,
block_count=None,
trunk=None, trunk=None,
color='auto', color='auto',
**args): **args):
@@ -536,6 +561,12 @@ def main(disk, roots=None, *,
else: else:
color = False color = False
# is bd geometry specified?
if isinstance(block_size, tuple):
block_size, block_count_ = block_size
if block_count is None:
block_count = block_count_
# flatten roots, default to block 0 # flatten roots, default to block 0
if not roots: if not roots:
roots = [[0]] roots = [[0]]
@@ -983,8 +1014,12 @@ if __name__ == "__main__":
help="Block address of the roots of the tree.") help="Block address of the roots of the tree.")
parser.add_argument( parser.add_argument(
'-B', '--block-size', '-B', '--block-size',
type=bdgeom,
help="Block size/geometry in bytes.")
parser.add_argument(
'--block-count',
type=lambda x: int(x, 0), type=lambda x: int(x, 0),
help="Block size in bytes.") help="Block count in blocks.")
parser.add_argument( parser.add_argument(
'--trunk', '--trunk',
type=lambda x: int(x, 0), type=lambda x: int(x, 0),
+39 -4
View File
@@ -52,10 +52,34 @@ TAG_GT = 0x2000
TAG_R = 0x1000 TAG_R = 0x1000
# some ways of block geometry representations
# 512 -> 512
# 512x16 -> (512, 16)
# 0x200x10 -> (512, 16)
def bdgeom(s):
s = s.strip()
b = 10
if s.startswith('0x') or s.startswith('0X'):
s = s[2:]
b = 16
elif s.startswith('0o') or s.startswith('0O'):
s = s[2:]
b = 8
elif s.startswith('0b') or s.startswith('0B'):
s = s[2:]
b = 2
if 'x' in s:
s, s_ = s.split('x', 1)
return (int(s, b), int(s_, b))
else:
return int(s, b)
# parse some rbyd addr encodings # parse some rbyd addr encodings
# 0xa -> [0xa] # 0xa -> [0xa]
# 0xa.b -> ([0xa], b) # 0xa.c -> [(0xa, 0xc)]
# 0x{a,b} -> [0xa, 0xb] # 0x{a,b} -> [0xa, 0xb]
# 0x{a,b}.c -> [(0xa, 0xc), (0xb, 0xc)]
def rbydaddr(s): def rbydaddr(s):
s = s.strip() s = s.strip()
b = 10 b = 10
@@ -1682,6 +1706,7 @@ def dbg_fstruct(f, block_size, mdir, rid, tag, j, d, data, *,
def main(disk, mroots=None, *, def main(disk, mroots=None, *,
block_size=None, block_size=None,
block_count=None,
mleaf_weight=None, mleaf_weight=None,
color='auto', color='auto',
**args): **args):
@@ -1693,6 +1718,12 @@ def main(disk, mroots=None, *,
else: else:
color = False color = False
# is bd geometry specified?
if isinstance(block_size, tuple):
block_size, block_count_ = block_size
if block_count is None:
block_count = block_count_
# flatten mroots, default to 0x{0,1} # flatten mroots, default to 0x{0,1}
if not mroots: if not mroots:
mroots = [[0,1]] mroots = [[0,1]]
@@ -2191,8 +2222,12 @@ if __name__ == "__main__":
help="Block address of the mroots. Defaults to 0x{0,1}.") help="Block address of the mroots. Defaults to 0x{0,1}.")
parser.add_argument( parser.add_argument(
'-B', '--block-size', '-B', '--block-size',
type=bdgeom,
help="Block size/geometry in bytes.")
parser.add_argument(
'--block-count',
type=lambda x: int(x, 0), type=lambda x: int(x, 0),
help="Block size in bytes.") help="Block count in blocks.")
parser.add_argument( parser.add_argument(
'-M', '--mleaf-weight', '-M', '--mleaf-weight',
type=lambda x: int(x, 0), type=lambda x: int(x, 0),
+39 -4
View File
@@ -51,10 +51,34 @@ TAG_GT = 0x2000
TAG_R = 0x1000 TAG_R = 0x1000
# some ways of block geometry representations
# 512 -> 512
# 512x16 -> (512, 16)
# 0x200x10 -> (512, 16)
def bdgeom(s):
s = s.strip()
b = 10
if s.startswith('0x') or s.startswith('0X'):
s = s[2:]
b = 16
elif s.startswith('0o') or s.startswith('0O'):
s = s[2:]
b = 8
elif s.startswith('0b') or s.startswith('0B'):
s = s[2:]
b = 2
if 'x' in s:
s, s_ = s.split('x', 1)
return (int(s, b), int(s_, b))
else:
return int(s, b)
# parse some rbyd addr encodings # parse some rbyd addr encodings
# 0xa -> [0xa] # 0xa -> [0xa]
# 0xa.b -> ([0xa], b) # 0xa.c -> [(0xa, 0xc)]
# 0x{a,b} -> [0xa, 0xb] # 0x{a,b} -> [0xa, 0xb]
# 0x{a,b}.c -> [(0xa, 0xc), (0xb, 0xc)]
def rbydaddr(s): def rbydaddr(s):
s = s.strip() s = s.strip()
b = 10 b = 10
@@ -788,6 +812,7 @@ class Rbyd:
def main(disk, mroots=None, *, def main(disk, mroots=None, *,
block_size=None, block_size=None,
block_count=None,
mleaf_weight=None, mleaf_weight=None,
color='auto', color='auto',
**args): **args):
@@ -799,6 +824,12 @@ def main(disk, mroots=None, *,
else: else:
color = False color = False
# is bd geometry specified?
if isinstance(block_size, tuple):
block_size, block_count_ = block_size
if block_count is None:
block_count = block_count_
# flatten mroots, default to 0x{0,1} # flatten mroots, default to 0x{0,1}
if not mroots: if not mroots:
mroots = [[0,1]] mroots = [[0,1]]
@@ -1635,8 +1666,12 @@ if __name__ == "__main__":
help="Block address of the mroots. Defaults to 0x{0,1}.") help="Block address of the mroots. Defaults to 0x{0,1}.")
parser.add_argument( parser.add_argument(
'-B', '--block-size', '-B', '--block-size',
type=bdgeom,
help="Block size/geometry in bytes.")
parser.add_argument(
'--block-count',
type=lambda x: int(x, 0), type=lambda x: int(x, 0),
help="Block size in bytes.") help="Block count in blocks.")
parser.add_argument( parser.add_argument(
'-M', '--mleaf-weight', '-M', '--mleaf-weight',
type=lambda x: int(x, 0), type=lambda x: int(x, 0),
+39 -4
View File
@@ -60,10 +60,34 @@ TAG_GT = 0x2000
TAG_R = 0x1000 TAG_R = 0x1000
# some ways of block geometry representations
# 512 -> 512
# 512x16 -> (512, 16)
# 0x200x10 -> (512, 16)
def bdgeom(s):
s = s.strip()
b = 10
if s.startswith('0x') or s.startswith('0X'):
s = s[2:]
b = 16
elif s.startswith('0o') or s.startswith('0O'):
s = s[2:]
b = 8
elif s.startswith('0b') or s.startswith('0B'):
s = s[2:]
b = 2
if 'x' in s:
s, s_ = s.split('x', 1)
return (int(s, b), int(s_, b))
else:
return int(s, b)
# parse some rbyd addr encodings # parse some rbyd addr encodings
# 0xa -> [0xa] # 0xa -> [0xa]
# 0xa.b -> ([0xa], b) # 0xa.c -> [(0xa, 0xc)]
# 0x{a,b} -> [0xa, 0xb] # 0x{a,b} -> [0xa, 0xb]
# 0x{a,b}.c -> [(0xa, 0xc), (0xb, 0xc)]
def rbydaddr(s): def rbydaddr(s):
s = s.strip() s = s.strip()
b = 10 b = 10
@@ -851,6 +875,7 @@ def dbg_tree(data, block_size, rev, trunk, weight, *,
def main(disk, blocks=None, *, def main(disk, blocks=None, *,
block_size=None, block_size=None,
block_count=None,
trunk=None, trunk=None,
color='auto', color='auto',
**args): **args):
@@ -862,6 +887,12 @@ def main(disk, blocks=None, *,
else: else:
color = False color = False
# is bd geometry specified?
if isinstance(block_size, tuple):
block_size, block_count_ = block_size
if block_count is None:
block_count = block_count_
# flatten blocks, default to block 0 # flatten blocks, default to block 0
if not blocks: if not blocks:
blocks = [[0]] blocks = [[0]]
@@ -1015,8 +1046,12 @@ if __name__ == "__main__":
help="Block address of metadata blocks.") help="Block address of metadata blocks.")
parser.add_argument( parser.add_argument(
'-B', '--block-size', '-B', '--block-size',
type=bdgeom,
help="Block size/geometry in bytes.")
parser.add_argument(
'--block-count',
type=lambda x: int(x, 0), type=lambda x: int(x, 0),
help="Block size in bytes.") help="Block count in blocks.")
parser.add_argument( parser.add_argument(
'--trunk', '--trunk',
type=lambda x: int(x, 0), type=lambda x: int(x, 0),
+266 -187
View File
@@ -21,15 +21,13 @@ import threading as th
import time import time
CHARS = 'rpe.' CHARS = 'rpe-'
COLORS = ['42', '45', '44', ''] COLORS = ['32', '35', '34', '']
WEAR_CHARS = '0123456789' WEAR_CHARS = '-123456789'
WEAR_CHARS_SUBSCRIPTS = '.₁₂₃₄₅₆789'
WEAR_COLORS = ['', '', '', '', '', '', '', '35', '35', '1;31'] WEAR_COLORS = ['', '', '', '', '', '', '', '35', '35', '1;31']
CHARS_DOTS = " .':" CHARS_DOTS = " .':"
COLORS_DOTS = ['32', '35', '34', '']
CHARS_BRAILLE = ( CHARS_BRAILLE = (
'⠀⢀⡀⣀⠠⢠⡠⣠⠄⢄⡄⣄⠤⢤⡤⣤' '⠐⢐⡐⣐⠰⢰⡰⣰⠔⢔⡔⣔⠴⢴⡴⣴' '⠀⢀⡀⣀⠠⢠⡠⣠⠄⢄⡄⣄⠤⢤⡤⣤' '⠐⢐⡐⣐⠰⢰⡰⣰⠔⢔⡔⣔⠴⢴⡴⣴'
'⠂⢂⡂⣂⠢⢢⡢⣢⠆⢆⡆⣆⠦⢦⡦⣦' '⠒⢒⡒⣒⠲⢲⡲⣲⠖⢖⡖⣖⠶⢶⡶⣶' '⠂⢂⡂⣂⠢⢢⡢⣢⠆⢆⡆⣆⠦⢦⡦⣦' '⠒⢒⡒⣒⠲⢲⡲⣲⠖⢖⡖⣖⠶⢶⡶⣶'
@@ -51,6 +49,67 @@ def openio(path, mode='r', buffering=-1):
else: else:
return open(path, mode, buffering) return open(path, mode, buffering)
# some ways of block geometry representations
# 512 -> 512
# 512x16 -> (512, 16)
# 0x200x10 -> (512, 16)
def bdgeom(s):
s = s.strip()
b = 10
if s.startswith('0x') or s.startswith('0X'):
s = s[2:]
b = 16
elif s.startswith('0o') or s.startswith('0O'):
s = s[2:]
b = 8
elif s.startswith('0b') or s.startswith('0B'):
s = s[2:]
b = 2
if 'x' in s:
s, s_ = s.split('x', 1)
return (int(s, b), int(s_, b))
else:
return int(s, b)
# parse some rbyd addr encodings
# 0xa -> [0xa]
# 0xa.c -> [(0xa, 0xc)]
# 0x{a,b} -> [0xa, 0xb]
# 0x{a,b}.c -> [(0xa, 0xc), (0xb, 0xc)]
def rbydaddr(s):
s = s.strip()
b = 10
if s.startswith('0x') or s.startswith('0X'):
s = s[2:]
b = 16
elif s.startswith('0o') or s.startswith('0O'):
s = s[2:]
b = 8
elif s.startswith('0b') or s.startswith('0B'):
s = s[2:]
b = 2
trunk = None
if '.' in s:
s, s_ = s.split('.', 1)
trunk = int(s_, b)
if s.startswith('{') and '}' in s:
ss = s[1:s.find('}')].split(',')
else:
ss = [s]
addr = []
for s in ss:
if trunk is not None:
addr.append((int(s, b), trunk))
else:
addr.append(int(s, b))
return addr
class LinesIO: class LinesIO:
def __init__(self, maxlen=None): def __init__(self, maxlen=None):
self.maxlen = maxlen self.maxlen = maxlen
@@ -203,7 +262,7 @@ def lebesgue_curve(width, height):
return curve return curve
class Block(int): class Pixel(int):
__slots__ = () __slots__ = ()
def __new__(cls, state=0, *, def __new__(cls, state=0, *,
wear=0, wear=0,
@@ -234,19 +293,19 @@ class Block(int):
return (self & 4) != 0 return (self & 4) != 0
def read(self): def read(self):
return Block(int(self) | 1) return Pixel(int(self) | 1)
def prog(self): def prog(self):
return Block(int(self) | 2) return Pixel(int(self) | 2)
def erase(self): def erase(self):
return Block((int(self) | 4) + 8) return Pixel((int(self) | 4) + 8)
def clear(self): def clear(self):
return Block(int(self) & ~7) return Pixel(int(self) & ~7)
def __or__(self, other): def __or__(self, other):
return Block( return Pixel(
(int(self) | int(other)) & 7, (int(self) | int(other)) & 7,
wear=max(self.wear, other.wear)) wear=max(self.wear, other.wear))
@@ -269,7 +328,6 @@ class Block(int):
wear=False, wear=False,
block_cycles=None, block_cycles=None,
color=True, color=True,
subscripts=False,
dots=False, dots=False,
braille=False, braille=False,
chars=None, chars=None,
@@ -284,18 +342,12 @@ class Block(int):
chars = chars + CHARS[len(chars):] chars = chars + CHARS[len(chars):]
if colors is None: if colors is None:
if braille or dots: colors = COLORS
colors = COLORS_DOTS
else:
colors = COLORS
if len(colors) < len(COLORS): if len(colors) < len(COLORS):
colors = colors + COLORS[len(colors):] colors = colors + COLORS[len(colors):]
if wear_chars is None: if wear_chars is None:
if subscripts: wear_chars = WEAR_CHARS
wear_chars = WEAR_CHARS_SUBSCRIPTS
else:
wear_chars = WEAR_CHARS
if wear_colors is None: if wear_colors is None:
wear_colors = WEAR_COLORS wear_colors = WEAR_COLORS
@@ -340,112 +392,109 @@ class Block(int):
class Bd: class Bd:
def __init__(self, *, def __init__(self, *,
size=1, block_size=1,
count=1, block_count=1,
width=None, width=None,
height=1, height=1,
blocks=None): pixels=None):
if width is None: if width is None:
width = count width = block_count
if blocks is None: if pixels is None:
self.blocks = [Block() for _ in range(width*height)] self.pixels = [Pixel() for _ in range(width*height)]
else: else:
self.blocks = blocks self.pixels = pixels
self.size = size self.block_size = block_size
self.count = count self.block_count = block_count
self.width = width self.width = width
self.height = height self.height = height
def _op(self, f, block=None, off=None, size=None): def _op(self, f, block=None, off=None, size=None):
if block is None: if block is None:
range_ = range(len(self.blocks)) range_ = range(len(self.pixels))
else: else:
if off is None: if off is None:
off, size = 0, self.size off, size = 0, self.block_size
elif size is None: elif size is None:
off, size = 0, off off, size = 0, off
# update our geometry? this will do nothing if we haven't changed # update our geometry?
self.resize( if off+size > self.block_size or block >= self.block_count:
size=max(self.size, off+size), self.resize(
count=max(self.count, block+1)) block_size=max(self.block_size, off+size),
block_count=max(self.block_count, block+1))
# map to our block space # map to our block space
start = (block*self.size + off) / (self.size*self.count) start = block*self.block_size + off
stop = (block*self.size + off+size) / (self.size*self.count) stop = block*self.block_size + off+size
start = ((start*len(self.pixels))
range_ = range( // (self.block_size*self.block_count))
m.floor(start*len(self.blocks)), stop = ((stop*len(self.pixels))
m.ceil(stop*len(self.blocks))) // (self.block_size*self.block_count))
stop = max(stop, start+1)
range_ = range(start, stop)
# apply the op # apply the op
for i in range_: for i in range_:
self.blocks[i] = f(self.blocks[i]) self.pixels[i] = f(self.pixels[i])
def read(self, block=None, off=None, size=None): def read(self, block=None, off=None, size=None):
self._op(Block.read, block, off, size) self._op(Pixel.read, block, off, size)
def prog(self, block=None, off=None, size=None): def prog(self, block=None, off=None, size=None):
self._op(Block.prog, block, off, size) self._op(Pixel.prog, block, off, size)
def erase(self, block=None, off=None, size=None): def erase(self, block=None, off=None, size=None):
self._op(Block.erase, block, off, size) self._op(Pixel.erase, block, off, size)
def clear(self, block=None, off=None, size=None): def clear(self, block=None, off=None, size=None):
self._op(Block.clear, block, off, size) self._op(Pixel.clear, block, off, size)
def copy(self): def copy(self):
return Bd( return Bd(
blocks=self.blocks.copy(), pixels=self.pixels.copy(),
size=self.size, block_size=self.block_size,
count=self.count, block_count=self.block_count,
width=self.width, width=self.width,
height=self.height) height=self.height)
def resize(self, *, def resize(self, *,
size=None, block_size=None,
count=None, block_count=None,
width=None, width=None,
height=None): height=None):
size = size if size is not None else self.size block_size = (block_size if block_size is not None
count = count if count is not None else self.count else self.block_size)
block_count = (block_count if block_count is not None
else self.block_count)
width = width if width is not None else self.width width = width if width is not None else self.width
height = height if height is not None else self.height height = height if height is not None else self.height
if (size == self.size if (block_size == self.block_size
and count == self.count and block_count == self.block_count
and width == self.width and width == self.width
and height == self.height): and height == self.height):
return return
# transform our blocks # transform our pixels
blocks = [] pixels = []
for x in range(width*height): for x in range(width*height):
# map from new bd space # map into our old bd space
start = m.floor(x * (size*count)/(width*height)) start = (x*(block_size*block_count)) // (width*height)
stop = m.ceil((x+1) * (size*count)/(width*height)) stop = ((x+1)*(block_size*block_count)) // (width*height)
start_block = start // size stop = max(stop, start+1)
start_off = start % size
stop_block = stop // size
stop_off = stop % size
# map to old bd space
start = start_block*self.size + start_off
stop = stop_block*self.size + stop_off
start = m.floor(start * len(self.blocks)/(self.size*self.count))
stop = m.ceil(stop * len(self.blocks)/(self.size*self.count))
# aggregate state # aggregate state
blocks.append(ft.reduce( pixels.append(ft.reduce(
Block.__or__, Pixel.__or__,
self.blocks[start:stop], self.pixels[start:stop],
Block())) Pixel()))
self.size = size self.block_size = block_size
self.count = count self.block_count = block_count
self.width = width self.width = width
self.height = height self.height = height
self.blocks = blocks self.pixels = pixels
def draw(self, row, *, def draw(self, row, *,
read=False, read=False,
@@ -460,23 +509,23 @@ class Bd:
# find max wear? # find max wear?
max_wear = None max_wear = None
if wear: if wear:
max_wear = max(b.wear for b in self.blocks) max_wear = max(p.wear for p in self.pixels)
# fold via a curve? # fold via a curve?
if hilbert: if hilbert:
grid = [None]*(self.width*self.height) grid = [None]*(self.width*self.height)
for (x,y), b in zip( for (x,y), p in zip(
hilbert_curve(self.width, self.height), hilbert_curve(self.width, self.height),
self.blocks): self.pixels):
grid[x + y*self.width] = b grid[x + y*self.width] = p
elif lebesgue: elif lebesgue:
grid = [None]*(self.width*self.height) grid = [None]*(self.width*self.height)
for (x,y), b in zip( for (x,y), p in zip(
lebesgue_curve(self.width, self.height), lebesgue_curve(self.width, self.height),
self.blocks): self.pixels):
grid[x + y*self.width] = b grid[x + y*self.width] = p
else: else:
grid = self.blocks grid = self.pixels
# need to wait for more trace output before rendering # need to wait for more trace output before rendering
# #
@@ -493,7 +542,7 @@ class Bd:
grid = list(it.chain.from_iterable( grid = list(it.chain.from_iterable(
# did we resize? # did we resize?
it.islice(it.chain(h, it.repeat(Block())), it.islice(it.chain(h, it.repeat(Pixel())),
self.width*self.height) self.width*self.height)
for h in self.history)) for h in self.history))
self.history = [] self.history = []
@@ -502,21 +551,21 @@ class Bd:
if braille: if braille:
# encode into a byte # encode into a byte
for x in range(0, self.width, 2): for x in range(0, self.width, 2):
byte_b = 0 byte_p = 0
best_b = Block() best_p = Pixel()
for i in range(2*4): for i in range(2*4):
b = grid[x+(2-1-(i%2)) + ((row*4)+(4-1-(i//2)))*self.width] p = grid[x+(2-1-(i%2)) + ((row*4)+(4-1-(i//2)))*self.width]
best_b |= b best_p |= p
if ((read and b.readed) if ((read and p.readed)
or (prog and b.proged) or (prog and p.proged)
or (erase and b.erased) or (erase and p.erased)
or (not read and not prog and not erase or (not read and not prog and not erase
and wear and b.worn(max_wear, **args) >= 0.7)): and wear and p.worn(max_wear, **args) >= 0.7)):
byte_b |= 1 << i byte_p |= 1 << i
line.append(best_b.draw( line.append(best_p.draw(
max_wear, max_wear,
CHARS_BRAILLE[byte_b], CHARS_BRAILLE[byte_p],
braille=True, braille=True,
read=read, read=read,
prog=prog, prog=prog,
@@ -526,21 +575,21 @@ class Bd:
elif dots: elif dots:
# encode into a byte # encode into a byte
for x in range(self.width): for x in range(self.width):
byte_b = 0 byte_p = 0
best_b = Block() best_p = Pixel()
for i in range(2): for i in range(2):
b = grid[x + ((row*2)+(2-1-i))*self.width] p = grid[x + ((row*2)+(2-1-i))*self.width]
best_b |= b best_p |= p
if ((read and b.readed) if ((read and p.readed)
or (prog and b.proged) or (prog and p.proged)
or (erase and b.erased) or (erase and p.erased)
or (not read and not prog and not erase or (not read and not prog and not erase
and wear and b.worn(max_wear, **args) >= 0.7)): and wear and p.worn(max_wear, **args) >= 0.7)):
byte_b |= 1 << i byte_p |= 1 << i
line.append(best_b.draw( line.append(best_p.draw(
max_wear, max_wear,
CHARS_DOTS[byte_b], CHARS_DOTS[byte_p],
dots=True, dots=True,
read=read, read=read,
prog=prog, prog=prog,
@@ -561,16 +610,16 @@ class Bd:
def main(path='-', *, def main(path='-', block=None, *,
off=None,
size=None,
block_size=None,
block_count=None,
block_cycles=None,
read=False, read=False,
prog=False, prog=False,
erase=False, erase=False,
wear=False, wear=False,
block=(None,None),
off=(None,None),
block_size=None,
block_count=None,
block_cycles=None,
reset=False, reset=False,
color='auto', color='auto',
dots=False, dots=False,
@@ -616,37 +665,58 @@ def main(path='-', *,
else: else:
lines = 5 lines = 5
# allow ranges for blocks/offs # is bd geometry specified?
block_start = block[0] if isinstance(block_size, tuple):
block_stop = block[1] if len(block) > 1 else block[0]+1 block_size, block_count_ = block_size
off_start = off[0] if block_count is None:
off_stop = off[1] if len(off) > 1 else off[0]+1 block_count = block_count_
if block_start is None: # allow ranges for blocks/offs
block_start = 0 if not isinstance(block, tuple):
if block_stop is None and block_count is not None: block = (block,)
block_stop = block_count if any(isinstance(block, list) and len(block) > 1 for block in block):
if off_start is None: print("error: More than one block address?")
off_start = 0 sys.exit(-1)
if off_stop is None and block_size is not None: block = tuple(
off_stop = block_size block[0] if isinstance(block, list) else block
for block in block)
if not isinstance(off, tuple):
off = (off,)
if not isinstance(size, tuple):
size = (size,)
block_start = (
block[0][0] if isinstance(block[0], tuple)
else block[0] if block[0] is not None
else 0)
block_stop = (
block[1][0] if len(block) > 1 and isinstance(block[1], tuple)
else block[1] if len(block) > 1 and block[1] is not None
else block_start+1 if len(block) == 1 and block[0] is not None
else block_count)
off_start = (
off[0] if off[0] is not None
else block[0][1] if isinstance(block[0], tuple)
else block[1][1] if len(block) > 1 and isinstance(block[1], tuple)
else size[0] if len(size) > 1 and size[0] is not None
else 0)
off_stop = (
off_start + size[0] if len(size) == 1 and size[0] is not None
else off[1] if len(off) > 1 and off[1] is not None
else size[1] if len(size) > 1 and size[1] is not None
else block_size)
# create a block device representation # create a block device representation
bd = Bd() bd = Bd()
def resize(*, size=None, count=None): def resize(*, block_size=None, block_count=None):
nonlocal bd nonlocal bd
# size may be overriden by cli args # size may be overriden by cli args
if block_size is not None: if off_stop is not None:
size = block_size block_size = off_stop-off_start
elif off_stop is not None: if block_stop is not None:
size = off_stop-off_start block_count = block_stop-block_start
if block_count is not None:
count = block_count
elif block_stop is not None:
count = block_stop-block_start
# figure out best width/height # figure out best width/height
if width is None: if width is None:
@@ -664,8 +734,8 @@ def main(path='-', *,
height_ = shutil.get_terminal_size((80, 5))[1] height_ = shutil.get_terminal_size((80, 5))[1]
bd.resize( bd.resize(
size=size, block_size=block_size,
count=count, block_count=block_count,
# scale if we're printing with dots or braille # scale if we're printing with dots or braille
width=2*width_ if braille else width_, width=2*width_ if braille else width_,
height=max(1, height=max(1,
@@ -714,14 +784,14 @@ def main(path='-', *,
if m.group('create'): if m.group('create'):
# update our block size/count # update our block size/count
size = int(m.group('block_size'), 0) block_size = int(m.group('block_size'), 0)
count = int(m.group('block_count'), 0) block_count = int(m.group('block_count'), 0)
resize(size=size, count=count) resize(block_size=block_size, block_count=block_count)
if reset: if reset:
bd = Bd( bd = Bd(
size=bd.size, block_size=bd.block_size,
count=bd.count, block_count=bd.block_count,
width=bd.width, width=bd.width,
height=bd.height) height=bd.height)
return True return True
@@ -731,14 +801,15 @@ def main(path='-', *,
off = int(m.group('read_off'), 0) off = int(m.group('read_off'), 0)
size = int(m.group('read_size'), 0) size = int(m.group('read_size'), 0)
if block_stop is not None and block >= block_stop: if ((block_stop is not None and block >= block_stop)
or block < block_start
or (off_stop is not None and off >= off_stop)
or off+size <= off_start):
return False return False
block -= block_start block -= block_start
if off_stop is not None: size = ((min(off+size, off_stop)
if off >= off_stop: if off_stop is not None else off+size)
return False - max(off, off_start))
size = min(size, off_stop-off)
off -= off_start
bd.read(block, off, size) bd.read(block, off, size)
return True return True
@@ -748,28 +819,33 @@ def main(path='-', *,
off = int(m.group('prog_off'), 0) off = int(m.group('prog_off'), 0)
size = int(m.group('prog_size'), 0) size = int(m.group('prog_size'), 0)
if block_stop is not None and block >= block_stop: if ((block_stop is not None and block >= block_stop)
or block < block_start
or (off_stop is not None and off >= off_stop)
or off+size <= off_start):
return False return False
block -= block_start block -= block_start
if off_stop is not None: size = ((min(off+size, off_stop)
if off >= off_stop: if off_stop is not None else off+size)
return False - max(off, off_start))
size = min(size, off_stop-off)
off -= off_start
bd.prog(block, off, size) bd.prog(block, off, size)
return True return True
elif m.group('erase') and (erase or wear): elif m.group('erase') and (erase or wear):
block = int(m.group('erase_block'), 0) block = int(m.group('erase_block'), 0)
off = 0
size = int(m.group('erase_size'), 0) size = int(m.group('erase_size'), 0)
if block_stop is not None and block >= block_stop: if ((block_stop is not None and block >= block_stop)
or block < block_start
or (off_stop is not None and off >= off_stop)
or off+size <= off_start):
return False return False
block -= block_start block -= block_start
if off_stop is not None: size = ((min(off+size, off_stop)
size = min(size, off_stop) if off_stop is not None else off+size)
off = -off_start - max(off, off_start))
bd.erase(block, off, size) bd.erase(block, off, size)
return True return True
@@ -876,6 +952,37 @@ if __name__ == "__main__":
'path', 'path',
nargs='?', nargs='?',
help="Path to read from.") help="Path to read from.")
parser.add_argument(
'block',
nargs='?',
type=lambda x: tuple(
rbydaddr(x) if x.strip() else None
for x in x.split(',')),
help="Optional block to show, may be a range.")
parser.add_argument(
'-B', '--block-size',
type=bdgeom,
help="Block size/geometry in bytes.")
parser.add_argument(
'--block-count',
type=lambda x: int(x, 0),
help="Block count in blocks.")
parser.add_argument(
'-C', '--block-cycles',
type=lambda x: int(x, 0),
help="Assumed maximum number of erase cycles when measuring wear.")
parser.add_argument(
'--off',
type=lambda x: tuple(
int(x, 0) if x.strip() else None
for x in x.split(',')),
help="Show a specific offset, may be a range.")
parser.add_argument(
'--size',
type=lambda x: tuple(
int(x, 0) if x.strip() else None
for x in x.split(',')),
help="Show this many bytes, may be a range.")
parser.add_argument( parser.add_argument(
'-r', '--read', '-r', '--read',
action='store_true', action='store_true',
@@ -892,30 +999,6 @@ if __name__ == "__main__":
'-w', '--wear', '-w', '--wear',
action='store_true', action='store_true',
help="Render wear.") help="Render wear.")
parser.add_argument(
'-b', '--block',
type=lambda x: tuple(
int(x, 0) if x.strip() else None
for x in x.split(',')),
help="Show a specific block or range of blocks.")
parser.add_argument(
'-i', '--off',
type=lambda x: tuple(
int(x, 0) if x.strip() else None
for x in x.split(',')),
help="Show a specific offset or range of offsets.")
parser.add_argument(
'-B', '--block-size',
type=lambda x: int(x, 0),
help="Assume a specific block size.")
parser.add_argument(
'--block-count',
type=lambda x: int(x, 0),
help="Assume a specific block count.")
parser.add_argument(
'-C', '--block-cycles',
type=lambda x: int(x, 0),
help="Assumed maximum number of erase cycles when measuring wear.")
parser.add_argument( parser.add_argument(
'-R', '--reset', '-R', '--reset',
action='store_true', action='store_true',
@@ -925,10 +1008,6 @@ if __name__ == "__main__":
choices=['never', 'always', 'auto'], choices=['never', 'always', 'auto'],
default='auto', default='auto',
help="When to use terminal colors. Defaults to 'auto'.") help="When to use terminal colors. Defaults to 'auto'.")
parser.add_argument(
'--subscripts',
action='store_true',
help="Use unicode subscripts for showing wear.")
parser.add_argument( parser.add_argument(
'-:', '--dots', '-:', '--dots',
action='store_true', action='store_true',