use std::env; use std::path::Path; use super::pattern::r#match; use super::r#macro::{MacroSet, MacroSource}; use super::token::TokenString; pub(crate) struct FunctionCall {} fn filter(macros: &MacroSet, patterns: &TokenString, text: &TokenString) -> String { let patterns = macros.expand(patterns); let patterns = patterns.split_whitespace().collect::>(); let text = macros.expand(&text); let text = text.split_whitespace(); let mut result_pieces = vec![]; for word in text { if patterns .iter() .any(|pattern| r#match(pattern, word).is_some()) { result_pieces.push(word); } } result_pieces.join(" ") } pub(crate) fn call(name: &str, args: &[TokenString], macros: &MacroSet) -> String { match name { // Text Functions "filter" => { assert_eq!(args.len(), 2); filter(macros, &args[0], &args[1]) } "filter-out" => { assert_eq!(args.len(), 2); let patterns = macros.expand(&args[0]); let patterns = patterns.split_whitespace().collect::>(); let text = macros.expand(&args[1]); let text = text.split_whitespace(); let mut result_pieces = vec![]; for word in text { if patterns .iter() .all(|pattern| r#match(pattern, word).is_none()) { result_pieces.push(word); } } result_pieces.join(" ") } "sort" => { assert_eq!(args.len(), 1); let words = macros.expand(&args[0]); let mut words = words.split_whitespace().collect::>(); words.sort(); words.dedup(); words.join(" ") } // File Name Functions "notdir" => { assert_eq!(args.len(), 1); let words = macros.expand(&args[0]); let words = words .split_whitespace() .map(|word| { Path::new(word) .file_name() .and_then(|filename| filename.to_str()) .unwrap_or("") }) .collect::>(); words.join(" ") } "basename" => { assert_eq!(args.len(), 1); let words = macros.expand(&args[0]); let words = words .split_whitespace() .map(|word| { Path::new(word) .with_extension("") .to_str() .map_or_else(String::new, ToString::to_string) }) .collect::>(); words.join(" ") } "addprefix" => { assert_eq!(args.len(), 2); let prefix = macros.expand(&args[0]); let targets = macros.expand(&args[1]); let results = targets .split_whitespace() .map(|t| format!("{}{}", prefix, t)) .collect::>(); results.join(" ") } "wildcard" => { assert_eq!(args.len(), 1); let pattern = macros.expand(&args[0]); let home_dir = env::var("HOME") .ok() .or(dirs::home_dir().and_then(|p| p.to_str().map(String::from))); let pattern = if let Some(home_dir) = home_dir { pattern.replace('~', &home_dir) } else { pattern }; let results = glob::glob(&pattern) .expect("invalid glob pattern!") .filter_map(|path| path.ok()) .map(|path| path.to_str().map(ToString::to_string).unwrap_or_default()) .collect::>(); results.join(" ") } // foreach "foreach" => { assert_eq!(args.len(), 3); let var = macros.expand(&args[0]); let list = macros.expand(&args[1]); let text = &args[2]; let words = list.split_whitespace(); let mut macros = macros.clone(); let results = words .map(|word| { macros.set(var.clone(), MacroSource::File, TokenString::text(word)); macros.expand(text) }) .collect::>(); results.join(" ") } // call "call" => { assert!(args.len() > 0); let args = args .iter() .map(|arg| macros.expand(arg)) .collect::>(); let function = args[0].clone(); let mut macros = macros.clone(); for (i, x) in args.into_iter().enumerate() { macros.set(i.to_string(), MacroSource::File, TokenString::text(x)); } macros.expand(&TokenString::r#macro(function)) } // eval "eval" => { assert_eq!(args.len(), 1); let arg = macros.expand(&args[0]); todo!() } // shell "shell" => todo!(), // fallback _ => panic!("function not implemented: {}", name), } } #[cfg(test)] mod test { use super::*; fn call(name: &str, args: &[TokenString], macros: &MacroSet) -> String { super::call(name, args, macros, lookup_fail) } macro_rules! call { ($func:literal $($arg:literal),+) => { call($func, &[$(TokenString::text($arg)),+], &MacroSet::new()) }; ($func:ident $($arg:literal),+) => { call(stringify!($func), &[$(TokenString::text($arg)),+], &MacroSet::new()) }; } #[test] fn filter() { let result = call!(filter "word", "this contains a word inside it"); assert_eq!(result, "word"); let result = call!(filter "%.c %.s", "foo.c bar.c baz.s ugh.h"); assert_eq!(result, "foo.c bar.c baz.s"); } #[test] fn filter_out() { let result = call!("filter-out" "main1.o main2.o", "main1.o foo.o main2.o bar.o"); assert_eq!(result, "foo.o bar.o"); } #[test] fn sort() { let result = call!(sort "foo bar lose foo"); assert_eq!(result, "bar foo lose"); } #[test] fn notdir() { let result = call!(notdir "src/foo.c hacks"); assert_eq!(result, "foo.c hacks"); } #[test] fn basename() { let result = call!(basename "src/foo.c src-1.0/bar hacks"); assert_eq!(result, "src/foo src-1.0/bar hacks"); } #[test] fn addprefix() { let result = call!(addprefix "src/", "foo bar"); assert_eq!(result, "src/foo src/bar"); } #[test] fn wildcard() { use std::env::{set_current_dir, set_var}; use std::fs::write; use std::path::MAIN_SEPARATOR; let tempdir = tempfile::tempdir().unwrap(); write(tempdir.path().join("foo.c"), "").unwrap(); write(tempdir.path().join("bar.h"), "").unwrap(); write(tempdir.path().join("baz.txt"), "").unwrap(); write(tempdir.path().join("acab.c"), "ACAB").unwrap(); write(tempdir.path().join("based.txt"), "☭").unwrap(); set_current_dir(tempdir.path()).unwrap(); set_var("HOME", tempdir.path().to_str().unwrap()); let sort = |x: String| call("sort", &[TokenString::text(&x)], &MacroSet::new()); assert_eq!(sort(call!(wildcard "*.c")), "acab.c foo.c"); assert_eq!( sort(call!(wildcard "~/ba?.*")), format!( "{0}{1}bar.h {0}{1}baz.txt", tempdir.path().to_str().unwrap(), MAIN_SEPARATOR ) ); } #[test] fn foreach() { let mut macros = MacroSet::new(); macros.set( "test".to_string(), MacroSource::File, "worked for $(item).".parse().unwrap(), ); assert_eq!( call( "foreach", &[ TokenString::text("item"), TokenString::text("a b c d"), TokenString::r#macro("test") ], ¯os, ), "worked for a. worked for b. worked for c. worked for d." ); } #[test] fn call_test() { let mut macros = MacroSet::new(); macros.set( "reverse".to_string(), MacroSource::File, "$(2) $(1)".parse().unwrap(), ); assert_eq!( call( "call", &[ TokenString::text("reverse"), TokenString::text("a"), TokenString::text("b") ], ¯os, ), "b a" ); } }