Skip to content

Commit 330d63a

Browse files
committed
feat: Tree json representation
Adds a JSON output allowing `pixi tree —json` to hopefully be easily consumable by tools like marimo. Closes #4130
1 parent 4c9f4c0 commit 330d63a

File tree

2 files changed

+66
-3
lines changed

2 files changed

+66
-3
lines changed

docs/reference/cli/pixi/tree.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ pixi tree [OPTIONS] [REGEX]
2222
: The environment to list packages for. Defaults to the default environment
2323
- <a id="arg---invert" href="#arg---invert">`--invert (-i)`</a>
2424
: Invert tree and show what depends on given package in the regex argument
25+
- <a id="arg---json" href="#arg---json">`--json`</a>
26+
: Show a JSON representation of the dependency tree
2527

2628
## Update Options
2729
- <a id="arg---no-lockfile-update" href="#arg---no-lockfile-update">`--no-lockfile-update`</a>

src/cli/tree.rs

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use pixi_manifest::FeaturesExt;
1313
use rattler_conda_types::Platform;
1414
use rattler_lock::LockedPackageRef;
1515
use regex::Regex;
16+
use serde::{Deserialize, Serialize};
1617

1718
use crate::{
1819
WorkspaceLocator, cli::cli_config::WorkspaceConfig, lock_file::UpdateLockFileOptions,
@@ -36,7 +37,7 @@ use super::cli_config::LockFileUpdateConfig;
3637
))]
3738
pub struct Args {
3839
/// List only packages matching a regular expression
39-
#[arg()]
40+
#[arg(conflicts_with = "json")]
4041
pub regex: Option<String>,
4142

4243
/// The platform to list packages for. Defaults to the current platform.
@@ -55,8 +56,12 @@ pub struct Args {
5556
pub lock_file_update_config: LockFileUpdateConfig,
5657

5758
/// Invert tree and show what depends on given package in the regex argument
58-
#[arg(short, long, requires = "regex")]
59+
#[arg(short, long, requires = "regex", conflicts_with = "json")]
5960
pub invert: bool,
61+
62+
/// Show a JSON representation of the dependency tree
63+
#[arg(long)]
64+
pub json: bool,
6065
}
6166

6267
struct Symbols {
@@ -108,7 +113,9 @@ pub async fn execute(args: Args) -> miette::Result<()> {
108113

109114
let stdout = std::io::stdout();
110115
let mut handle = stdout.lock();
111-
if args.invert {
116+
if args.json {
117+
print_json_dependency_tree(&mut handle, &dep_map, &direct_deps)?;
118+
} else if args.invert {
112119
print_inverted_dependency_tree(
113120
&mut handle,
114121
&invert_dep_map(&dep_map),
@@ -123,6 +130,60 @@ pub async fn execute(args: Args) -> miette::Result<()> {
123130
Ok(())
124131
}
125132

133+
#[derive(Debug, Clone, Serialize, Deserialize)]
134+
struct DependencyTreeNode {
135+
name: String,
136+
version: String,
137+
tags: Vec<String>,
138+
dependencies: Vec<DependencyTreeNode>,
139+
}
140+
141+
fn package_to_tree_node(
142+
package: &Package,
143+
dep_map: &HashMap<String, Package>,
144+
) -> DependencyTreeNode {
145+
let dependencies = package
146+
.dependencies
147+
.iter()
148+
.filter_map(|dep_name| dep_map.get(dep_name))
149+
.map(|dep| package_to_tree_node(dep, dep_map))
150+
.collect();
151+
152+
DependencyTreeNode {
153+
name: package.name.clone(),
154+
version: package.version.clone(),
155+
tags: Vec::new(),
156+
dependencies,
157+
}
158+
}
159+
160+
/// Print a JSON representation of the dependency tree
161+
fn print_json_dependency_tree(
162+
handle: &mut StdoutLock,
163+
dep_map: &HashMap<String, Package>,
164+
direct_deps: &HashSet<String>,
165+
) -> miette::Result<()> {
166+
let dependencies: Vec<DependencyTreeNode> = direct_deps
167+
.iter()
168+
.filter_map(|pkg_name| dep_map.get(pkg_name))
169+
.map(|pkg| package_to_tree_node(pkg, dep_map))
170+
.collect();
171+
let json = serde_json::to_string_pretty(&dependencies)
172+
.into_diagnostic()
173+
.wrap_err("Failed to serialize dependency tree to JSON")?;
174+
writeln!(handle, "{}", json)
175+
.map_err(|e| {
176+
if e.kind() == std::io::ErrorKind::BrokenPipe {
177+
// Exit gracefully
178+
std::process::exit(0);
179+
} else {
180+
e
181+
}
182+
})
183+
.into_diagnostic()
184+
.wrap_err("Failed to serialize dependency tree to JSON")
185+
}
186+
126187
/// Filter and print an inverted dependency tree
127188
fn print_inverted_dependency_tree(
128189
handle: &mut StdoutLock,

0 commit comments

Comments
 (0)