use crate::{
Result, Rudof,
errors::{DataError, IriError},
formats::{IriNormalizationMode, QueryType},
};
#[cfg(not(target_family = "wasm"))]
use crossterm::terminal;
use prefixmap::IriRef;
use rudof_iri::IriS;
use rudof_rdf::rdf_core::{NeighsRDF, query::SparqlQuery};
use shex_ast::{ShapeMapParser, shapemap::NodeSelector};
#[cfg(not(target_family = "wasm"))]
use std::env;
use std::str::FromStr;
#[cfg(not(target_family = "wasm"))]
use url::Url;
pub(crate) fn normalize_iri_str(s: &str, mode: IriNormalizationMode) -> String {
let trimmed = s.trim();
match mode {
IriNormalizationMode::Strict => trimmed.to_string(),
IriNormalizationMode::Lax => {
let is_bare_iri = !trimmed.starts_with('<')
&& !trimmed.starts_with('_')
&& !trimmed.starts_with('{')
&& trimmed.contains("://");
if is_bare_iri {
format!("<{}>", trimmed)
} else {
trimmed.to_string()
}
},
}
}
pub fn get_base_iri(rudof: &mut Rudof, base_iri: Option<&str>) -> Result<IriS> {
if let Some(base_iri) = base_iri {
let base_iri = IriS::from_str(base_iri).map_err(|error| IriError::ParseError {
iri: base_iri.to_string(),
error: error.to_string(),
})?;
Ok(base_iri.clone())
} else if let Some(base_iri) = rudof.config.shex().base() {
Ok(base_iri.clone())
} else {
#[cfg(target_family = "wasm")]
return Err(IriError::WasmNotSupported {
operation: "defaulting the base IRI to the current directory (provide a base IRI)".to_string(),
}
.into());
#[cfg(not(target_family = "wasm"))]
{
let cwd = env::current_dir().map_err(|e| IriError::PathConversionError {
path: ".".to_string(),
error: format!("Error resolving base IRI. Failed to get current directory: {e}"),
})?;
let url = Url::from_directory_path(&cwd).map_err(|_| IriError::PathConversionError {
path: cwd.to_string_lossy().to_string(),
error: "Error resolving base IRI. Cannot convert current directory to a file URL".to_string(),
})?;
Ok(url.into())
}
}
}
pub fn terminal_colors() -> bool {
!cfg!(target_family = "wasm")
}
#[cfg(not(target_family = "wasm"))]
const MAX_TERMINAL_WIDTH: usize = 100;
const DEFAULT_TERMINAL_WIDTH: usize = 80;
#[cfg(target_family = "wasm")]
pub fn terminal_width() -> usize {
DEFAULT_TERMINAL_WIDTH
}
#[cfg(not(target_family = "wasm"))]
pub fn terminal_width() -> usize {
if let Ok((cols, _)) = terminal::size() {
sanitize_width(cols as usize)
} else {
DEFAULT_TERMINAL_WIDTH
}
}
#[cfg(not(target_family = "wasm"))]
fn sanitize_width(width: usize) -> usize {
match width {
w if w > MAX_TERMINAL_WIDTH => MAX_TERMINAL_WIDTH,
w if w < 40 => DEFAULT_TERMINAL_WIDTH,
w => w,
}
}
pub fn detect_query_type(query: &SparqlQuery) -> QueryType {
if query.is_select() {
QueryType::Select
} else if query.is_construct() {
QueryType::Construct
} else if query.is_ask() {
QueryType::Ask
} else {
QueryType::Describe
}
}
pub fn parse_node_selector(node: &str, iri_mode: IriNormalizationMode) -> Result<NodeSelector> {
let normalized = normalize_iri_str(node, iri_mode);
ShapeMapParser::parse_node_selector(normalized.as_str()).map_err(|e| {
Box::new(DataError::FailedNodeSelectorParse {
node: normalized.as_str().to_string(),
error: e.to_string(),
})
.into()
})
}
pub fn convert_predicate_strings_to_iris<S>(predicates: &[String], rdf: &S) -> Result<Vec<S::IRI>>
where
S: NeighsRDF,
{
predicates
.iter()
.map(|pred_str| {
let iri_ref = parse_iri_ref(pred_str)?;
let iri = match iri_ref {
IriRef::Prefixed { prefix, local } => {
rdf.resolve_prefix_local(prefix.as_str(), local.as_str()).map_err(|e| {
Box::new(DataError::FailedPrefixResolution {
prefix: prefix.to_string(),
error: e.to_string(),
})
})?
},
IriRef::Iri(iri) => iri,
};
Ok(iri.into())
})
.collect()
}
fn parse_iri_ref(iri: &str) -> Result<IriRef> {
ShapeMapParser::parse_iri_ref(iri).map_err(|e| {
Box::new(DataError::FailedIriRefParse {
iri: iri.to_string(),
error: e.to_string(),
})
.into()
})
}