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
30 changes: 30 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,36 @@ Requirements
Usage
=====

CLI Tool
--------

py-multiaddr provides a command line interface to inspect multiaddrs and decode them into structured JSON.

.. code-block:: bash

$ multiaddr /ip4/1.2.3.4/tcp/80
{
"string": "/ip4/1.2.3.4/tcp/80",
"packed": "0x0401020304060050",
"packedSize": 8,
"components": [
{
"protocol": "ip4",
"code": 4,
"value": "1.2.3.4",
"rawValue": "0x01020304"
},
{
"protocol": "tcp",
"code": 6,
"value": "80",
"rawValue": "0x0050"
}
]
}

You can also pass a hex-encoded multiaddr prefixed with `0x`, e.g. `multiaddr 0x0401020304060050`. Use the `-c` or `--compact` flag to output on a single line.

Simple
------

Expand Down
63 changes: 63 additions & 0 deletions multiaddr/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import argparse
import json
import sys
from typing import Any

from . import __version__
from .multiaddr import Multiaddr
from .transforms import bytes_iter


def main() -> None:
parser = argparse.ArgumentParser(description="Inspect multiaddrs")
parser.add_argument("addr", help="Multiaddr string or hex bytes (0x...)")
parser.add_argument("-c", "--compact", action="store_true", help="Output compact JSON")
parser.add_argument(
"--version", action="version", version=f"%(prog)s {__version__}"
)

args = parser.parse_args()

try:
if args.addr.startswith("0x"):
addr_bytes = bytes.fromhex(args.addr[2:])
addr = Multiaddr(addr_bytes)
else:
addr = Multiaddr(args.addr)
except Exception as e:
print(f"Error parsing multiaddr: {e}", file=sys.stderr)
sys.exit(1)

try:
components = []
for _, proto, codec, value_bytes in bytes_iter(addr.to_bytes()):
component: dict[str, Any] = {
"protocol": proto.name,
"code": proto.code,
}
if codec.SIZE != 0:
component["value"] = codec.to_string(proto, value_bytes)
component["rawValue"] = "0x" + value_bytes.hex()
else:
component["value"] = None
component["rawValue"] = None
components.append(component)

except Exception as e:
print(f"Error decoding multiaddr components: {e}", file=sys.stderr)
sys.exit(1)

indent = None if args.compact else 2

output = {
"string": str(addr),
"packed": "0x" + addr.to_bytes().hex(),
"packedSize": len(addr.to_bytes()),
"components": components,
}

print(json.dumps(output, indent=indent))


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions newsfragments/119.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add a command-line interface (CLI) to decode multiaddrs into structured JSON.
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ dependencies = [
Homepage = "https://github.com/multiformats/py-multiaddr"
Download = "https://github.com/multiformats/py-multiaddr/tarball/0.2.0"

[project.scripts]
multiaddr = "multiaddr.cli:main"

[project.optional-dependencies]
dev = [
"Sphinx>=5.0.0",
Expand Down
59 changes: 59 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import json
import sys
from unittest.mock import patch

import pytest

from multiaddr import cli


def test_cli_string(capsys):
with patch.object(sys, "argv", ["multiaddr", "/ip4/1.2.3.4/tcp/80"]):
cli.main()

out, _ = capsys.readouterr()
data = json.loads(out)

assert data["string"] == "/ip4/1.2.3.4/tcp/80"
assert data["packed"] == "0x0401020304060050"
assert data["packedSize"] == 8
assert len(data["components"]) == 2

assert data["components"][0]["protocol"] == "ip4"
assert data["components"][0]["value"] == "1.2.3.4"
assert data["components"][0]["rawValue"] == "0x01020304"

assert data["components"][1]["protocol"] == "tcp"
assert data["components"][1]["value"] == "80"
assert data["components"][1]["rawValue"] == "0x0050"


def test_cli_hex(capsys):
with patch.object(sys, "argv", ["multiaddr", "0x0401020304060050"]):
cli.main()

out, _ = capsys.readouterr()
data = json.loads(out)

assert data["string"] == "/ip4/1.2.3.4/tcp/80"
assert data["packed"] == "0x0401020304060050"


def test_cli_compact(capsys):
with patch.object(sys, "argv", ["multiaddr", "-c", "/ip4/1.2.3.4"]):
cli.main()

out, _ = capsys.readouterr()
assert "\n" not in out.strip()
data = json.loads(out)
assert data["string"] == "/ip4/1.2.3.4"


def test_cli_invalid_input(capsys):
with patch.object(sys, "argv", ["multiaddr", "/invalid/input"]):
with pytest.raises(SystemExit) as e:
cli.main()
assert e.value.code == 1

_, err = capsys.readouterr()
assert "Error parsing multiaddr:" in err
Loading