• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pkgxdev / pkgx / 15890332232

26 Jun 2025 12:49AM UTC coverage: 90.096%. First build
15890332232

Pull #1187

github

web-flow
Merge 60ceaf5a0 into 838eecd24
Pull Request #1187: Revamp --query

90 of 122 new or added lines in 6 files covered. (73.77%)

1592 of 1767 relevant lines covered (90.1%)

15800842.85 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

72.65
/crates/cli/src/query.rs
1
use std::error::Error;
2

3
use libpkgx::{config::Config, inventory, pantry_db};
4
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(
6✔
16
    pkgspec: &mut Pkgspec,
6✔
17
    conn: &Connection,
6✔
18
) -> Result<Vec<String>, Box<dyn Error>> {
6✔
19
    match pkgspec {
6✔
20
        Pkgspec::Req(pkgreq) => {
6✔
21
            // Check if this looks like a program name (no dots and wildcard constraint)
6✔
22
            if !pkgreq.project.contains('.') && pkgreq.constraint.raw == "*" {
6✔
23
                // Handle as program lookup
24
                Ok(pantry_db::which(&pkgreq.project, conn)?)
6✔
25
            } else {
26
                // Handle as package spec - resolve project name and return single project
NEW
27
                let (project, _) = resolve_project_name(&pkgreq.project, conn)?;
×
NEW
28
                pkgreq.project = project.clone();
×
NEW
29
                Ok(vec![project])
×
30
            }
31
        }
NEW
32
        Pkgspec::Latest(program_or_project) => {
×
NEW
33
            let (project, _) = resolve_project_name(program_or_project, conn)?;
×
NEW
34
            Ok(vec![project])
×
35
        }
36
    }
37
}
6✔
38

NEW
39
fn resolve_project_name(
×
40
    input: &str,
41
    conn: &Connection,
42
) -> Result<(String, String), Box<dyn Error>> {
NEW
43
    let original = input.to_string();
×
44

45
    // First, try to resolve as a program name
NEW
46
    let projects = pantry_db::which(&input.to_string(), conn)?;
×
NEW
47
    match projects.len() {
×
48
        0 => {
49
            // If not found as a program and contains a dot, check if it exists as a project
NEW
50
            if input.contains('.') {
×
NEW
51
                let mut stmt = conn.prepare("SELECT COUNT(*) FROM provides WHERE project = ?")?;
×
NEW
52
                let count: i64 = stmt.query_row(params![input], |row| row.get(0))?;
×
NEW
53
                if count > 0 {
×
NEW
54
                    return Ok((input.to_string(), original));
×
55
                }
56
            }
NEW
57
            Err(format!("Package '{}' not found", original).into())
×
58
        }
NEW
59
        1 => Ok((projects[0].clone(), original)),
×
NEW
60
        _ => Err(format!(
×
61
            "Package '{}' is ambiguous: {}",
62
            original,
NEW
63
            projects.join(", ")
×
64
        )
NEW
65
        .into()),
×
66
    }
67
}
68

69
fn get_programs_for_project(
2,616✔
70
    project: &str,
2,616✔
71
    conn: &Connection,
2,616✔
72
) -> Result<Vec<String>, Box<dyn Error>> {
2,616✔
73
    let mut stmt =
2,616✔
74
        conn.prepare("SELECT program FROM provides WHERE project = ? ORDER BY program")?;
2,616✔
75
    let mut rows = stmt.query(params![project])?;
2,616✔
76
    let mut programs = Vec::new();
2,616✔
77
    while let Some(row) = rows.next()? {
10,261✔
78
        programs.push(row.get(0)?);
7,645✔
79
    }
80
    Ok(programs)
2,616✔
81
}
2,616✔
82

83
async fn process_query_arg(
6✔
84
    arg: &str,
6✔
85
    conn: &Connection,
6✔
86
    config: &Config,
6✔
87
) -> Result<Vec<QueryResult>, Box<dyn Error>> {
6✔
88
    let mut pkgspec = parse_pkgspec(arg)?;
6✔
89
    let projects = resolve_projects_for_pkgspec(&mut pkgspec, conn)?;
6✔
90

91
    if projects.is_empty() {
6✔
92
        let name = match &pkgspec {
4✔
93
            Pkgspec::Req(req) => &req.project,
4✔
NEW
94
            Pkgspec::Latest(project) => project,
×
95
        };
96
        return Err(format!("{} not found", name).into());
4✔
97
    }
2✔
98

2✔
99
    let mut results = Vec::new();
2✔
100

101
    // Determine which projects to process
102
    let projects_to_process = match &pkgspec {
2✔
103
        Pkgspec::Req(pkgreq) if !pkgreq.project.contains('.') && pkgreq.constraint.raw == "*" => {
2✔
104
            // For program lookups (no dots and wildcard), process all matching projects
2✔
105
            &projects
2✔
106
        }
107
        _ => {
108
            // For package specs and latest, process first project only
NEW
109
            &projects[0..1]
×
110
        }
111
    };
112

113
    // Process each project
114
    for project in projects_to_process {
4✔
115
        // For version specs with constraints, check if any matching versions are available
116
        if let Pkgspec::Req(pkgreq) = &pkgspec {
2✔
117
            if pkgreq.constraint.raw != "*" {
2✔
NEW
118
                match inventory::ls(project, config).await {
×
NEW
119
                    Ok(versions) => {
×
NEW
120
                        let matching_versions: Vec<_> = versions
×
121
                            .iter()
NEW
122
                            .filter(|v| pkgreq.constraint.satisfies(v))
×
123
                            .collect();
124

NEW
125
                        if matching_versions.is_empty() {
×
NEW
126
                            return Err(format!(
×
127
                                "No versions matching {} found for {}",
128
                                pkgreq.constraint.raw, project
129
                            )
130
                            .into());
131
                        }
132
                    }
133
                    Err(_) => {
NEW
134
                        return Err(format!("Failed to get versions for {}", project).into());
×
135
                    }
136
                }
137
            }
2✔
138
        }
139

140
        let programs = get_programs_for_project(project, conn)?;
2✔
141
        results.push(QueryResult {
2✔
142
            project: project.clone(),
2✔
143
            programs,
2✔
144
        });
2✔
145
    }
146

147
    Ok(results)
2✔
148
}
6✔
149

150
fn format_standard_output(results: &[QueryResult]) -> Vec<String> {
4✔
151
    results
4✔
152
        .iter()
4✔
153
        .map(|result| result.project.clone())
2,616✔
154
        .collect()
4✔
155
}
4✔
156

NEW
157
fn format_json_output(results: &[QueryResult]) -> String {
×
NEW
158
    serde_json::to_string_pretty(results).unwrap_or_else(|_| "[]".to_string())
×
159
}
160

161
pub async fn query(
8✔
162
    args: &Vec<String>,
8✔
163
    silent: bool,
8✔
164
    conn: &Connection,
8✔
165
    json_version: Option<isize>,
8✔
166
    config: &Config,
8✔
167
) -> Result<(), Box<dyn Error>> {
8✔
168
    let is_json = json_version == Some(2);
8✔
169
    let mut all_results = Vec::new();
8✔
170

8✔
171
    if args.is_empty() {
8✔
172
        let mut stmt = conn.prepare("SELECT DISTINCT project FROM provides ORDER BY project")?;
2✔
173
        let mut rows = stmt.query(params![])?;
2✔
174

175
        while let Some(row) = rows.next()? {
2,616✔
176
            let project: String = row.get(0)?;
2,614✔
177
            let programs = get_programs_for_project(&project, conn)?;
2,614✔
178
            all_results.push(QueryResult { project, programs });
2,614✔
179
        }
180
    } else {
181
        for arg in args {
8✔
182
            let results = process_query_arg(arg, conn, config).await?;
6✔
183
            all_results.extend(results);
2✔
184
        }
185
    }
186

187
    if is_json {
4✔
NEW
188
        println!("{}", format_json_output(&all_results));
×
189
    } else if !silent {
4✔
190
        let output_lines = format_standard_output(&all_results);
4✔
191
        for line in output_lines {
2,620✔
192
            println!("{}", line);
2,616✔
193
        }
2,616✔
194
    }
195

196
    Ok(())
4✔
197
}
8✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc