Skip to content
This repository was archived by the owner on Jan 13, 2025. It is now read-only.

oops, wasn’t created the db fully #13

Merged
merged 1 commit into from
Jan 7, 2025
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
93 changes: 66 additions & 27 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use config::Config;
use execve::execve;
use hydrate::hydrate;
use resolve::resolve;
use rusqlite::Connection;
use types::PackageReq;

#[tokio::main]
Expand All @@ -47,13 +48,47 @@ async fn main() -> Result<(), Box<dyn Error>> {

let config = Config::new()?;

let conn = if sync::should(&config) {
sync::replace(&config).await?
let cache_dir = config.pantry_dir.parent().unwrap();
std::fs::create_dir_all(cache_dir)?;
let mut conn = Connection::open(cache_dir.join("pantry.db"))?;

let did_sync = if sync::should(&config) {
sync::replace(&config, &mut conn).await?;
true
} else {
rusqlite::Connection::open(config.pantry_dir.parent().unwrap().join("pantry.db"))?
false
};

let mut pkgs = vec![];

if find_program {
let PackageReq {
constraint,
project: cmd,
} = PackageReq::parse(&args[0])?;

args[0] = cmd.clone(); // invoke eg. `node` rather than eg. `node@20`

let project = match which(&cmd, &conn).await {
Err(WhichError::CmdNotFound(cmd)) => {
if !did_sync {
// cmd not found ∴ sync in case it is new
// sync::replace(&config, &mut conn).await?;
which(&cmd, &conn).await
} else {
Err(WhichError::CmdNotFound(cmd))
}
}
Err(err) => Err(err),
Ok(project) => Ok(project),
}?;

pkgs.push(PackageReq {
project,
constraint,
});
}

for pkgspec in plus {
let PackageReq {
project: project_or_cmd,
Expand All @@ -70,27 +105,14 @@ async fn main() -> Result<(), Box<dyn Error>> {
constraint,
});
} else {
let project = which(&project_or_cmd, &conn)?;
let project = which(&project_or_cmd, &conn).await?;
pkgs.push(PackageReq {
project,
constraint,
});
}
}

if find_program {
let PackageReq {
constraint,
project: cmd,
} = PackageReq::parse(&args[0])?;
args[0] = cmd.clone(); // invoke eg. `node` rather than `node@20`
let project = which(&cmd, &conn)?;
pkgs.push(PackageReq {
project,
constraint,
});
}

let companions = pantry_db::companions_for_projects(
&pkgs
.iter()
Expand Down Expand Up @@ -127,7 +149,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
}

let cmd = if find_program {
utils::find_program(&args.remove(0), &env["PATH"], &config).await?
utils::find_program(&args.remove(0), &env["PATH"]).await?
} else if args[0].contains('/') {
// user specified a path to program which we should use
args.remove(0)
Expand All @@ -147,7 +169,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
.collect::<Vec<String>>(),
);
}
utils::find_program(&args.remove(0), &paths, &config).await?
utils::find_program(&args.remove(0), &paths).await?
};
let env = env::mix(env);
let mut env = env::mix_runtime(&env, &installations, &conn)?;
Expand All @@ -169,17 +191,34 @@ async fn main() -> Result<(), Box<dyn Error>> {
}
}

fn which(cmd: &String, conn: &rusqlite::Connection) -> Result<String, Box<dyn Error>> {
let candidates = pantry_db::which(cmd, conn)?;
#[derive(Debug)]
pub enum WhichError {
CmdNotFound(String),
MultipleProjects(String, Vec<String>),
DbError(rusqlite::Error),
}

impl std::fmt::Display for WhichError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
WhichError::CmdNotFound(cmd) => write!(f, "cmd not found: {}", cmd),
WhichError::MultipleProjects(cmd, projects) => {
write!(f, "multiple projects found for {}: {:?}", cmd, projects)
}
WhichError::DbError(err) => write!(f, "db error: {}", err),
}
}
}

impl std::error::Error for WhichError {}

async fn which(cmd: &String, conn: &Connection) -> Result<String, WhichError> {
let candidates = pantry_db::which(cmd, conn).map_err(WhichError::DbError)?;
if candidates.len() == 1 {
Ok(candidates[0].clone())
} else if candidates.is_empty() {
return Err(format!("cmd not found: {}", cmd).into());
return Err(WhichError::CmdNotFound(cmd.clone()));
} else {
return Err(format!(
"cmd `{}` provided by multiple projects: {:?}",
cmd, candidates
)
.into());
return Err(WhichError::MultipleProjects(cmd.clone(), candidates));
}
}
174 changes: 149 additions & 25 deletions src/pantry.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::{config::Config, types::PackageReq};
use libsemverator::range::Range as VersionReq;
use serde::Deserialize;
use serde::Deserializer;
use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;
Expand All @@ -9,29 +10,30 @@ pub struct PantryEntry {
pub project: String,
pub deps: Vec<PackageReq>,
pub programs: Vec<String>,
pub companions: Vec<String>,
pub companions: Vec<PackageReq>,
pub env: HashMap<String, String>,
}

impl PantryEntry {
fn from_path(path: &PathBuf) -> Result<Self, Box<dyn std::error::Error>> {
fn from_path(path: &PathBuf, pantry_dir: &PathBuf) -> Result<Self, Box<dyn std::error::Error>> {
let project = path
.parent()
.unwrap()
.file_name()
.strip_prefix(pantry_dir)
.unwrap()
.to_str()
.unwrap()
.to_string();

Self::from_raw_entry(RawPantryEntry::from_path(path)?, project)
}

fn from_raw_entry(
entry: RawPantryEntry,
project: String,
) -> Result<Self, Box<dyn std::error::Error>> {
if let Some(deps) = entry.dependencies {
let deps = deps
let deps = if let Some(deps) = entry.dependencies {
deps.0
.iter()
.map(|(k, v)| {
// if v is a number, prefix with ^
Expand All @@ -45,37 +47,64 @@ impl PantryEntry {
constraint,
})
})
.collect::<Result<Vec<_>, _>>()?;
.collect::<Result<Vec<_>, _>>()?
} else {
vec![]
};

let env = HashMap::new();
let programs = if let Some(provides) = entry.provides {
provides.0
} else {
vec![]
};

Ok(Self {
deps,
project,
env,
companions: vec![],
programs: vec![],
})
let companions = if let Some(companions) = entry.companions {
companions
.0
.iter()
.map(|(k, v)| {
// if v is a number, prefix with ^
let v = if v.chars().next().unwrap().is_ascii_digit() {
format!("^{}", v)
} else {
v.clone()
};
VersionReq::parse(&v).map(|constraint| PackageReq {
project: k.clone(),
constraint,
})
})
.collect::<Result<Vec<_>, _>>()?
} else {
Ok(Self {
deps: vec![],
project,
env: HashMap::new(),
companions: vec![],
programs: vec![],
})
}
vec![]
};

let env = if let Some(runtime) = entry.runtime {
runtime.env
} else {
HashMap::new()
};

Ok(Self {
deps,
project,
env,
companions,
programs,
})
}
}

pub struct PackageEntryIterator {
stack: Vec<PathBuf>, // stack for directories to visit
pantry_dir: PathBuf,
}

impl PackageEntryIterator {
pub fn new(pantry_dir: PathBuf) -> Self {
Self {
stack: vec![pantry_dir.clone()],
pantry_dir,
}
}
}
Expand All @@ -93,8 +122,10 @@ impl Iterator for PackageEntryIterator {
}
}
} else if path.file_name() == Some("package.yml".as_ref()) {
if let Ok(entry) = PantryEntry::from_path(&path) {
if let Ok(entry) = PantryEntry::from_path(&path, &self.pantry_dir) {
return Some(entry);
} else if cfg!(debug_assertions) {
eprintln!("parse failure: {:?}", path);
}
}
}
Expand All @@ -103,12 +134,105 @@ impl Iterator for PackageEntryIterator {
}

pub fn ls(config: &Config) -> PackageEntryIterator {
PackageEntryIterator::new(config.pantry_dir.clone())
PackageEntryIterator::new(config.pantry_dir.join("projects"))
}

#[derive(Debug, Deserialize)]
struct RawPantryEntry {
dependencies: Option<HashMap<String, String>>,
dependencies: Option<Deps>,
provides: Option<Provides>,
companions: Option<Deps>,
runtime: Option<Runtime>,
}

#[derive(Debug, Deserialize)]
struct Runtime {
env: HashMap<String, String>,
}

#[derive(Debug)]
struct Deps(HashMap<String, String>);

impl<'de> Deserialize<'de> for Deps {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
// Deserialize the map as a generic HashMap
let full_map: HashMap<String, serde_yaml::Value> = Deserialize::deserialize(deserializer)?;

// Determine the current platform
#[cfg(target_os = "macos")]
let platform_key = "darwin";

#[cfg(target_os = "linux")]
let platform_key = "linux";

#[cfg(target_os = "windows")]
let platform_key = "windows";

// Create the result map
let mut result = HashMap::new();

for (key, value) in full_map {
if key == "linux" || key == "darwin" || key == "windows" {
// If the key is platform-specific, only include values for the current platform
if key == platform_key {
if let serde_yaml::Value::Mapping(platform_values) = value {
for (k, v) in platform_values {
if let (serde_yaml::Value::String(k), serde_yaml::Value::String(v)) =
(k, v)
{
result.insert(k, v);
}
}
}
}
} else {
// Include non-platform-specific keys
if let serde_yaml::Value::String(v) = value {
result.insert(key, v);
}
}
}

Ok(Deps(result))
}
}

#[derive(Debug)]
struct Provides(Vec<String>);

impl<'de> Deserialize<'de> for Provides {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
// Define an enum to capture the possible YAML structures
#[derive(Deserialize)]
#[serde(untagged)]
enum ProvidesHelper {
List(Vec<String>),
Map(HashMap<String, Vec<String>>),
}

match ProvidesHelper::deserialize(deserializer)? {
ProvidesHelper::List(list) => Ok(Provides(list)),
ProvidesHelper::Map(map) => {
#[cfg(target_os = "macos")]
let key = "darwin";

#[cfg(target_os = "linux")]
let key = "linux";

if let Some(values) = map.get(key) {
Ok(Provides(values.clone()))
} else {
Ok(Provides(Vec::new())) // Return an empty Vec if the key isn't found
}
}
}
}
}

impl RawPantryEntry {
Expand Down
Loading
Loading