diff --git a/src/bsv/cli.py b/src/bsv/cli.py index 3757187..3424115 100644 --- a/src/bsv/cli.py +++ b/src/bsv/cli.py @@ -26,6 +26,7 @@ from typing import Any, ClassVar, Literal import click +from bsv.cli_utils import format_human_byte_size from bsv.repo import default_repository_path from bsv.vfs import ( AlreadyExistError, @@ -90,7 +91,7 @@ class BsvPathType(click.ParamType): class AnyPathType(click.ParamType): """Converter for bsv or fs paths given on the command line.""" - name: ClassVar[str] = "any_path" + name: str = "any_path" default: Literal["bsv", "fs"] @@ -164,7 +165,8 @@ def info(params: RepositoryParams): @click.pass_obj def init(params: RepositoryParams, device_name: str): """Initialize a bsv repository.""" - print(f"device_name: {device_name!r}") + print(f"Repository path: {params.path!r}") + print(f"Device name: {device_name!r}") @cli.command() @@ -199,23 +201,6 @@ def mkdir( sys.exit(return_code) -SI_PREFIXES = ["", "k", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q"] - - -def human_byte_size(byte_size: int) -> str: - if byte_size == 0: - return "0" - - prefix_index = int(math.log2(byte_size) / 10) - display_size: float = byte_size / 1024**prefix_index - size = ( - str(math.ceil(display_size)) - if prefix_index == 0 or display_size >= 10 - else repr(math.ceil(display_size * 10) / 10) - ) - return f"{size}{SI_PREFIXES[prefix_index]}" - - @cli.command() @click.argument("files", nargs=-1, type=BsvPathType()) @click.option("--filter", flag_value="hidden", default=True, hidden=True) @@ -237,7 +222,7 @@ def ls( if not files: files = (PurePosixPath("/"),) - filter_md = FileMetadata.is_hidden_files if filter == "hidden" else lambda _: False + filter_md = FileMetadata.is_hidden if filter == "hidden" else lambda _: False for file_index, file in enumerate(files): if len(files) > 1: @@ -260,7 +245,7 @@ def ls( for name, md in items: mode = str(md.unix_mode) size = ( - human_byte_size(md.byte_size) + format_human_byte_size(md.byte_size) if human_readable else str(md.byte_size) ) diff --git a/src/bsv/cli_utils.py b/src/bsv/cli_utils.py new file mode 100644 index 0000000..2dfa2e3 --- /dev/null +++ b/src/bsv/cli_utils.py @@ -0,0 +1,48 @@ +# pybsv - Backup, Synchronization, Versioning. +# Copyright (C) 2025 Simon Boyé +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +"""Tools and utilities to build the command-line interface.""" + +from __future__ import annotations + +from typing import Final + + +BINARY_PREFIXES: Final[list[str]] = [ + "", + "Ki", + "Mi", + "Gi", + "Ti", + "Pi", + "Ei", + "Zi", + "Yi", + "Ri", + "Qi", +] + + +def format_human_byte_size(byte_size: int) -> str: + """Format the given `byte_size` as a human-readable string.""" + index = min(max((byte_size.bit_length() - 1) // 10, 0), len(BINARY_PREFIXES) - 1) + size = byte_size / 1024**index + num_digits = len(str(int(size))) + decimals = max(0, 3 - num_digits) + rounded = round(size, decimals) + if rounded == 1024 and index + 1 < len(BINARY_PREFIXES): + rounded = 1 + index += 1 + return f"{rounded:.16g}{BINARY_PREFIXES[index]}B" diff --git a/src/bsv/vfs.py b/src/bsv/vfs.py index 361b51c..164f559 100644 --- a/src/bsv/vfs.py +++ b/src/bsv/vfs.py @@ -22,7 +22,7 @@ from functools import total_ordering import os from pathlib import Path, PurePosixPath from stat import S_IFDIR, S_IFLNK, S_IFMT, S_IFREG, S_IMODE, filemode -from typing import TYPE_CHECKING, Any, BinaryIO, Literal, Self, cast +from typing import TYPE_CHECKING, Any, BinaryIO, Literal, Self from typing_extensions import Buffer diff --git a/tests/test_bsv/test_cli_utils.py b/tests/test_bsv/test_cli_utils.py new file mode 100644 index 0000000..c7e5ca4 --- /dev/null +++ b/tests/test_bsv/test_cli_utils.py @@ -0,0 +1,56 @@ +# pybsv - Backup, Synchronization, Versioning. +# Copyright (C) 2025 Simon Boyé +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +"""Tests for cli_utils.py.""" + +from __future__ import annotations + +from bsv.cli_utils import format_human_byte_size + + +def test_format_human_byte_size(): + assert format_human_byte_size(0) == "0B" + assert format_human_byte_size(1) == "1B" + assert format_human_byte_size(9) == "9B" + assert format_human_byte_size(10) == "10B" + assert format_human_byte_size(99) == "99B" + assert format_human_byte_size(100) == "100B" + assert format_human_byte_size(999) == "999B" + assert format_human_byte_size(1000) == "1000B" + assert format_human_byte_size(1023) == "1023B" + assert format_human_byte_size(2**10) == "1KiB" + assert format_human_byte_size(int(1.23456 * 2**10)) == "1.23KiB" + assert format_human_byte_size(9 * 2**10) == "9KiB" + assert format_human_byte_size(10 * 2**10 - 1) == "10KiB" + assert format_human_byte_size(int(98.76543 * 2**10)) == "98.8KiB" + assert format_human_byte_size(99 * 2**10 - 1) == "99KiB" + assert format_human_byte_size(100 * 2**10 - 1) == "100KiB" + assert format_human_byte_size(int(192.8374 * 2**10)) == "193KiB" + assert format_human_byte_size(999 * 2**10 - 1) == "999KiB" + assert format_human_byte_size(1000 * 2**10 - 1) == "1000KiB" + assert format_human_byte_size(2**20 - 1) == "1MiB" + assert format_human_byte_size(2**20) == "1MiB" + assert format_human_byte_size(2**30) == "1GiB" + assert format_human_byte_size(2**40) == "1TiB" + assert format_human_byte_size(2**50) == "1PiB" + assert format_human_byte_size(2**60) == "1EiB" + assert format_human_byte_size(2**70) == "1ZiB" + assert format_human_byte_size(2**80) == "1YiB" + assert format_human_byte_size(2**90) == "1RiB" + assert format_human_byte_size(2**100 - 2**80) == "1QiB" + assert format_human_byte_size(2**100) == "1QiB" + assert format_human_byte_size(2**110 - 2**90) == "1024QiB" + assert format_human_byte_size(2**110) == "1024QiB" + assert format_human_byte_size(2**120) == "1048576QiB"