Skip to content
Merged
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
37 changes: 22 additions & 15 deletions py3plex/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,12 @@ def create_parser() -> argparse.ArgumentParser:

parser.add_argument("--version", action="version", version=f"py3plex {__version__}")

subparsers = parser.add_subparsers(dest="command", help="Available commands")
subparsers = parser.add_subparsers(
dest="command",
title="commands",
metavar="COMMAND",
help="Available commands (run 'py3plex COMMAND --help' for details)",
)

# HELP command
subparsers.add_parser("help", help="Show detailed help information about py3plex")
Expand Down Expand Up @@ -438,7 +443,7 @@ def create_parser() -> argparse.ArgumentParser:
"--type",
choices=["random", "er", "ba", "ws"],
default="random",
help="Network type - Possible values: 'random' (random network, default), 'er' (Erdős-Rényi), 'ba' (Barabási-Albert preferential attachment), 'ws' (Watts-Strogatz small-world)",
help="Network model (default: random): random (random multilayer), er (Erdős-Rényi), ba (Barabási-Albert), ws (Watts-Strogatz)",
)
create_parser.add_argument(
"--probability",
Expand All @@ -460,7 +465,7 @@ def create_parser() -> argparse.ArgumentParser:
load_parser = subparsers.add_parser(
"load", help="Load and inspect a multilayer network (use '-' for stdin)"
)
load_parser.add_argument("input", help="Input network file (use '-' to read from stdin)")
load_parser.add_argument("input", help="Input network file (use '-' for stdin)")
load_parser.add_argument(
"--info", action="store_true", help="Display network information"
)
Expand All @@ -470,6 +475,7 @@ def create_parser() -> argparse.ArgumentParser:
load_parser.add_argument("--output", "-o", help="Save output to file (JSON format)")
load_parser.add_argument(
"--input-format",
dest="input_format",
choices=["auto", "multiedgelist", "edgelist", "json"],
default="auto",
help="Input format for stdin data (default: auto-detect)",
Expand All @@ -479,13 +485,13 @@ def create_parser() -> argparse.ArgumentParser:
community_parser = subparsers.add_parser(
"community", help="Detect communities in the network"
)
community_parser.add_argument("input", help="Input network file")
community_parser.add_argument("input", help="Input network file (use '-' for stdin)")
community_parser.add_argument(
"--algorithm",
"-a",
choices=["louvain", "infomap", "label_prop"],
default="louvain",
help="Community detection algorithm - Possible values: 'louvain' (Louvain method, default), 'infomap' (Infomap algorithm), 'label_prop' (Label propagation)",
help="Community detection algorithm (default: louvain): louvain, infomap, or label_prop (label propagation)",
)
community_parser.add_argument(
"--output", "-o", help="Output file for community assignments (JSON)"
Expand All @@ -501,13 +507,13 @@ def create_parser() -> argparse.ArgumentParser:
centrality_parser = subparsers.add_parser(
"centrality", help="Compute node centrality measures"
)
centrality_parser.add_argument("input", help="Input network file")
centrality_parser.add_argument("input", help="Input network file (use '-' for stdin)")
centrality_parser.add_argument(
"--measure",
"-m",
choices=["degree", "betweenness", "closeness", "eigenvector", "pagerank"],
default="degree",
help="Centrality measure - Possible values: 'degree' (degree centrality, default), 'betweenness' (betweenness centrality), 'closeness' (closeness centrality), 'eigenvector' (eigenvector centrality), 'pagerank' (PageRank)",
help="Centrality measure (default: degree): degree, betweenness, closeness, eigenvector, or pagerank",
)
centrality_parser.add_argument(
"--output", "-o", help="Output file for centrality scores (JSON)"
Expand All @@ -518,7 +524,7 @@ def create_parser() -> argparse.ArgumentParser:
stats_parser = subparsers.add_parser(
"stats", help="Compute multilayer network statistics"
)
stats_parser.add_argument("input", help="Input network file")
stats_parser.add_argument("input", help="Input network file (use '-' for stdin)")
stats_parser.add_argument(
"--measure",
"-m",
Expand All @@ -532,7 +538,7 @@ def create_parser() -> argparse.ArgumentParser:
"edge_overlap",
],
default="all",
help="Statistic to compute - Possible values: 'all' (compute all statistics, default), 'density' (network density), 'clustering' (clustering coefficient), 'layer_density' (density per layer), 'node_activity' (node activity across layers), 'versatility' (versatility centrality), 'edge_overlap' (edge overlap between layers)",
help="Statistic (default: all): all, density, clustering, layer_density, node_activity, versatility, or edge_overlap",
)
stats_parser.add_argument(
"--layer", help="Specific layer for layer-specific statistics"
Expand All @@ -545,7 +551,7 @@ def create_parser() -> argparse.ArgumentParser:
viz_parser = subparsers.add_parser(
"visualize", help="Visualize the multilayer network"
)
viz_parser.add_argument("input", help="Input network file")
viz_parser.add_argument("input", help="Input network file (use '-' for stdin)")
viz_parser.add_argument(
"--output",
"-o",
Expand All @@ -556,7 +562,7 @@ def create_parser() -> argparse.ArgumentParser:
"--layout",
choices=["spring", "circular", "kamada_kawai", "multilayer"],
default="multilayer",
help="Layout algorithm - Possible values: 'spring' (force-directed spring layout), 'circular' (circular layout), 'kamada_kawai' (Kamada-Kawai force layout), 'multilayer' (specialized multilayer layout, default)",
help="Layout algorithm (default: multilayer): spring, circular, kamada_kawai, or multilayer",
)
viz_parser.add_argument(
"--width", type=int, default=12, help="Figure width in inches (default: 12)"
Expand All @@ -569,12 +575,12 @@ def create_parser() -> argparse.ArgumentParser:
aggregate_parser = subparsers.add_parser(
"aggregate", help="Aggregate multilayer network into single layer"
)
aggregate_parser.add_argument("input", help="Input network file")
aggregate_parser.add_argument("input", help="Input network file (use '-' for stdin)")
aggregate_parser.add_argument(
"--method",
choices=["sum", "mean", "max", "min"],
default="sum",
help="Aggregation method for edge weights - Possible values: 'sum' (sum weights, default), 'mean' (average weights), 'max' (maximum weight), 'min' (minimum weight)",
help="Aggregation method (default: sum): sum, mean, max, or min",
)
aggregate_parser.add_argument(
"--output", "-o", required=True, help="Output file for aggregated network"
Expand All @@ -584,7 +590,7 @@ def create_parser() -> argparse.ArgumentParser:
convert_parser = subparsers.add_parser(
"convert", help="Convert network between different formats"
)
convert_parser.add_argument("input", help="Input network file")
convert_parser.add_argument("input", help="Input network file (use '-' for stdin)")
convert_parser.add_argument(
"--output",
"-o",
Expand Down Expand Up @@ -629,7 +635,7 @@ def create_parser() -> argparse.ArgumentParser:
)
query_parser.add_argument(
"input",
help="Input network file (use '-' to read from stdin)",
help="Input network file (use '-' for stdin)",
)
query_parser.add_argument(
"query",
Expand All @@ -652,6 +658,7 @@ def create_parser() -> argparse.ArgumentParser:
)
query_parser.add_argument(
"--input-format",
dest="input_format",
choices=["auto", "multiedgelist", "edgelist", "json"],
default="auto",
help="Input format for stdin data (default: auto-detect)",
Expand Down
19 changes: 19 additions & 0 deletions tests/test_cli_ergonomics.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,3 +255,22 @@ def test_create_help_mentions_edgelist(self, capsys):

# Check that edgelist is mentioned as a recommended format
assert "edgelist" in help_text.lower() or ".txt" in help_text.lower()

def test_help_text_without_redundant_possible_values(self, capsys):
"""Test that help text avoids redundant 'Possible values' phrasing."""
with pytest.raises(SystemExit):
cli.main(["create", "--help"])

captured = capsys.readouterr()
help_text = captured.out
assert "Possible values:" not in help_text

def test_file_input_commands_document_stdin(self, capsys):
"""Test that file-input commands consistently document stdin support."""
commands = ["load", "community", "centrality", "stats", "visualize", "aggregate", "convert", "query"]
for command in commands:
with pytest.raises(SystemExit):
cli.main([command, "--help"])
captured = capsys.readouterr()
help_text = captured.out
assert "Input network file (use '-' for stdin)" in help_text
Loading