Repository: dandi/dandi-cli

Issue:
digest command
Primarily to facilitate user's access to `dandi-etag` digest which is to be introduced in https://github.com/dandi/dandi-cli/pull/474 .  Could be folded into #474 or developed independently after/on top.  Should by default provide our `dandi-etag` but allow for estimation of other digests we have supported in Digester as well, in particular md5 and sha256.

Comments:

Patch:
diff --git a/dandi/cli/cmd_digest.py b/dandi/cli/cmd_digest.py
new file mode 100644
index 00000000..41c9fa41
--- /dev/null
+++ b/dandi/cli/cmd_digest.py
@@ -0,0 +1,25 @@
+import click
+
+from .base import map_to_click_exceptions
+
+
+@click.command()
+@click.option(
+    "-d",
+    "--digest",
+    "digest_alg",
+    type=click.Choice(
+        ["dandi-etag", "md5", "sha1", "sha256", "sha512"], case_sensitive=False
+    ),
+    default="dandi-etag",
+    help="Digest algorithm to use",
+    show_default=True,
+)
+@click.argument("paths", nargs=-1, type=click.Path(exists=True, dir_okay=False))
+@map_to_click_exceptions
+def digest(paths, digest_alg):
+    """ Calculate file digests """
+    from ..support.digests import get_digest
+
+    for p in paths:
+        print(f"{p}:", get_digest(p, digest=digest_alg))
diff --git a/dandi/cli/command.py b/dandi/cli/command.py
index 2cd121a6..958c3538 100644
--- a/dandi/cli/command.py
+++ b/dandi/cli/command.py
@@ -127,18 +127,18 @@ def main(ctx, log_level, pdb=False):
         )
 
 
-from .cmd_download import download  # noqa: E402
-
 #
 # Commands in the main group
 #
+from .cmd_digest import digest  # noqa: E402
+from .cmd_download import download  # noqa: E402
 from .cmd_ls import ls  # noqa: E402
 from .cmd_organize import organize  # noqa: E402
 from .cmd_register import register  # noqa: E402
 from .cmd_upload import upload  # noqa: E402
 from .cmd_validate import validate  # noqa: E402
 
-__all_commands__ = (ls, organize, upload, download, validate, register)
+__all_commands__ = (ls, organize, upload, download, validate, register, digest)
 
 for cmd in __all_commands__:
     main.add_command(cmd)


Test patch:
diff --git a/dandi/cli/tests/test_digest.py b/dandi/cli/tests/test_digest.py
new file mode 100644
index 00000000..f54fb18d
--- /dev/null
+++ b/dandi/cli/tests/test_digest.py
@@ -0,0 +1,38 @@
+from pathlib import Path
+
+from click.testing import CliRunner
+import pytest
+
+from ..cmd_digest import digest
+
+
+def test_digest_default():
+    runner = CliRunner()
+    with runner.isolated_filesystem():
+        Path("file.txt").write_bytes(b"123")
+        r = runner.invoke(digest, ["file.txt"])
+        assert r.exit_code == 0
+        assert r.output == "file.txt: d022646351048ac0ba397d12dfafa304-1\n"
+
+
+@pytest.mark.parametrize(
+    "alg,filehash",
+    [
+        ("md5", "202cb962ac59075b964b07152d234b70"),
+        ("sha1", "40bd001563085fc35165329ea1ff5c5ecbdbbeef"),
+        ("sha256", "a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3"),
+        (
+            "sha512",
+            "3c9909afec25354d551dae21590bb26e38d53f2173b8d3dc3eee4c047e7a"
+            "b1c1eb8b85103e3be7ba613b31bb5c9c36214dc9f14a42fd7a2fdb84856b"
+            "ca5c44c2",
+        ),
+    ],
+)
+def test_digest(alg, filehash):
+    runner = CliRunner()
+    with runner.isolated_filesystem():
+        Path("file.txt").write_bytes(b"123")
+        r = runner.invoke(digest, ["--digest", alg, "file.txt"])
+        assert r.exit_code == 0
+        assert r.output == f"file.txt: {filehash}\n"