use std::fmt;
use std::ops::Deref;
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use camino::Utf8Path;
use tokio::sync::OnceCell;
use crate::core::config::Config;
use crate::core::manifest::{ManifestDependency, Summary};
use crate::core::package::{Package, PackageId};
use crate::core::source::{Source, SourceId};
use crate::ops;
use crate::MANIFEST_FILE_NAME;
pub struct PathSource<'c> {
source_id: SourceId,
config: &'c Config,
packages: PackagesCell,
}
impl<'c> PathSource<'c> {
pub fn new(source_id: SourceId, config: &'c Config) -> Self {
let root = source_id
.to_path()
.expect("path sources cannot be remote")
.join(MANIFEST_FILE_NAME);
Self {
source_id,
config,
packages: PackagesCell::new(move |source_id, config| {
Self::fetch_workspace_at_root(&root, source_id, config)
}),
}
}
pub fn preloaded(packages: &[Package], config: &'c Config) -> Self {
assert!(
!packages.is_empty(),
"PathSource must be preloaded with non-empty package set"
);
for wnd in packages.windows(2) {
let source_a = wnd[0].id.source_id;
let source_b = wnd[1].id.source_id;
assert_eq!(
source_a, source_b,
"PathSource must be preloaded with packages from the same source"
);
}
let source_id = packages[0].id.source_id;
Self {
source_id,
config,
packages: PackagesCell::preloaded(packages.to_vec()),
}
}
pub fn recursive_at(path: &Utf8Path, source_id: SourceId, config: &'c Config) -> Self {
Self {
source_id,
config,
packages: PackagesCell::new({
let path = path.to_path_buf();
move |source_id, config| Self::find_packages_recursive(&path, source_id, config)
}),
}
}
async fn packages(&self) -> Result<&[Package]> {
self.packages.try_get(self.source_id, self.config).await
}
fn fetch_workspace_at_root(
root: &Utf8Path,
source_id: SourceId,
config: &Config,
) -> Result<Vec<Package>> {
let ws = ops::read_workspace_with_source_id(root, source_id, config)?;
Ok(ws.members().collect())
}
fn find_packages_recursive(
root: &Utf8Path,
source_id: SourceId,
config: &Config,
) -> Result<Vec<Package>> {
ops::find_all_packages_recursive_with_source_id(root, source_id, config)
}
}
#[async_trait]
impl<'c> Source for PathSource<'c> {
#[tracing::instrument(level = "trace", skip(self))]
async fn query(&self, dependency: &ManifestDependency) -> Result<Vec<Summary>> {
Ok(self
.packages()
.await?
.iter()
.map(|pkg| pkg.manifest.summary.clone())
.filter(|summary| dependency.matches_summary(summary))
.collect())
}
#[tracing::instrument(level = "trace", skip(self))]
async fn download(&self, id: PackageId) -> Result<Package> {
self.packages()
.await?
.iter()
.find(|pkg| pkg.id == id)
.cloned()
.ok_or_else(|| anyhow!("failed to find {id} in path source {}", self.source_id))
}
}
impl<'c> fmt::Debug for PathSource<'c> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PathSource")
.field("source", &self.source_id.to_string())
.finish_non_exhaustive()
}
}
type PackagesScanner = dyn Fn(SourceId, &Config) -> Result<Vec<Package>> + Send + Sync;
struct PackagesCell {
cell: OnceCell<Vec<Package>>,
scanner: Option<Box<PackagesScanner>>,
}
impl PackagesCell {
fn new(
scanner: impl Fn(SourceId, &Config) -> Result<Vec<Package>> + Send + Sync + 'static,
) -> Self {
Self {
cell: OnceCell::new(),
scanner: Some(Box::new(scanner)),
}
}
fn preloaded(packages: Vec<Package>) -> Self {
Self {
cell: OnceCell::from(packages),
scanner: None,
}
}
async fn try_get(&self, source_id: SourceId, config: &Config) -> Result<&[Package]> {
self.cell
.get_or_try_init(|| async {
let f = self.scanner.as_ref().unwrap().deref();
f(source_id, config)
})
.await
.map(|v| v.as_slice())
}
}