use std::path::{Path, PathBuf};
use arity_formatter::formatter::{DeclineReason, DescriptionFormatError};
use super::{FormatError, FormatStyle, format_description_with_style, format_with_options};
use crate::file_discovery::{DiscoveredFiles, is_description_file};
use crate::formatter::cache::CacheKey;
use crate::parser::ParseOptions;
use crate::project::description::MarkdownDefaultResolver;
pub fn merge(files: DiscoveredFiles) -> Vec<PathBuf> {
let mut all = files.r;
all.extend(files.description);
all.sort();
all
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Formatted {
Text(String),
Declined(DeclineReason),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FormatSourceError {
R(FormatError),
Description(DescriptionFormatError),
}
impl std::fmt::Display for FormatSourceError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::R(err) => write!(f, "{err}"),
Self::Description(err) => write!(f, "{err}"),
}
}
}
impl std::error::Error for FormatSourceError {}
pub fn format_file(
path: &Path,
content: &str,
style: FormatStyle,
markdown: &mut MarkdownDefaultResolver,
) -> Result<Formatted, FormatSourceError> {
if is_description_file(path) {
return match format_description_with_style(content, style) {
Ok(text) => Ok(Formatted::Text(text)),
Err(DescriptionFormatError::Declined(reason)) => Ok(Formatted::Declined(reason)),
Err(err) => Err(FormatSourceError::Description(err)),
};
}
let options = ParseOptions::default().with_roxygen_markdown_default(markdown.resolve(path));
format_with_options(content, style, &options)
.map(Formatted::Text)
.map_err(FormatSourceError::R)
}
pub fn cache_key<'a>(
path: &Path,
content: &'a str,
markdown: &mut MarkdownDefaultResolver,
) -> CacheKey<'a> {
if is_description_file(path) {
return CacheKey::dcf(content);
}
CacheKey::r(content, markdown.resolve(path))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_description_path_selects_the_dcf_grammar() {
let mut markdown = MarkdownDefaultResolver::new();
let formatted = format_file(
Path::new("pkg/DESCRIPTION"),
"Package: p\nImports: b, a\n",
FormatStyle::default(),
&mut markdown,
)
.expect("formats");
assert_eq!(
formatted,
Formatted::Text("Package: p\nImports:\n a,\n b\n".to_string())
);
}
#[test]
fn a_declined_description_is_not_a_failure() {
let mut markdown = MarkdownDefaultResolver::new();
let formatted = format_file(
Path::new("pkg/DESCRIPTION"),
"Package: p\n\nPackage: q\n",
FormatStyle::default(),
&mut markdown,
)
.expect("declines without erroring");
assert!(matches!(
formatted,
Formatted::Declined(DeclineReason::MultipleRecords { .. })
));
}
#[test]
fn a_malformed_description_is_a_failure() {
let mut markdown = MarkdownDefaultResolver::new();
let err = format_file(
Path::new("pkg/DESCRIPTION"),
"Package: p\ngarbage\n",
FormatStyle::default(),
&mut markdown,
)
.expect_err("errors");
assert!(matches!(err, FormatSourceError::Description(_)));
}
#[test]
fn merge_interleaves_both_grammars_by_path() {
let merged = merge(DiscoveredFiles {
r: vec![PathBuf::from("pkg/R/z.R"), PathBuf::from("pkg/R/a.R")],
description: vec![PathBuf::from("pkg/DESCRIPTION")],
});
assert_eq!(
merged,
vec![
PathBuf::from("pkg/DESCRIPTION"),
PathBuf::from("pkg/R/a.R"),
PathBuf::from("pkg/R/z.R"),
]
);
}
}