#![feature(trivial_bounds)]
use std::path::PathBuf;
pub use pathdiff::diff_paths;
pub mod bytes_str;
pub mod hash;
pub use nanoid;
pub const PARSE_QUERY_TRUE: &str = "";
pub const FARM_IGNORE_ACTION_COMMENT: &str = "$farm-ignore";
pub const FARM_IGNORE_ACTION_COMMENTS: [&str; 2] = [FARM_IGNORE_ACTION_COMMENT, "$vite-ignore"];
pub fn is_skip_action_by_comment(comment: &str) -> bool {
if comment.is_empty() || !comment.contains('$') {
return false;
}
for action in FARM_IGNORE_ACTION_COMMENTS {
if comment.contains(action) {
return true;
}
}
false
}
pub fn parse_query(path: &str) -> Vec<(String, String)> {
if !path.contains('?') {
return vec![];
}
let query_str = path.split('?').last().unwrap();
let pairs = query_str.split('&');
let mut query = vec![];
for pair in pairs {
if pair.contains('=') {
let kv = pair.split('=').collect::<Vec<_>>();
if kv.len() != 2 {
panic!("Invalid query: {pair}");
}
query.push((kv[0], kv[1]));
} else {
query.push((pair, PARSE_QUERY_TRUE))
}
}
query
.into_iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect()
}
pub fn stringify_query(query: &Vec<(String, String)>) -> String {
if query.is_empty() {
return String::new();
}
let mut qs = vec![];
for (k, v) in query {
if v == PARSE_QUERY_TRUE || v.is_empty() {
qs.push(k.to_string());
} else {
qs.push(format!("{k}={v}"));
}
}
format!("?{}", qs.join("&"))
}
pub fn file_url_to_path(url: &str) -> String {
let url = url.replace("file://", "");
if cfg!(windows) {
if let Some(url) = url.strip_prefix('/') {
url.replace('/', "\\")
} else {
url.replace("/", "\\")
}
} else {
url
}
}
pub fn relative(from: &str, to: &str) -> String {
let from = file_url_to_path(from);
let to = file_url_to_path(to);
if !PathBuf::from(&from).is_absolute() {
panic!("from path must be absolute. from: {from} to: {to}");
}
let rp = diff_paths(&to, &from).unwrap_or_else(|| {
if !PathBuf::from(&to).is_absolute() {
return PathBuf::from(&to);
}
panic!("failed to get relative path from {from} to {to}");
});
let mut result = String::new();
for comp in rp.components() {
match comp {
std::path::Component::Prefix(_)
| std::path::Component::RootDir
| std::path::Component::CurDir => {
if result.is_empty() {
result += ".";
} else {
unreachable!("Invalid relative: {from} -> {to}");
}
}
std::path::Component::ParentDir => {
if result.is_empty() {
result += "..";
} else {
result += "/.."
}
}
std::path::Component::Normal(c) => {
let c = c.to_string_lossy().to_string();
if result.is_empty() {
result += &c;
} else {
result = format!("{result}/{c}");
}
}
}
}
result
}
pub fn transform_string_to_static_str(s: String) -> &'static str {
Box::leak(s.into_boxed_str())
}
#[cfg(test)]
mod tests {
use crate::{relative, stringify_query, PARSE_QUERY_TRUE};
use super::parse_query;
#[test]
fn parse_query_t() {
let str = "./a.png?inline&b=c";
let parsed_query = parse_query(str);
assert_eq!(
parsed_query,
vec![
("inline".to_string(), "".to_string()),
("b".to_string(), "c".to_string())
]
);
let str = "?a=b";
let parsed_query = parse_query(str);
assert_eq!(parsed_query, vec![("a".to_string(), "b".to_string())]);
let str = "./a";
let parsed_query = parse_query(str);
assert_eq!(parsed_query, vec![]);
}
#[test]
fn stringify_query_t() {
let query = vec![("inline".to_string(), PARSE_QUERY_TRUE.to_string())];
let str = stringify_query(&query);
assert_eq!(str, "?inline");
let query = vec![("a".to_string(), "b".to_string())];
let str = stringify_query(&query);
assert_eq!(str, "?a=b".to_string());
}
#[test]
fn test_relative() {
let from = if cfg!(windows) {
"C:\\desktop\\farm\\projects"
} else {
"/desktop/farm/projects"
};
let to = if cfg!(windows) {
"C:\\desktop\\farm\\documents\\report.txt"
} else {
"/desktop/farm/documents/report.txt"
};
assert_eq!(relative(from, to), "../documents/report.txt");
}
}