Skip to content
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
12 changes: 12 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,18 @@ jobs:
! pkgx -Qqq flubber-flubber
pkgx -Q

- name: query --json
run: |
pkgx -Q --json=v2 git | pkgx jq -e '.[0].project == "git-scm.org"'
pkgx -Q --json=v2 git | pkgx jq -e '.[0].programs | length > 0'
pkgx -Q --json=v2 | pkgx jq -e 'length > 0'

- name: query pkgspecs
run: |
pkgx -Q git-scm.org | grep -q git-scm.org
pkgx -Q node git | grep -q nodejs.org
! pkgx -Q git-scm.org@99999

- run: if [ $(find ~/.pkgx -name .tmp\* -type d | wc -l) -gt 0 ]; then
exit 1;
fi
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "pkgx"
description = "Run anything"
authors = ["Max Howell <mxcl@me.com>", "Jacob Heider <jacob@pkgx.dev>"]
license = "Apache-2.0"
version = "2.7.1"
version = "2.8.0"
edition = "2021"
repository = "https://github.com/pkgxdev/pkgx"

Expand Down
7 changes: 5 additions & 2 deletions crates/cli/src/args.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use console::style;

#[derive(PartialEq)]
pub enum Mode {
X,
Help,
Expand Down Expand Up @@ -114,8 +115,10 @@ pub fn parse() -> Args {
}
}
} else {
find_program = !arg.contains('/');
collecting_args = true;
if mode != Mode::Query {
find_program = !arg.contains('/');
collecting_args = true;
}
args.push(arg);
}
}
Expand Down
4 changes: 2 additions & 2 deletions crates/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
args::Mode::Query => {
let (conn, _, _, _) = setup(&flags).await?;
query::query(&args, flags.silent, &conn)
let (conn, _, config, _) = setup(&flags).await?;
query::query(&args, &flags, &conn, &config).await
}
args::Mode::X => {
let (mut conn, did_sync, config, mut spinner) = setup(&flags).await?;
Expand Down
174 changes: 155 additions & 19 deletions crates/cli/src/query.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,168 @@
use std::error::Error;

use libpkgx::pantry_db;
use libpkgx::{config::Config, inventory, pantry_db};
use rusqlite::{params, Connection};
use serde::Serialize;

pub fn query(args: &Vec<String>, silent: bool, conn: &Connection) -> Result<(), Box<dyn Error>> {
use crate::{
args::Flags,
resolve::{parse_pkgspec, Pkgspec},
};

#[derive(Serialize)]
struct QueryResult {
project: String,
programs: Vec<String>,
}

pub async fn query(
args: &Vec<String>,
flags: &Flags,
conn: &Connection,
config: &Config,
) -> Result<(), Box<dyn Error>> {
let is_json = flags.json == Some(2);
let silent = flags.silent;

// print out the whole list if no args
if args.is_empty() {
let mut stmt = conn.prepare("SELECT program FROM provides")?;
let mut rows = stmt.query(params![])?;
while let Some(row) = rows.next()? {
let program: String = row.get(0)?;
println!("{}", program);
// if they requested json, output full mapping of project -> programs
if is_json {
let mut stmt =
conn.prepare("SELECT DISTINCT project FROM provides ORDER BY project")?;
let mut rows = stmt.query(params![])?;
let mut results = Vec::new();
while let Some(row) = rows.next()? {
let project: String = row.get(0)?;
let programs = get_programs(conn, &project)?;
results.push(QueryResult { project, programs });
}
println!("{}", serde_json::to_string_pretty(&results)?);
// if not, just list all programs
} else {
let mut stmt = conn.prepare("SELECT program FROM provides")?;
let mut rows = stmt.query(params![])?;
while let Some(row) = rows.next()? {
let program: String = row.get(0)?;
println!("{}", program);
}
}
} else {
let mut fail = false;
for arg in args {
let projects = pantry_db::which(arg, conn)?;
if projects.is_empty() && silent {
return Ok(());
}

let mut results = Vec::new();
let mut fail = false;

for arg in args {
let mut pkgspec = parse_pkgspec(arg)?;

let projects = match &mut pkgspec {
Pkgspec::Req(req) if !req.project.contains('.') && req.constraint.raw == "*" => {
pantry_db::which(&req.project, conn)?
}
Pkgspec::Req(req) => match resolve_project(&req.project, conn) {
Ok(project) => {
req.project = project.clone();
vec![project]
}
Err(e) => {
if silent {
std::process::exit(1);
}
println!("{}", e);
fail = true;
continue;
}
},
Pkgspec::Latest(name) => match resolve_project(name, conn) {
Ok(project) => vec![project],
Err(e) => {
if silent {
std::process::exit(1);
}
println!("{}", e);
fail = true;
continue;
}
},
};

if projects.is_empty() {
if silent {
std::process::exit(1);
} else if projects.is_empty() {
println!("{} not found", arg);
fail = true;
} else if !silent {
println!("{}", projects.join(", "));
}
println!("{} not found", arg);
fail = true;
continue;
}

// validate version constraint if specified
if let Pkgspec::Req(req) = &pkgspec {
if req.constraint.raw != "*" {
let versions = inventory::ls(&projects[0], config).await?;
if !versions.iter().any(|v| req.constraint.satisfies(v)) {
if silent {
std::process::exit(1);
}
println!(
"no versions matching {} found for {}",
req.constraint.raw, projects[0]
);
fail = true;
continue;
}
}
}
if fail {
std::process::exit(1);

if is_json {
for project in &projects {
let programs = get_programs(conn, project)?;
results.push(QueryResult {
project: project.clone(),
programs,
});
}
} else if !silent {
println!("{}", projects.join(", "));
}
}

if is_json {
println!("{}", serde_json::to_string_pretty(&results)?);
}

if fail {
std::process::exit(1);
}

Ok(())
}

fn resolve_project(input: &str, conn: &Connection) -> Result<String, Box<dyn Error>> {
let projects = pantry_db::which(&input.to_string(), conn)?;
match projects.len() {
1 => Ok(projects[0].clone()),
0 => {
if input.contains('.') {
let mut stmt = conn.prepare("SELECT COUNT(*) FROM provides WHERE project = ?")?;
let count: i64 = stmt.query_row(params![input], |row| row.get(0))?;
if count > 0 {
return Ok(input.to_string());
}
}
Err(format!("{} not found", input).into())
}
_ => Err(format!("{} is ambiguous: {}", input, projects.join(", ")).into()),
}
}

fn get_programs(conn: &Connection, project: &str) -> Result<Vec<String>, Box<dyn Error>> {
let mut stmt =
conn.prepare("SELECT program FROM provides WHERE project = ? ORDER BY program")?;
let mut rows = stmt.query(params![project])?;
let mut programs = Vec::new();
while let Some(row) = rows.next()? {
programs.push(row.get(0)?);
}
Ok(programs)
}
4 changes: 2 additions & 2 deletions crates/cli/src/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ pub async fn resolve(
Ok((installations, graph))
}

enum Pkgspec {
pub enum Pkgspec {
Req(PackageReq),
Latest(String),
}
Expand Down Expand Up @@ -133,7 +133,7 @@ impl Pkgspec {
}
}

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