use std::path::{Path, PathBuf};
use std::sync::Arc;
use query_flow::asset_key;
use url::Url;
use super::error::EureQueryError;
#[asset_key(asset = TextFileContent)]
pub enum TextFile {
Local(Arc<PathBuf>),
Remote(Url),
}
impl TextFile {
pub fn from_path(path: PathBuf) -> Self {
Self::Local(Arc::new(path))
}
pub fn from_url(url: Url) -> Self {
Self::Remote(url)
}
pub fn parse(s: &str) -> Result<Self, EureQueryError> {
if s.starts_with("https://") {
Url::parse(s)
.map(Self::from_url)
.map_err(|e| EureQueryError::InvalidUrl {
url: s.to_string(),
reason: e.to_string(),
})
} else {
Ok(Self::from_path(PathBuf::from(s)))
}
}
pub fn resolve(target: &str, base_dir: &Path) -> Result<Self, EureQueryError> {
if target.starts_with("https://") {
Self::parse(target)
} else {
Ok(Self::from_path(base_dir.join(target)))
}
}
pub fn new(path: Arc<PathBuf>) -> Self {
Self::Local(path)
}
pub fn as_local_path(&self) -> Option<&Path> {
match self {
Self::Local(p) => Some(p),
Self::Remote(_) => None,
}
}
pub fn as_url(&self) -> Option<&Url> {
match self {
Self::Local(_) => None,
Self::Remote(url) => Some(url),
}
}
pub fn is_local(&self) -> bool {
matches!(self, Self::Local(_))
}
pub fn ends_with(&self, suffix: &str) -> bool {
match self {
Self::Local(path) => path
.file_name()
.is_some_and(|name| name.to_string_lossy().ends_with(suffix)),
Self::Remote(url) => url.path().ends_with(suffix),
}
}
}
impl std::fmt::Display for TextFile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Local(path) => write!(f, "{}", path.display()),
Self::Remote(url) => write!(f, "{}", url),
}
}
}
#[derive(Clone, PartialEq, Debug)]
pub struct TextFileContent(pub String);
impl TextFileContent {
pub fn get(&self) -> &str {
&self.0
}
}
#[asset_key(asset = Workspace)]
pub struct WorkspaceId(pub String);
#[derive(Clone, PartialEq)]
pub struct Workspace {
pub path: PathBuf,
pub config_path: PathBuf,
}
#[asset_key(asset = GlobResult)]
pub struct Glob {
pub base_dir: PathBuf,
pub pattern: String,
}
impl Glob {
pub fn new(base_dir: impl Into<PathBuf>, pattern: impl Into<String>) -> Self {
Self {
base_dir: base_dir.into(),
pattern: pattern.into(),
}
}
pub fn full_pattern(&self) -> PathBuf {
self.base_dir.join(&self.pattern)
}
}
#[derive(Clone, PartialEq, Debug)]
pub struct GlobResult(pub Vec<TextFile>);
#[asset_key(asset = OpenDocumentsList)]
pub struct OpenDocuments;
#[derive(Clone, PartialEq, Debug)]
pub struct OpenDocumentsList(pub Vec<TextFile>);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum DecorStyle {
#[default]
Unicode,
Ascii,
}
#[asset_key(asset = DecorStyle)]
pub struct DecorStyleKey;
#[cfg(test)]
mod tests {
use super::*;
mod text_file_parse {
use super::*;
#[test]
fn parses_https_url() {
let file = TextFile::parse("https://example.com/schema.eure").unwrap();
assert!(file.as_url().is_some());
assert!(file.as_local_path().is_none());
assert_eq!(
file.as_url().unwrap().as_str(),
"https://example.com/schema.eure"
);
}
#[test]
fn parses_local_path() {
let file = TextFile::parse("/path/to/file.eure").unwrap();
assert!(file.as_local_path().is_some());
assert!(file.as_url().is_none());
assert_eq!(
file.as_local_path().unwrap(),
Path::new("/path/to/file.eure")
);
}
#[test]
fn parses_relative_path() {
let file = TextFile::parse("relative/path.eure").unwrap();
assert!(file.as_local_path().is_some());
assert_eq!(
file.as_local_path().unwrap(),
Path::new("relative/path.eure")
);
}
#[test]
fn http_without_s_is_local_path() {
let file = TextFile::parse("http://example.com").unwrap();
assert!(file.as_local_path().is_some());
}
#[test]
fn invalid_url_returns_error() {
let result = TextFile::parse("https://");
assert!(result.is_err());
let result = TextFile::parse("https://[invalid");
assert!(result.is_err());
}
}
mod text_file_ends_with {
use super::*;
#[test]
fn local_file_ends_with_extension() {
let file = TextFile::from_path(PathBuf::from("/path/to/file.schema.eure"));
assert!(file.ends_with(".schema.eure"));
assert!(file.ends_with(".eure"));
assert!(!file.ends_with(".json"));
}
#[test]
fn local_file_ends_with_filename() {
let file = TextFile::from_path(PathBuf::from("/path/to/config.eure"));
assert!(file.ends_with("config.eure"));
assert!(!file.ends_with("other.eure"));
}
#[test]
fn remote_url_ends_with_extension() {
let file = TextFile::parse("https://example.com/schemas/user.schema.eure").unwrap();
assert!(file.ends_with(".schema.eure"));
assert!(file.ends_with(".eure"));
assert!(!file.ends_with(".json"));
}
#[test]
fn remote_url_ignores_query_params() {
let file = TextFile::parse("https://example.com/file.eure?version=1").unwrap();
assert!(file.ends_with(".eure"));
assert!(!file.ends_with("?version=1"));
}
#[test]
fn remote_url_ignores_fragment() {
let file = TextFile::parse("https://example.com/file.eure#section").unwrap();
assert!(file.ends_with(".eure"));
assert!(!file.ends_with("#section"));
}
}
}