Better human byte size + minor fixes.
This commit is contained in:
@@ -26,6 +26,7 @@ from typing import Any, ClassVar, Literal
|
|||||||
|
|
||||||
import click
|
import click
|
||||||
|
|
||||||
|
from bsv.cli_utils import format_human_byte_size
|
||||||
from bsv.repo import default_repository_path
|
from bsv.repo import default_repository_path
|
||||||
from bsv.vfs import (
|
from bsv.vfs import (
|
||||||
AlreadyExistError,
|
AlreadyExistError,
|
||||||
@@ -90,7 +91,7 @@ class BsvPathType(click.ParamType):
|
|||||||
class AnyPathType(click.ParamType):
|
class AnyPathType(click.ParamType):
|
||||||
"""Converter for bsv or fs paths given on the command line."""
|
"""Converter for bsv or fs paths given on the command line."""
|
||||||
|
|
||||||
name: ClassVar[str] = "any_path"
|
name: str = "any_path"
|
||||||
|
|
||||||
default: Literal["bsv", "fs"]
|
default: Literal["bsv", "fs"]
|
||||||
|
|
||||||
@@ -164,7 +165,8 @@ def info(params: RepositoryParams):
|
|||||||
@click.pass_obj
|
@click.pass_obj
|
||||||
def init(params: RepositoryParams, device_name: str):
|
def init(params: RepositoryParams, device_name: str):
|
||||||
"""Initialize a bsv repository."""
|
"""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()
|
@cli.command()
|
||||||
@@ -199,23 +201,6 @@ def mkdir(
|
|||||||
sys.exit(return_code)
|
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()
|
@cli.command()
|
||||||
@click.argument("files", nargs=-1, type=BsvPathType())
|
@click.argument("files", nargs=-1, type=BsvPathType())
|
||||||
@click.option("--filter", flag_value="hidden", default=True, hidden=True)
|
@click.option("--filter", flag_value="hidden", default=True, hidden=True)
|
||||||
@@ -237,7 +222,7 @@ def ls(
|
|||||||
if not files:
|
if not files:
|
||||||
files = (PurePosixPath("/"),)
|
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):
|
for file_index, file in enumerate(files):
|
||||||
if len(files) > 1:
|
if len(files) > 1:
|
||||||
@@ -260,7 +245,7 @@ def ls(
|
|||||||
for name, md in items:
|
for name, md in items:
|
||||||
mode = str(md.unix_mode)
|
mode = str(md.unix_mode)
|
||||||
size = (
|
size = (
|
||||||
human_byte_size(md.byte_size)
|
format_human_byte_size(md.byte_size)
|
||||||
if human_readable
|
if human_readable
|
||||||
else str(md.byte_size)
|
else str(md.byte_size)
|
||||||
)
|
)
|
||||||
|
|||||||
48
src/bsv/cli_utils.py
Normal file
48
src/bsv/cli_utils.py
Normal file
@@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
"""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"
|
||||||
@@ -22,7 +22,7 @@ from functools import total_ordering
|
|||||||
import os
|
import os
|
||||||
from pathlib import Path, PurePosixPath
|
from pathlib import Path, PurePosixPath
|
||||||
from stat import S_IFDIR, S_IFLNK, S_IFMT, S_IFREG, S_IMODE, filemode
|
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
|
from typing_extensions import Buffer
|
||||||
|
|
||||||
|
|||||||
56
tests/test_bsv/test_cli_utils.py
Normal file
56
tests/test_bsv/test_cli_utils.py
Normal file
@@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
"""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"
|
||||||
Reference in New Issue
Block a user