use std::ffi::OsStr;
use std::fmt;
use std::fs;
use std::io;
use std::iter;
use std::path::{Path, PathBuf};
use anyhow::Result;
use relative_path::{RelativePath, RelativePathBuf};
use semver::Version;
use serde::de::IntoDeserializer;
use serde::Deserialize;
use serde_hashkey as key;
use crate as rune;
use crate::alloc::prelude::*;
use crate::alloc::{self, String, Vec};
use crate::ast::{Span, Spanned};
use crate::workspace::spanned_value::{Array, SpannedValue, Table, Value};
use crate::workspace::{
glob, Diagnostics, SourceLoader, WorkspaceError, WorkspaceErrorKind, MANIFEST_FILE,
};
use crate::{SourceId, Sources};
const BIN: &str = "bin";
const TESTS: &str = "tests";
const EXAMPLES: &str = "examples";
const BENCHES: &str = "benches";
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub enum WorkspaceFilter<'a> {
Name(&'a str),
All,
}
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub enum FoundKind {
Binary,
Test,
Example,
Bench,
}
impl fmt::Display for FoundKind {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FoundKind::Binary => "bin".fmt(f),
FoundKind::Test => "test".fmt(f),
FoundKind::Example => "example".fmt(f),
FoundKind::Bench => "bench".fmt(f),
}
}
}
#[derive(Debug, TryClone)]
#[non_exhaustive]
pub struct Found {
#[try_clone(copy)]
pub kind: FoundKind,
pub path: PathBuf,
pub name: String,
}
#[derive(Debug, TryClone)]
#[non_exhaustive]
pub struct FoundPackage<'a> {
pub found: Found,
pub package: &'a Package,
}
impl WorkspaceFilter<'_> {
fn matches(self, name: &str) -> bool {
match self {
WorkspaceFilter::Name(expected) => name == expected,
WorkspaceFilter::All => true,
}
}
}
impl<T> Spanned for toml::Spanned<T> {
#[inline]
fn span(&self) -> Span {
let range = toml::Spanned::span(self);
Span::new(range.start, range.end)
}
}
#[derive(Default, Debug)]
#[non_exhaustive]
pub struct Manifest {
pub packages: Vec<Package>,
}
impl Manifest {
fn find_paths<'m>(
&'m self,
m: WorkspaceFilter<'_>,
kind: FoundKind,
auto_path: &Path,
auto_find: fn(&Package) -> bool,
) -> Result<Vec<FoundPackage<'m>>> {
let mut output = Vec::new();
for package in self.packages.iter() {
for found in package.find_paths(m, kind, auto_path, auto_find)? {
output.try_push(FoundPackage { found, package })?;
}
}
Ok(output)
}
pub fn find_all(&self, m: WorkspaceFilter<'_>) -> Result<Vec<FoundPackage<'_>>> {
let mut output = Vec::new();
output.try_extend(self.find_bins(m)?)?;
output.try_extend(self.find_tests(m)?)?;
output.try_extend(self.find_examples(m)?)?;
output.try_extend(self.find_benches(m)?)?;
Ok(output)
}
pub fn find_bins(&self, m: WorkspaceFilter<'_>) -> Result<Vec<FoundPackage<'_>>> {
self.find_paths(m, FoundKind::Binary, Path::new(BIN), |p| p.auto_bins)
}
pub fn find_tests(&self, m: WorkspaceFilter<'_>) -> Result<Vec<FoundPackage<'_>>> {
self.find_paths(m, FoundKind::Test, Path::new(TESTS), |p| p.auto_tests)
}
pub fn find_examples(&self, m: WorkspaceFilter<'_>) -> Result<Vec<FoundPackage<'_>>> {
self.find_paths(m, FoundKind::Example, Path::new(EXAMPLES), |p| {
p.auto_examples
})
}
pub fn find_benches(&self, m: WorkspaceFilter<'_>) -> Result<Vec<FoundPackage<'_>>> {
self.find_paths(m, FoundKind::Bench, Path::new(BENCHES), |p| p.auto_benches)
}
}
#[derive(Debug)]
#[non_exhaustive]
pub struct Package {
pub name: String,
pub version: Version,
pub root: Option<PathBuf>,
pub auto_bins: bool,
pub auto_tests: bool,
pub auto_examples: bool,
pub auto_benches: bool,
}
impl Package {
fn find_paths(
&self,
m: WorkspaceFilter<'_>,
kind: FoundKind,
auto_path: &Path,
auto_find: fn(&Package) -> bool,
) -> Result<Vec<Found>> {
let mut output = Vec::new();
if let (Some(path), true) = (&self.root, auto_find(self)) {
let path = path.join(auto_path);
let results = find_rune_files(&path)?;
for result in results {
let (path, name) = result?;
if m.matches(&name) {
output.try_push(Found { kind, path, name })?;
}
}
}
Ok(output)
}
pub fn find_all(&self, m: WorkspaceFilter<'_>) -> Result<Vec<Found>> {
let mut output = Vec::new();
output.try_extend(self.find_bins(m)?)?;
output.try_extend(self.find_tests(m)?)?;
output.try_extend(self.find_examples(m)?)?;
output.try_extend(self.find_benches(m)?)?;
Ok(output)
}
pub fn find_bins(&self, m: WorkspaceFilter<'_>) -> Result<Vec<Found>> {
self.find_paths(m, FoundKind::Binary, Path::new(BIN), |p| p.auto_bins)
}
pub fn find_tests(&self, m: WorkspaceFilter<'_>) -> Result<Vec<Found>> {
self.find_paths(m, FoundKind::Test, Path::new(TESTS), |p| p.auto_tests)
}
pub fn find_examples(&self, m: WorkspaceFilter<'_>) -> Result<Vec<Found>> {
self.find_paths(m, FoundKind::Example, Path::new(EXAMPLES), |p| {
p.auto_examples
})
}
pub fn find_benches(&self, m: WorkspaceFilter<'_>) -> Result<Vec<Found>> {
self.find_paths(m, FoundKind::Bench, Path::new(BENCHES), |p| p.auto_benches)
}
}
pub(crate) struct Loader<'a> {
id: SourceId,
sources: &'a mut Sources,
diagnostics: &'a mut Diagnostics,
source_loader: &'a mut dyn SourceLoader,
manifest: &'a mut Manifest,
}
impl<'a> Loader<'a> {
pub(crate) fn new(
id: SourceId,
sources: &'a mut Sources,
diagnostics: &'a mut Diagnostics,
source_loader: &'a mut dyn SourceLoader,
manifest: &'a mut Manifest,
) -> Self {
Self {
id,
sources,
diagnostics,
source_loader,
manifest,
}
}
pub(crate) fn load_manifest(&mut self) -> Result<()> {
let Some(source) = self.sources.get(self.id) else {
self.fatal(WorkspaceError::new(
Span::empty(),
WorkspaceErrorKind::MissingSourceId { source_id: self.id },
))?;
return Ok(());
};
let value: SpannedValue = match toml::from_str(source.as_str()) {
Ok(value) => value,
Err(e) => {
let span = match e.span() {
Some(span) => Span::new(span.start, span.end),
None => Span::new(0, source.len()),
};
self.fatal(WorkspaceError::new(span, e))?;
return Ok(());
}
};
let root = source
.path()
.and_then(|p| p.parent().map(TryToOwned::try_to_owned))
.transpose()?;
let root = root.as_deref();
let Some((mut table, _)) = self.ensure_table(value)? else {
return Ok(());
};
if let Some((package, span)) = table
.remove("package")
.map(|value| self.ensure_table(value))
.transpose()?
.flatten()
{
if let Some(package) = self.load_package(package, span, root)? {
self.manifest.packages.try_push(package)?;
}
}
if let Some((mut table, span)) = table
.remove("workspace")
.map(|value| self.ensure_table(value))
.transpose()?
.flatten()
{
match &root {
Some(root) => {
if let Some(members) = self.load_members(&mut table, root)? {
for (span, path) in members {
self.load_member(span, &path)?;
}
}
}
None => {
self.fatal(WorkspaceError::new(
span,
WorkspaceErrorKind::MissingManifestPath,
))?;
}
}
self.ensure_empty(table)?;
}
self.ensure_empty(table)?;
Ok(())
}
fn load_members(
&mut self,
table: &mut Table,
root: &Path,
) -> Result<Option<Vec<(Span, PathBuf)>>> {
let Some(members) = table.remove("members") else {
return Ok(None);
};
let Some((members, _)) = self.ensure_array(members)? else {
return Ok(None);
};
let mut output = Vec::new();
for value in members {
let span = Spanned::span(&value);
match deserialize::<RelativePathBuf>(value) {
Ok(member) => {
self.glob_relative_path(&mut output, span, &member, root)?;
}
Err(error) => {
self.fatal(error)?;
}
};
}
Ok(Some(output))
}
fn glob_relative_path(
&mut self,
output: &mut Vec<(Span, PathBuf)>,
span: Span,
member: &RelativePath,
root: &Path,
) -> Result<()> {
let glob = glob::Glob::new(root, member)?;
for m in glob.matcher()? {
let Some(mut path) = self.glob_error(span, root, m)? else {
continue;
};
path.push(MANIFEST_FILE);
if !path.is_file() {
continue;
}
output.try_push((span, path))?;
}
Ok(())
}
fn glob_error<T>(
&mut self,
span: Span,
path: &Path,
result: Result<T, glob::GlobError>,
) -> alloc::Result<Option<T>> {
Ok(match result {
Ok(result) => Some(result),
Err(error) => {
self.fatal(WorkspaceError::new(
span,
WorkspaceErrorKind::GlobError {
path: path.try_into()?,
error,
},
))?;
None
}
})
}
fn load_member(&mut self, span: Span, path: &Path) -> Result<()> {
let source = match self.source_loader.load(span, path) {
Ok(source) => source,
Err(error) => {
self.fatal(error)?;
return Ok(());
}
};
let id = self.sources.insert(source)?;
let old = std::mem::replace(&mut self.id, id);
self.load_manifest()?;
self.id = old;
Ok(())
}
fn load_package(
&mut self,
mut table: Table,
span: Span,
root: Option<&Path>,
) -> alloc::Result<Option<Package>> {
let name = self.field(&mut table, span, "name")?;
let version = self.field(&mut table, span, "version")?;
self.ensure_empty(table)?;
let (Some(name), Some(version)) = (name, version) else {
return Ok(None);
};
Ok(Some(Package {
name,
version,
root: root.map(|p| p.into()),
auto_bins: true,
auto_tests: true,
auto_examples: true,
auto_benches: true,
}))
}
fn ensure_empty(&mut self, table: Table) -> alloc::Result<()> {
for (key, _) in table {
let span = Spanned::span(&key);
self.fatal(WorkspaceError::new(
span,
WorkspaceErrorKind::UnsupportedKey {
key: key.get_ref().as_str().try_into()?,
},
))?;
}
Ok(())
}
fn ensure_table(&mut self, value: SpannedValue) -> alloc::Result<Option<(Table, Span)>> {
let span = Spanned::span(&value);
Ok(match value.into_inner() {
Value::Table(table) => Some((table, span)),
_ => {
let error = WorkspaceError::new(span, WorkspaceErrorKind::ExpectedTable);
self.fatal(error)?;
None
}
})
}
fn ensure_array(&mut self, value: SpannedValue) -> alloc::Result<Option<(Array, Span)>> {
let span = Spanned::span(&value);
Ok(match value.into_inner() {
Value::Array(array) => Some((array, span)),
_ => {
let error = WorkspaceError::expected_array(span);
self.fatal(error)?;
None
}
})
}
fn field<T>(
&mut self,
table: &mut Table,
span: Span,
field: &'static str,
) -> alloc::Result<Option<T>>
where
T: for<'de> Deserialize<'de>,
{
Ok(match table.remove(field) {
Some(value) => match deserialize(value) {
Ok(value) => Some(value),
Err(error) => {
self.fatal(error)?;
None
}
},
None => {
let error = WorkspaceError::missing_field(span, field);
self.fatal(error)?;
None
}
})
}
fn fatal(&mut self, error: WorkspaceError) -> alloc::Result<()> {
self.diagnostics.fatal(self.id, error)
}
}
fn deserialize<T>(value: SpannedValue) -> Result<T, WorkspaceError>
where
T: for<'de> Deserialize<'de>,
{
let span = Spanned::span(&value);
let f = key::to_key(value.get_ref()).map_err(|e| WorkspaceError::new(span, e))?;
let deserializer = f.into_deserializer();
let value = T::deserialize(deserializer).map_err(|e| WorkspaceError::new(span, e))?;
Ok(value)
}
fn find_rune_files(path: &Path) -> Result<impl Iterator<Item = Result<(PathBuf, String)>>> {
let mut dir = match fs::read_dir(path) {
Ok(dir) => Some(dir),
Err(e) if e.kind() == io::ErrorKind::NotFound => None,
Err(e) => return Err(e.into()),
};
Ok(iter::from_fn(move || loop {
let e = dir.as_mut()?.next()?;
let e = match e {
Ok(e) => e,
Err(err) => return Some(Err(err.into())),
};
let m = match e.metadata() {
Ok(m) => m,
Err(err) => return Some(Err(err.into())),
};
if !m.is_file() {
continue;
}
let path = e.path();
let (Some(name), Some(ext)) = (path.file_stem().and_then(OsStr::to_str), path.extension())
else {
continue;
};
if ext != OsStr::new("rn") {
continue;
}
let name = match String::try_from(name) {
Ok(name) => name,
Err(error) => return Some(Err(error.into())),
};
return Some(Ok((path, name)));
}))
}