use std::path::Path;
use std::sync::Arc;
use super::condition::is_test_source;
use super::discovery::GoPackageDirectory;
use super::paths::file_name;
use super::snapshot_error::GoSnapshotError;
use super::store::GoSourceStore;
use super::unit::GoPackageContext;
const EXTERNAL_SUFFIX: &str = "_test";
pub(super) struct PackageSite<'a> {
pub(super) root: &'a Path,
pub(super) module: u32,
pub(super) module_path: &'a Arc<str>,
}
pub(super) struct UnitDraft {
pub(super) module: u32,
pub(super) context: GoPackageContext,
pub(super) import_path: Arc<str>,
pub(super) package_name: Box<str>,
pub(super) directory: Arc<str>,
pub(super) sources: Box<[Arc<str>]>,
}
struct ClassifiedSource {
path: Arc<str>,
package: Box<str>,
test: bool,
}
pub(super) fn directory_units(
store: &mut GoSourceStore,
site: &PackageSite<'_>,
directory: &GoPackageDirectory,
) -> Result<Box<[UnitDraft]>, GoSnapshotError> {
let relative = super::paths::relative_shared(site.root, &directory.canonical)?;
let classified = classify(store, directory)?;
let package = declared_package(&classified, &relative)?;
let contexts = assign_contexts(&classified, &package, &relative)?;
let import_path = import_path(site.module_path, &directory.within_module);
Ok(drafts(
site,
&Placement {
import_path,
package,
directory: relative,
},
(&classified, &contexts),
))
}
struct Placement {
import_path: Arc<str>,
package: Box<str>,
directory: Arc<str>,
}
fn classify(
store: &mut GoSourceStore,
directory: &GoPackageDirectory,
) -> Result<Box<[ClassifiedSource]>, GoSnapshotError> {
let mut classified = Vec::new();
for source in directory.sources.iter() {
let path = store.intern(source)?;
let package =
store
.package_name(&path)
.ok_or_else(|| GoSnapshotError::MissingStoredSource {
path: Box::from(&*path),
})?;
classified.push(ClassifiedSource {
test: is_test_source(file_name(&path)),
package: Box::from(package),
path,
});
}
Ok(classified.into_boxed_slice())
}
fn declared_package(
classified: &[ClassifiedSource],
directory: &str,
) -> Result<Box<str>, GoSnapshotError> {
let production = classified
.iter()
.find(|source| !source.test)
.map(|source| Box::from(&*source.package));
production
.or_else(|| internal_package(classified))
.or_else(|| suffixless_package(classified))
.ok_or_else(|| GoSnapshotError::MissingPackageClause {
path: Box::from(directory),
})
}
fn internal_package(classified: &[ClassifiedSource]) -> Option<Box<str>> {
classified
.iter()
.find(|source| source.test && !source.package.ends_with(EXTERNAL_SUFFIX))
.map(|source| Box::from(&*source.package))
}
fn suffixless_package(classified: &[ClassifiedSource]) -> Option<Box<str>> {
classified
.iter()
.filter(|source| source.test)
.find_map(|source| source.package.strip_suffix(EXTERNAL_SUFFIX).map(Box::from))
}
fn assign_contexts(
classified: &[ClassifiedSource],
package: &str,
directory: &str,
) -> Result<Box<[GoPackageContext]>, GoSnapshotError> {
let mut contexts = Vec::with_capacity(classified.len());
for source in classified {
let context = context_of(source, package).ok_or_else(|| {
GoSnapshotError::ConflictingPackageClause {
directory: Box::from(directory),
first: Box::from(package),
second: Box::from(&*source.package),
}
})?;
contexts.push(context);
}
Ok(contexts.into_boxed_slice())
}
fn context_of(source: &ClassifiedSource, package: &str) -> Option<GoPackageContext> {
match (source.test, &*source.package == package) {
(false, true) => Some(GoPackageContext::Production),
(true, true) => Some(GoPackageContext::InternalTest),
(true, false) => source
.package
.strip_suffix(EXTERNAL_SUFFIX)
.filter(|base| *base == package)
.map(|_| GoPackageContext::ExternalTest),
(false, false) => None,
}
}
fn drafts(
site: &PackageSite<'_>,
placement: &Placement,
assigned: (&[ClassifiedSource], &[GoPackageContext]),
) -> Box<[UnitDraft]> {
let (_, contexts) = assigned;
[
GoPackageContext::Production,
GoPackageContext::InternalTest,
GoPackageContext::ExternalTest,
]
.into_iter()
.filter(|context| contexts.contains(context))
.map(|context| {
let sources = selected(assigned, compiled_by(context));
draft(site, placement, (context, sources))
})
.collect()
}
fn compiled_by(context: GoPackageContext) -> &'static [GoPackageContext] {
match context {
GoPackageContext::Production => &[GoPackageContext::Production],
GoPackageContext::InternalTest => {
&[GoPackageContext::Production, GoPackageContext::InternalTest]
}
GoPackageContext::ExternalTest => &[GoPackageContext::ExternalTest],
}
}
fn draft(
site: &PackageSite<'_>,
placement: &Placement,
unit: (GoPackageContext, Box<[Arc<str>]>),
) -> UnitDraft {
let (context, sources) = unit;
UnitDraft {
module: site.module,
context,
import_path: Arc::clone(&placement.import_path),
package_name: external_package(&placement.package, context),
directory: Arc::clone(&placement.directory),
sources,
}
}
fn external_package(package: &str, context: GoPackageContext) -> Box<str> {
match context {
GoPackageContext::ExternalTest => format!("{package}{EXTERNAL_SUFFIX}").into_boxed_str(),
GoPackageContext::Production | GoPackageContext::InternalTest => Box::from(package),
}
}
fn selected(
assigned: (&[ClassifiedSource], &[GoPackageContext]),
wanted: &[GoPackageContext],
) -> Box<[Arc<str>]> {
let (classified, contexts) = assigned;
let mut sources: Vec<Arc<str>> = classified
.iter()
.zip(contexts)
.filter(|(_, context)| wanted.contains(context))
.map(|(source, _)| Arc::clone(&source.path))
.collect();
sources.sort();
sources.into_boxed_slice()
}
fn import_path(module: &Arc<str>, within: &str) -> Arc<str> {
match within.is_empty() {
true => Arc::clone(module),
false => Arc::from(format!("{module}/{within}")),
}
}