Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions asdf/_asdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -1164,9 +1164,10 @@ def schema_info(self, key="description", path=None, preserve_list=True):

def info(
self,
max_rows=display.DEFAULT_MAX_ROWS,
max_cols=display.DEFAULT_MAX_COLS,
show_values=display.DEFAULT_SHOW_VALUES,
max_rows: int | tuple[int, ...] | None = display.DEFAULT_MAX_ROWS,
max_cols: int | None = display.DEFAULT_MAX_COLS,
show_values: bool = display.DEFAULT_SHOW_VALUES,
show_blocks: bool = False,
):
"""
Print a rendering of this file's tree to stdout.
Expand All @@ -1190,6 +1191,10 @@ def info(
show_values : bool, optional
Set to False to disable display of primitive values in
the rendered tree.

show_blocks: bool, optional
Set to True to also print a table of block header fields
for each block in the file.
"""
lines = display.render_tree(
self.tree,
Expand All @@ -1201,6 +1206,24 @@ def info(
)
print("\n".join(lines))

if show_blocks:
for i, block in enumerate(self._blocks.blocks):
if block.header["compression"] == b"\0\0\0\0":
compression = "none"
else:
compression = block.header["compression"].decode()

items = [
("flags", f"{block.header['flags']:#010x}"),
("compression", compression),
("allocated_size", str(block.header["allocated_size"])),
("used_size", str(block.header["used_size"])),
("data_size", str(block.header["data_size"])),
("checksum", block.header["checksum"].hex()),
]
rows = display.render_table(f"Block #{i}", items)
print("\n".join(rows))

def search(self, key=NotSet, type_=NotSet, value=NotSet, filter_=None):
"""
Search this file's tree.
Expand Down
11 changes: 8 additions & 3 deletions asdf/_commands/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,21 @@ def setup_arguments(cls, subparsers):
)
parser.add_argument("--no-show-values", dest="show_values", action="store_false")
parser.set_defaults(show_values=True)
parser.add_argument(
"--show-blocks",
action="store_true",
help="Display a table of header information for each block in the file.",
)

parser.set_defaults(func=cls.run)

return parser

@classmethod
def run(cls, args):
info(args.filename, args.max_rows, args.max_cols, args.show_values)
info(args.filename, args.max_rows, args.max_cols, args.show_values, args.show_blocks)


def info(filename, max_rows, max_cols, show_values):
def info(filename, max_rows, max_cols, show_values, show_blocks):
with asdf.open(filename) as af:
af.info(max_rows, max_cols, show_values)
af.info(max_rows, max_cols, show_values, show_blocks=show_blocks)
25 changes: 25 additions & 0 deletions asdf/_display.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"DEFAULT_MAX_COLS",
"DEFAULT_MAX_ROWS",
"DEFAULT_SHOW_VALUES",
"render_table",
"render_tree",
]

Expand All @@ -25,6 +26,30 @@
DEFAULT_SHOW_VALUES = True


def render_table(
title: str,
rows: list[tuple[str, str]],
) -> list[str]:
"""Generate a simple 2 column table with title header."""
# TODO: table formatting will break if the terminal is narrower than the content.
# We could consider using shutil.get_terminal size to adjust the width of the table.

# TODO: this doesn't account for unicode glyphs that aren't 1 column wide
key_width = max(len(str(row[0])) for row in rows) + 2
val_width = max(len(str(row[1])) for row in rows) + 2
inner_width = key_width + val_width + 1

# Format each row with key left-aligned and value centered
content = [f"┃ {key:<{key_width-2}} │ {value:^{val_width-2}} ┃" for key, value in rows]
return [
"┏" + "━" * inner_width + "┓",
f"┃{title:^{inner_width}s}┃", # Centered table title
"┣" + "━" * key_width + "┯" + "━" * val_width + "┫",
*content,
"┗" + "━" * key_width + "┷" + "━" * val_width + "┛",
]


def render_tree(
node,
max_rows=DEFAULT_MAX_ROWS,
Expand Down
27 changes: 27 additions & 0 deletions asdf/_tests/commands/test_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,30 @@ def test_info_command(capsys, test_data_path):
assert "frames" in captured.out
new_len = len(captured.out.split("\n"))
assert new_len < original_len


def test_info_command_blocks_show(capsys, test_data_path):
"""Verify blocks are printed when `--show-blocks` is passed."""

file_path = test_data_path / "ndarray0.asdf"
assert main.main_from_args(["info", "--show-blocks", str(file_path)]) == 0
captured = capsys.readouterr()
assert "Block #0" in captured.out


def test_info_command_blocks_hide(capsys, test_data_path):
"""Verify no block output is shown by default when the file contains blocks."""

file_path = test_data_path / "ndarray0.asdf"
assert main.main_from_args(["info", str(file_path)]) == 0
captured = capsys.readouterr()
assert "Block" not in captured.out


def test_info_command_no_blocks(capsys, test_data_path):
"""Verify no block output is shown even with `--show-blocks` if file contains no blocks."""

file_path = test_data_path / "simple_inline_array0.asdf"
assert main.main_from_args(["info", "--show-blocks", str(file_path)]) == 0
captured = capsys.readouterr()
assert "Block" not in captured.out
30 changes: 30 additions & 0 deletions asdf/_tests/test_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -832,3 +832,33 @@ def test_info_no_infinite_loop(capsys):
af.info()
captured = capsys.readouterr()
assert "recursive" in captured.out


def test_info_blocks_show(capsys, test_data_path):
"""Verify blocks are printed when `show_blocks=True`."""

file_path = test_data_path / "ndarray0.asdf"
with asdf.open(file_path) as af:
af.info(show_blocks=True)
captured = capsys.readouterr()
assert "Block #0" in captured.out


def test_info_blocks_hide(capsys, test_data_path):
"""Verify no block output is shown by default when the file contains blocks."""

file_path = test_data_path / "ndarray0.asdf"
with asdf.open(file_path) as af:
af.info()
captured = capsys.readouterr()
assert "Block" not in captured.out


def test_info_no_blocks(capsys, test_data_path):
"""Verify no block output is shown even with `show_blocks=True` if file contains no blocks."""

file_path = test_data_path / "simple_inline_array0.asdf"
with asdf.open(file_path) as af:
af.info(show_blocks=True)
captured = capsys.readouterr()
assert "Block" not in captured.out
1 change: 1 addition & 0 deletions changes/2014.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added optional block info table to ``asdftool info`` and ``AsdfFile.info``.
Loading