Skip to content

Commit 82e573a

Browse files
authored
Merge pull request #1187 from felipecrs/revamp-query
Revamp --query
2 parents 4e86ff3 + c36fa49 commit 82e573a

File tree

7 files changed

+178
-27
lines changed

7 files changed

+178
-27
lines changed

.github/workflows/ci.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,18 @@ jobs:
167167
! pkgx -Qqq flubber-flubber
168168
pkgx -Q
169169
170+
- name: query --json
171+
run: |
172+
pkgx -Q --json=v2 git | pkgx jq -e '.[0].project == "git-scm.org"'
173+
pkgx -Q --json=v2 git | pkgx jq -e '.[0].programs | length > 0'
174+
pkgx -Q --json=v2 | pkgx jq -e 'length > 0'
175+
176+
- name: query pkgspecs
177+
run: |
178+
pkgx -Q git-scm.org | grep -q git-scm.org
179+
pkgx -Q node git | grep -q nodejs.org
180+
! pkgx -Q git-scm.org@99999
181+
170182
- run: if [ $(find ~/.pkgx -name .tmp\* -type d | wc -l) -gt 0 ]; then
171183
exit 1;
172184
fi

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/cli/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "pkgx"
33
description = "Run anything"
44
authors = ["Max Howell <mxcl@me.com>", "Jacob Heider <jacob@pkgx.dev>"]
55
license = "Apache-2.0"
6-
version = "2.7.1"
6+
version = "2.8.0"
77
edition = "2021"
88
repository = "https://github.com/pkgxdev/pkgx"
99

crates/cli/src/args.rs

Lines changed: 5 additions & 2 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,8 +115,10 @@ pub fn parse() -> Args {
114115
}
115116
}
116117
} else {
117-
find_program = !arg.contains('/');
118-
collecting_args = true;
118+
if mode != Mode::Query {
119+
find_program = !arg.contains('/');
120+
collecting_args = true;
121+
}
119122
args.push(arg);
120123
}
121124
}

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, &conn, &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: 155 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,168 @@
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;
56

6-
pub fn query(args: &Vec<String>, silent: bool, conn: &Connection) -> Result<(), Box<dyn Error>> {
7+
use crate::{
8+
args::Flags,
9+
resolve::{parse_pkgspec, Pkgspec},
10+
};
11+
12+
#[derive(Serialize)]
13+
struct QueryResult {
14+
project: String,
15+
programs: Vec<String>,
16+
}
17+
18+
pub async fn query(
19+
args: &Vec<String>,
20+
flags: &Flags,
21+
conn: &Connection,
22+
config: &Config,
23+
) -> Result<(), Box<dyn Error>> {
24+
let is_json = flags.json == Some(2);
25+
let silent = flags.silent;
26+
27+
// print out the whole list if no args
728
if args.is_empty() {
8-
let mut stmt = conn.prepare("SELECT program FROM provides")?;
9-
let mut rows = stmt.query(params![])?;
10-
while let Some(row) = rows.next()? {
11-
let program: String = row.get(0)?;
12-
println!("{}", program);
29+
// if they requested json, output full mapping of project -> programs
30+
if is_json {
31+
let mut stmt =
32+
conn.prepare("SELECT DISTINCT project FROM provides ORDER BY project")?;
33+
let mut rows = stmt.query(params![])?;
34+
let mut results = Vec::new();
35+
while let Some(row) = rows.next()? {
36+
let project: String = row.get(0)?;
37+
let programs = get_programs(conn, &project)?;
38+
results.push(QueryResult { project, programs });
39+
}
40+
println!("{}", serde_json::to_string_pretty(&results)?);
41+
// if not, just list all programs
42+
} else {
43+
let mut stmt = conn.prepare("SELECT program FROM provides")?;
44+
let mut rows = stmt.query(params![])?;
45+
while let Some(row) = rows.next()? {
46+
let program: String = row.get(0)?;
47+
println!("{}", program);
48+
}
1349
}
14-
} else {
15-
let mut fail = false;
16-
for arg in args {
17-
let projects = pantry_db::which(arg, conn)?;
18-
if projects.is_empty() && silent {
50+
return Ok(());
51+
}
52+
53+
let mut results = Vec::new();
54+
let mut fail = false;
55+
56+
for arg in args {
57+
let mut pkgspec = parse_pkgspec(arg)?;
58+
59+
let projects = match &mut pkgspec {
60+
Pkgspec::Req(req) if !req.project.contains('.') && req.constraint.raw == "*" => {
61+
pantry_db::which(&req.project, conn)?
62+
}
63+
Pkgspec::Req(req) => match resolve_project(&req.project, conn) {
64+
Ok(project) => {
65+
req.project = project.clone();
66+
vec![project]
67+
}
68+
Err(e) => {
69+
if silent {
70+
std::process::exit(1);
71+
}
72+
println!("{}", e);
73+
fail = true;
74+
continue;
75+
}
76+
},
77+
Pkgspec::Latest(name) => match resolve_project(name, conn) {
78+
Ok(project) => vec![project],
79+
Err(e) => {
80+
if silent {
81+
std::process::exit(1);
82+
}
83+
println!("{}", e);
84+
fail = true;
85+
continue;
86+
}
87+
},
88+
};
89+
90+
if projects.is_empty() {
91+
if silent {
1992
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(", "));
93+
}
94+
println!("{} not found", arg);
95+
fail = true;
96+
continue;
97+
}
98+
99+
// validate version constraint if specified
100+
if let Pkgspec::Req(req) = &pkgspec {
101+
if req.constraint.raw != "*" {
102+
let versions = inventory::ls(&projects[0], config).await?;
103+
if !versions.iter().any(|v| req.constraint.satisfies(v)) {
104+
if silent {
105+
std::process::exit(1);
106+
}
107+
println!(
108+
"no versions matching {} found for {}",
109+
req.constraint.raw, projects[0]
110+
);
111+
fail = true;
112+
continue;
113+
}
25114
}
26115
}
27-
if fail {
28-
std::process::exit(1);
116+
117+
if is_json {
118+
for project in &projects {
119+
let programs = get_programs(conn, project)?;
120+
results.push(QueryResult {
121+
project: project.clone(),
122+
programs,
123+
});
124+
}
125+
} else if !silent {
126+
println!("{}", projects.join(", "));
29127
}
30128
}
129+
130+
if is_json {
131+
println!("{}", serde_json::to_string_pretty(&results)?);
132+
}
133+
134+
if fail {
135+
std::process::exit(1);
136+
}
137+
31138
Ok(())
32139
}
140+
141+
fn resolve_project(input: &str, conn: &Connection) -> Result<String, Box<dyn Error>> {
142+
let projects = pantry_db::which(&input.to_string(), conn)?;
143+
match projects.len() {
144+
1 => Ok(projects[0].clone()),
145+
0 => {
146+
if input.contains('.') {
147+
let mut stmt = conn.prepare("SELECT COUNT(*) FROM provides WHERE project = ?")?;
148+
let count: i64 = stmt.query_row(params![input], |row| row.get(0))?;
149+
if count > 0 {
150+
return Ok(input.to_string());
151+
}
152+
}
153+
Err(format!("{} not found", input).into())
154+
}
155+
_ => Err(format!("{} is ambiguous: {}", input, projects.join(", ")).into()),
156+
}
157+
}
158+
159+
fn get_programs(conn: &Connection, project: &str) -> Result<Vec<String>, Box<dyn Error>> {
160+
let mut stmt =
161+
conn.prepare("SELECT program FROM provides WHERE project = ? ORDER BY program")?;
162+
let mut rows = stmt.query(params![project])?;
163+
let mut programs = Vec::new();
164+
while let Some(row) = rows.next()? {
165+
programs.push(row.get(0)?);
166+
}
167+
Ok(programs)
168+
}

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)