Skip to content

Commit ea5214b

Browse files
committed
Revamp --query
1 parent 1d0c081 commit ea5214b

File tree

6 files changed

+254
-24
lines changed

6 files changed

+254
-24
lines changed

.vscode/launch.json

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
// Use IntelliSense to learn about possible attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": [
7+
{
8+
"type": "lldb",
9+
"request": "launch",
10+
"name": "Debug executable 'pkgx'",
11+
"cargo": {
12+
"args": ["build", "--bin=pkgx", "--package=pkgx"],
13+
"filter": {
14+
"name": "pkgx",
15+
"kind": "bin"
16+
}
17+
},
18+
"args": ["+git", "--json=v2"],
19+
"cwd": "${workspaceFolder}"
20+
},
21+
{
22+
"type": "lldb",
23+
"request": "launch",
24+
"name": "Debug unit tests in executable 'pkgx'",
25+
"cargo": {
26+
"args": ["test", "--no-run", "--bin=pkgx", "--package=pkgx"],
27+
"filter": {
28+
"name": "pkgx",
29+
"kind": "bin"
30+
}
31+
},
32+
"args": [],
33+
"cwd": "${workspaceFolder}"
34+
},
35+
{
36+
"type": "lldb",
37+
"request": "launch",
38+
"name": "Debug unit tests in library 'libpkgx'",
39+
"cargo": {
40+
"args": ["test", "--no-run", "--lib", "--package=libpkgx"],
41+
"filter": {
42+
"name": "libpkgx",
43+
"kind": "lib"
44+
}
45+
},
46+
"args": [],
47+
"cwd": "${workspaceFolder}"
48+
}
49+
]
50+
}

.vscode/settings.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"rust-analyzer.check.command": "clippy",
3+
"rust-analyzer.rustfmt.rangeFormatting.enable": true,
4+
"[rust]": {
5+
"editor.defaultFormatter": "rust-lang.rust-analyzer",
6+
"editor.formatOnSave": true
7+
}
8+
}

crates/cli/src/args.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use console::style;
22

3+
#[derive(PartialEq)]
34
pub enum Mode {
45
X,
56
Help,
@@ -114,9 +115,15 @@ pub fn parse() -> Args {
114115
}
115116
}
116117
} else {
117-
find_program = !arg.contains('/');
118-
collecting_args = true;
119-
args.push(arg);
118+
// Only start collecting args if not in query mode, or if we're already collecting
119+
if mode == Mode::Query && !collecting_args {
120+
// In query mode, continue processing flags until we hit a non-flag argument
121+
args.push(arg);
122+
} else {
123+
find_program = !arg.contains('/');
124+
collecting_args = true;
125+
args.push(arg);
126+
}
120127
}
121128
}
122129

crates/cli/src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
4242
Ok(())
4343
}
4444
args::Mode::Query => {
45-
let (conn, _, _, _) = setup(&flags).await?;
46-
query::query(&args, flags.silent, &conn)
45+
let (conn, _, config, _) = setup(&flags).await?;
46+
query::query(&args, flags.silent, &conn, flags.json, &config).await
4747
}
4848
args::Mode::X => {
4949
let (mut conn, did_sync, config, mut spinner) = setup(&flags).await?;

crates/cli/src/query.rs

Lines changed: 182 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,197 @@
11
use std::error::Error;
22

3-
use libpkgx::pantry_db;
3+
use libpkgx::{config::Config, inventory, pantry_db};
44
use rusqlite::{params, Connection};
5+
use serde::Serialize;
6+
7+
use crate::resolve::{parse_pkgspec, Pkgspec};
8+
9+
#[derive(Serialize, Clone)]
10+
struct QueryResult {
11+
project: String,
12+
programs: Vec<String>,
13+
}
14+
15+
fn resolve_projects_for_pkgspec(
16+
pkgspec: &mut Pkgspec,
17+
conn: &Connection,
18+
) -> Result<Vec<String>, Box<dyn Error>> {
19+
match pkgspec {
20+
Pkgspec::Req(pkgreq) => {
21+
// Check if this looks like a program name (no dots and wildcard constraint)
22+
if !pkgreq.project.contains('.') && pkgreq.constraint.raw == "*" {
23+
// Handle as program lookup
24+
Ok(pantry_db::which(&pkgreq.project, conn)?)
25+
} else {
26+
// Handle as package spec - resolve project name and return single project
27+
let (project, _) = resolve_project_name(&pkgreq.project, conn)?;
28+
pkgreq.project = project.clone();
29+
Ok(vec![project])
30+
}
31+
}
32+
Pkgspec::Latest(program_or_project) => {
33+
let (project, _) = resolve_project_name(program_or_project, conn)?;
34+
Ok(vec![project])
35+
}
36+
}
37+
}
38+
39+
fn resolve_project_name(
40+
input: &str,
41+
conn: &Connection,
42+
) -> Result<(String, String), Box<dyn Error>> {
43+
let original = input.to_string();
44+
45+
// First, try to resolve as a program name
46+
let projects = pantry_db::which(&input.to_string(), conn)?;
47+
match projects.len() {
48+
0 => {
49+
// If not found as a program and contains a dot, check if it exists as a project
50+
if input.contains('.') {
51+
let mut stmt = conn.prepare("SELECT COUNT(*) FROM provides WHERE project = ?")?;
52+
let count: i64 = stmt.query_row(params![input], |row| row.get(0))?;
53+
if count > 0 {
54+
return Ok((input.to_string(), original));
55+
}
56+
}
57+
Err(format!("Package '{}' not found", original).into())
58+
}
59+
1 => Ok((projects[0].clone(), original)),
60+
_ => Err(format!(
61+
"Package '{}' is ambiguous: {}",
62+
original,
63+
projects.join(", ")
64+
)
65+
.into()),
66+
}
67+
}
68+
69+
fn get_programs_for_project(
70+
project: &str,
71+
conn: &Connection,
72+
) -> Result<Vec<String>, Box<dyn Error>> {
73+
let mut stmt =
74+
conn.prepare("SELECT program FROM provides WHERE project = ? ORDER BY program")?;
75+
let mut rows = stmt.query(params![project])?;
76+
let mut programs = Vec::new();
77+
while let Some(row) = rows.next()? {
78+
programs.push(row.get(0)?);
79+
}
80+
Ok(programs)
81+
}
82+
83+
async fn process_query_arg(
84+
arg: &str,
85+
conn: &Connection,
86+
config: &Config,
87+
) -> Result<Vec<QueryResult>, Box<dyn Error>> {
88+
let mut pkgspec = parse_pkgspec(arg)?;
89+
let projects = resolve_projects_for_pkgspec(&mut pkgspec, conn)?;
90+
91+
if projects.is_empty() {
92+
let name = match &pkgspec {
93+
Pkgspec::Req(req) => &req.project,
94+
Pkgspec::Latest(project) => project,
95+
};
96+
return Err(format!("{} not found", name).into());
97+
}
98+
99+
let mut results = Vec::new();
100+
101+
// Determine which projects to process
102+
let projects_to_process = match &pkgspec {
103+
Pkgspec::Req(pkgreq) if !pkgreq.project.contains('.') && pkgreq.constraint.raw == "*" => {
104+
// For program lookups (no dots and wildcard), process all matching projects
105+
&projects
106+
}
107+
_ => {
108+
// For package specs and latest, process first project only
109+
&projects[0..1]
110+
}
111+
};
112+
113+
// Process each project
114+
for project in projects_to_process {
115+
// For version specs with constraints, check if any matching versions are available
116+
if let Pkgspec::Req(pkgreq) = &pkgspec {
117+
if pkgreq.constraint.raw != "*" {
118+
match inventory::ls(project, config).await {
119+
Ok(versions) => {
120+
let matching_versions: Vec<_> = versions
121+
.iter()
122+
.filter(|v| pkgreq.constraint.satisfies(v))
123+
.collect();
124+
125+
if matching_versions.is_empty() {
126+
return Err(format!(
127+
"No versions matching {} found for {}",
128+
pkgreq.constraint.raw, project
129+
)
130+
.into());
131+
}
132+
}
133+
Err(_) => {
134+
return Err(format!("Failed to get versions for {}", project).into());
135+
}
136+
}
137+
}
138+
}
139+
140+
let programs = get_programs_for_project(project, conn)?;
141+
results.push(QueryResult {
142+
project: project.clone(),
143+
programs,
144+
});
145+
}
146+
147+
Ok(results)
148+
}
149+
150+
fn format_standard_output(results: &[QueryResult]) -> Vec<String> {
151+
results
152+
.iter()
153+
.map(|result| result.project.clone())
154+
.collect()
155+
}
156+
157+
fn format_json_output(results: &[QueryResult]) -> String {
158+
serde_json::to_string_pretty(results).unwrap_or_else(|_| "[]".to_string())
159+
}
160+
161+
pub async fn query(
162+
args: &Vec<String>,
163+
silent: bool,
164+
conn: &Connection,
165+
json_version: Option<isize>,
166+
config: &Config,
167+
) -> Result<(), Box<dyn Error>> {
168+
let is_json = json_version == Some(2);
169+
let mut all_results = Vec::new();
5170

6-
pub fn query(args: &Vec<String>, silent: bool, conn: &Connection) -> Result<(), Box<dyn Error>> {
7171
if args.is_empty() {
8-
let mut stmt = conn.prepare("SELECT program FROM provides")?;
172+
let mut stmt = conn.prepare("SELECT DISTINCT project FROM provides ORDER BY project")?;
9173
let mut rows = stmt.query(params![])?;
174+
10175
while let Some(row) = rows.next()? {
11-
let program: String = row.get(0)?;
12-
println!("{}", program);
176+
let project: String = row.get(0)?;
177+
let programs = get_programs_for_project(&project, conn)?;
178+
all_results.push(QueryResult { project, programs });
13179
}
14180
} else {
15-
let mut fail = false;
16181
for arg in args {
17-
let projects = pantry_db::which(arg, conn)?;
18-
if projects.is_empty() && silent {
19-
std::process::exit(1);
20-
} else if projects.is_empty() {
21-
println!("{} not found", arg);
22-
fail = true;
23-
} else if !silent {
24-
println!("{}", projects.join(", "));
25-
}
182+
let results = process_query_arg(arg, conn, config).await?;
183+
all_results.extend(results);
26184
}
27-
if fail {
28-
std::process::exit(1);
185+
}
186+
187+
if is_json {
188+
println!("{}", format_json_output(&all_results));
189+
} else if !silent {
190+
let output_lines = format_standard_output(&all_results);
191+
for line in output_lines {
192+
println!("{}", line);
29193
}
30194
}
195+
31196
Ok(())
32197
}

crates/cli/src/resolve.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ pub async fn resolve(
9191
Ok((installations, graph))
9292
}
9393

94-
enum Pkgspec {
94+
pub enum Pkgspec {
9595
Req(PackageReq),
9696
Latest(String),
9797
}
@@ -133,7 +133,7 @@ impl Pkgspec {
133133
}
134134
}
135135

136-
fn parse_pkgspec(pkgspec: &str) -> Result<Pkgspec, Box<dyn std::error::Error>> {
136+
pub fn parse_pkgspec(pkgspec: &str) -> Result<Pkgspec, Box<dyn std::error::Error>> {
137137
if let Some(project) = pkgspec.strip_suffix("@latest") {
138138
Ok(Pkgspec::Latest(project.to_string()))
139139
} else {

0 commit comments

Comments
 (0)