Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

conan list --format=compact #15011

Merged
merged 2 commits into from
Oct 25, 2023
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
62 changes: 61 additions & 1 deletion conan/cli/commands/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,65 @@ def format_timestamps(item):
print_serial(info)


def print_list_compact(results):
info = results["results"]
# Extract command single package name
new_info = {}

for remote, remote_info in info.items():
if not remote_info or "error" in remote_info:
new_info[remote] = {"warning": "There are no matching recipe references"}
continue
new_remote_info = {}
for ref, ref_info in remote_info.items():
new_ref_info = {}
for rrev, rrev_info in ref_info.get("revisions", {}).items():
new_rrev_info = {}
new_rrev = f"{ref}#{rrev}"
timestamp = rrev_info.get("timestamp")
if timestamp:
new_rrev += f" ({timestamp_to_str(timestamp)})"
# collect all options
common_options = {}
for pid, pid_info in rrev_info.get("packages", {}).items():
options = pid_info.get("info", {}).get("options", {})
common_options.update(options)
for pid, pid_info in rrev_info.get("packages", {}).items():
options = pid_info.get("info", {}).get("options")
if options: # If a package has no options, like header-only, skip
common_options = {k: v for k, v in common_options.items()
if k in options and v == options[k]}
for pid, pid_info in rrev_info.get("packages", {}).items():
options = pid_info.get("info", {}).get("options")
if options:
for k, v in options.items():
if v != common_options.get(k):
common_options.pop(k, None)
# format options
for pid, pid_info in rrev_info.get("packages", {}).items():
new_pid = f"{ref}#{rrev}:{pid}"
new_pid_info = {}
info = pid_info.get("info")
settings = info.get("settings")
if settings: # A bit of pretty order, first OS-ARCH
values = [settings.pop(s, None)
for s in ("os", "arch", "build_type", "compiler")]
values = [v for v in values if v is not None]
values.extend(settings.values())
new_pid_info["settings"] = ", ".join(values)
options = info.get("options")
if options:
diff_options = {k: v for k, v in options.items() if k not in common_options}
options = ", ".join(f"{k}={v}" for k, v in diff_options.items())
new_pid_info["options(diff)"] = options
new_rrev_info[new_pid] = new_pid_info
new_ref_info[new_rrev] = new_rrev_info
new_remote_info[ref] = new_ref_info
new_info[remote] = new_remote_info

print_serial(new_info)


def print_list_json(data):
results = data["results"]
myjson = json.dumps(results, indent=4)
Expand All @@ -90,7 +149,8 @@ def print_list_json(data):

@conan_command(group="Consumer", formatters={"text": print_list_text,
"json": print_list_json,
"html": list_packages_html})
"html": list_packages_html,
"compact": print_list_compact})
def list(conan_api: ConanAPI, parser, *args):
"""
List existing recipes, revisions, or packages in the cache (by default) or the remotes.
Expand Down
50 changes: 47 additions & 3 deletions conans/test/integration/command_v2/list_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -731,9 +731,8 @@ def test_list_html(self):
c.run("create dep")
c.run("create pkg -s os=Windows -s arch=x86")
# Revision is needed explicitly!
c.run("list pkg/2.3.4#latest --format=html", redirect_stdout="table.html")
table = c.load("table.html")
assert "<!DOCTYPE html>" in table
c.run("list pkg/2.3.4#latest --format=html")
assert "<!DOCTYPE html>" in c.stdout
# TODO: The actual good html is missing

def test_list_html_custom(self):
Expand All @@ -747,3 +746,48 @@ def test_list_html_custom(self):
c.save({"list_packages.html": '{{ base_template_path }}'}, path=template_folder)
c.run("list lib/0.1#latest --format=html")
assert template_folder in c.stdout


class TestListCompact:
def test_list_compact(self):
c = TestClient()
c.save({"conanfile.py": GenConanfile("pkg", "1.0").with_settings("os", "arch")
memsharded marked this conversation as resolved.
Show resolved Hide resolved
.with_shared_option(False)})
c.run("create . -s os=Windows -s arch=x86")
c.run("create . -s os=Linux -s arch=armv8")
c.run("create . -s os=Macos -s arch=armv8 -o shared=True")
c.run("list pkg:* --format=compact")

expected = textwrap.dedent("""\
pkg/1.0#03591c8b22497dd74214e08b3bf2a56f:2a67a51fbf36a4ee345b2125dd2642be60ffd3ec
settings: Macos, armv8
options(diff): shared=True
pkg/1.0#03591c8b22497dd74214e08b3bf2a56f:2d46abc802bbffdf2af11591e3e452bc6149ea2b
settings: Linux, armv8
options(diff): shared=False
pkg/1.0#03591c8b22497dd74214e08b3bf2a56f:d2e97769569ac0a583d72c10a37d5ca26de7c9fa
settings: Windows, x86
options(diff): shared=False
""")
assert textwrap.indent(expected, " ") in c.stdout

def test_list_compact_no_settings_no_options(self):
c = TestClient()
c.save({"pkg/conanfile.py": GenConanfile("pkg", "1.0").with_settings("os", "arch"),
"other/conanfile.py": GenConanfile("other", "1.0")})
c.run("create pkg -s os=Windows -s arch=x86")
c.run("create other")
c.run("list *:* --format=compact")
expected_output = re.sub(r"\(.*\)", "(timestamp)", c.stdout)
expected = textwrap.dedent("""\
Local Cache
other/1.0
other/1.0#d3c8cc5e6d23ca8c6f0eaa6285c04cbd (timestamp)
other/1.0#d3c8cc5e6d23ca8c6f0eaa6285c04cbd:da39a3ee5e6b4b0d3255bfef95601890afd80709
pkg/1.0
pkg/1.0#d24b74828b7681f08d8f5ba0e7fd791e (timestamp)
pkg/1.0#d24b74828b7681f08d8f5ba0e7fd791e:c11e463c49652ba9c5adc62573ee49f966bd8417
settings: Windows, x86
""")

assert expected == expected_output