mod anthology; pub mod elements; pub mod read; pub mod recite; use elements::verse::Verse; /// Parse and run shell commands or `dwvsh` files /// /// A [Poem] is the structure that contains a full shell command/program. It /// may be composed of one or many [Verse]'s. pub type Poem = Vec; #[cfg(test)] mod tests { use super::elements::rune::Rune; use super::read::Readable; use super::*; use crate::compose::Environment; #[test] fn it_parses_a_verse_with_no_meter() { let poem = Poem::read("cargo build --release".to_string(), &mut Environment::new()); assert!(poem.is_ok()); let poem = poem.unwrap(); assert_eq!(poem.first().unwrap().verb(), "cargo"); } #[test] fn it_parses_a_verse_with_the_couplet_meter() { let poem = Poem::read("ls -la | lolcat".to_string(), &mut Environment::new()); assert!(poem.is_ok()); let poem = poem.unwrap(); assert_eq!(poem.first().unwrap().verb(), "ls"); assert_eq!(poem.first().unwrap().meter, Rune::Couplet); } #[test] fn it_parses_a_verse_with_the_quiet_meter() { let poem = Poem::read("sleep 20 &".to_string(), &mut Environment::new()); assert!(poem.is_ok()); let poem = poem.unwrap(); assert_eq!(poem.first().unwrap().verb(), "sleep"); assert_eq!(poem.first().unwrap().meter, Rune::Quiet); } #[test] fn it_parses_a_verse_with_the_and_meter() { let poem = Poem::read("sleep 2 && ls -la".to_string(), &mut Environment::new()); assert!(poem.is_ok()); let poem = poem.unwrap(); assert_eq!(poem.first().unwrap().verb(), "sleep"); assert_eq!(poem.first().unwrap().meter, Rune::And); } #[test] fn it_parses_a_verse_with_the_continue_meter() { let poem = Poem::read("sleep 2; ls -la".to_string(), &mut Environment::new()); assert!(poem.is_ok()); let poem = poem.unwrap(); assert_eq!(poem.first().unwrap().verb(), "sleep"); assert_eq!(poem.first().unwrap().meter, Rune::Continue); } #[test] fn it_parses_a_verse_with_the_read_rune() { let poem = Poem::read("lolcat < src/main.rs".to_string(), &mut Environment::new()); assert!(poem.is_ok()); let mut verses = poem.unwrap().into_iter(); let verse = verses.next().unwrap(); assert!(verse.io.contains(&Rune::Read)); assert_eq!( verse.stanza, vec!["lolcat"] .iter() .map(|s| s.to_string()) .collect::>() ); assert_eq!( verse.ip, vec!["src/main.rs"] .iter() .map(|s| s.to_string()) .collect::>() ); } #[test] fn it_parses_a_verse_with_the_write_rune() { let poem = Poem::read( "cat src/main.rs > /dev/null".to_string(), &mut Environment::new(), ); assert!(poem.is_ok()); let mut verses = poem.unwrap().into_iter(); let verse = verses.next().unwrap(); assert!(verse.io.contains(&Rune::Write)); assert_eq!( verse.stanza, vec!["cat", "src/main.rs"] .iter() .map(|s| s.to_string()) .collect::>() ); assert_eq!( verse.op, vec!["/dev/null"] .iter() .map(|s| s.to_string()) .collect::>() ); } #[test] fn it_parses_a_verse_with_the_write2_rune() { let poem = Poem::read( "cat src/main.rs 2> /dev/null".to_string(), &mut Environment::new(), ); assert!(poem.is_ok()); let mut verses = poem.unwrap().into_iter(); let verse = verses.next().unwrap(); assert!(verse.io.contains(&Rune::Write2)); assert_eq!( verse.stanza, vec!["cat", "src/main.rs"] .iter() .map(|s| s.to_string()) .collect::>() ); assert_eq!( verse.ep, vec!["/dev/null"] .iter() .map(|s| s.to_string()) .collect::>() ); } #[test] fn it_parses_a_verse_with_the_write_all_rune() { let poem = Poem::read( "cat src/main.rs &> /dev/null".to_string(), &mut Environment::new(), ); assert!(poem.is_ok()); let mut verses = poem.unwrap().into_iter(); let verse = verses.next().unwrap(); assert!(verse.io.contains(&Rune::WriteAll)); assert_eq!( verse.stanza, vec!["cat", "src/main.rs"] .iter() .map(|s| s.to_string()) .collect::>() ); assert_eq!( verse.op, vec!["/dev/null"] .iter() .map(|s| s.to_string()) .collect::>() ); assert_eq!( verse.ep, vec!["/dev/null"] .iter() .map(|s| s.to_string()) .collect::>() ); } #[test] fn it_parses_a_verse_with_the_addendum_rune() { let poem = Poem::read( "cat src/main.rs >> /dev/null".to_string(), &mut Environment::new(), ); assert!(poem.is_ok()); let mut verses = poem.unwrap().into_iter(); let verse = verses.next().unwrap(); assert!(verse.io.contains(&Rune::Addendum)); assert_eq!( verse.stanza, vec!["cat", "src/main.rs"] .iter() .map(|s| s.to_string()) .collect::>() ); assert_eq!( verse.op, vec!["/dev/null"] .iter() .map(|s| s.to_string()) .collect::>() ); } #[test] fn it_parses_a_verse_with_the_addendum2_rune() { let poem = Poem::read( "cat src/main.rs 2>> /dev/null".to_string(), &mut Environment::new(), ); assert!(poem.is_ok()); let mut verses = poem.unwrap().into_iter(); let verse = verses.next().unwrap(); assert!(verse.io.contains(&Rune::Addendum2)); assert_eq!( verse.stanza, vec!["cat", "src/main.rs"] .iter() .map(|s| s.to_string()) .collect::>() ); assert_eq!( verse.ep, vec!["/dev/null"] .iter() .map(|s| s.to_string()) .collect::>() ); } #[test] fn it_parses_a_verse_with_the_addendum_all_rune() { let poem = Poem::read( "cat src/main.rs &>> /dev/null".to_string(), &mut Environment::new(), ); assert!(poem.is_ok()); let mut verses = poem.unwrap().into_iter(); let verse = verses.next().unwrap(); assert!(verse.io.contains(&Rune::AddendumAll)); assert_eq!( verse.stanza, vec!["cat", "src/main.rs"] .iter() .map(|s| s.to_string()) .collect::>() ); assert_eq!( verse.op, vec!["/dev/null"] .iter() .map(|s| s.to_string()) .collect::>() ); assert_eq!( verse.ep, vec!["/dev/null"] .iter() .map(|s| s.to_string()) .collect::>() ); } #[test] fn it_throws_a_parse_error_if_no_files_are_specified_for_the_read_rune() { let poem = Poem::read("lolcat <".to_string(), &mut Environment::new()); assert!(poem.is_err()); let poem = Poem::read("lolcat <;".to_string(), &mut Environment::new()); assert!(poem.is_err()); let poem = Poem::read("lolcat < && ls -la".to_string(), &mut Environment::new()); assert!(poem.is_err()); } #[test] fn it_throws_a_parse_error_if_no_files_are_specified_for_the_write_rune() { let poem = Poem::read("cat src/main.rs >".to_string(), &mut Environment::new()); assert!(poem.is_err()); let poem = Poem::read("cat src/main.rs >;".to_string(), &mut Environment::new()); assert!(poem.is_err()); let poem = Poem::read("cat > && ls -la".to_string(), &mut Environment::new()); assert!(poem.is_err()); } #[test] fn it_throws_a_parse_error_if_no_files_are_specified_for_the_addendum_rune() { let poem = Poem::read("cat src/main.rs >>".to_string(), &mut Environment::new()); assert!(poem.is_err()); let poem = Poem::read("cat src/main.rs >>;".to_string(), &mut Environment::new()); assert!(poem.is_err()); let poem = Poem::read("cat >> && ls -la".to_string(), &mut Environment::new()); assert!(poem.is_err()); } #[test] fn it_parses_a_complex_verse_with_lots_of_different_meters() { let poem = Poem::read( "ls -la | lolcat && echo hello | lolcat && sleep 2 &".to_string(), &mut Environment::new(), ); assert!(poem.is_ok()); let mut verses = poem.unwrap().into_iter(); let verse = verses.next().unwrap(); assert_eq!(verse.verb(), "ls"); assert_eq!(verse.clause(), vec!["-la".to_string()]); assert_eq!(verse.meter, Rune::Couplet); let verse = verses.next().unwrap(); assert_eq!(verse.verb(), "lolcat"); assert_eq!(verse.meter, Rune::And); let verse = verses.next().unwrap(); assert_eq!(verse.verb(), "echo"); assert_eq!(verse.clause(), vec!["hello".to_string()]); assert_eq!(verse.meter, Rune::Couplet); let verse = verses.next().unwrap(); assert_eq!(verse.verb(), "lolcat"); assert_eq!(verse.meter, Rune::And); let verse = verses.next().unwrap(); assert_eq!(verse.verb(), "sleep"); assert_eq!(verse.clause(), vec!["2".to_string()]); assert_eq!(verse.meter, Rune::Quiet); } #[test] fn it_parses_the_continue_meter_without_a_stanza() { let poem = Poem::read(";;;;;;;".to_string(), &mut Environment::new()); assert!(poem.is_ok()); } #[test] fn it_errors_if_the_couplet_meter_is_used_without_a_stanza() { let poem = Poem::read("|".to_string(), &mut Environment::new()); assert!(poem.is_err()); } #[test] fn it_errors_if_the_quiet_meter_is_used_without_a_stanza() { let poem = Poem::read("&".to_string(), &mut Environment::new()); assert!(poem.is_err()); } #[test] fn it_errors_if_the_and_meter_is_used_without_a_stanza() { let poem = Poem::read("&&".to_string(), &mut Environment::new()); assert!(poem.is_err()); } #[test] fn it_parses_a_file() { let file = r" ps aux | lolcat sleep 2 "; let poem = Poem::read(file.to_string(), &mut Environment::new()); assert!(poem.is_ok()); let poem = poem.unwrap(); assert_eq!(poem.len(), 3); let mut verses = poem.into_iter(); let verse = verses.next().unwrap(); assert_eq!(verse.verb(), "ps"); assert_eq!(verse.clause(), vec!["aux".to_string()]); assert_eq!(verse.meter, Rune::Couplet); let verse = verses.next().unwrap(); assert_eq!(verse.verb(), "lolcat"); assert_eq!(verse.meter, Rune::Continue); let verse = verses.next().unwrap(); assert_eq!(verse.verb(), "sleep"); assert_eq!(verse.clause(), vec!["2".to_string()]); assert_eq!(verse.meter, Rune::Continue); } #[test] fn it_parses_a_longer_file() { let file = r" ps aux | lolcat sleep 2 ps aux | lolcat sleep 2 echo hello there export PATH=$PATH:~/.local/bin ps aux | lolcat && lolcat src/main.rs fortune | cowsay | lolcat wc -l src/**/*.rs | lolcat; ls -la | grep git "; let poem = Poem::read(file.to_string(), &mut Environment::new()); assert!(poem.is_ok()); let poem = poem.unwrap(); assert_eq!(poem.len(), 18); } #[test] fn it_catches_parser_errors_related_to_invalid_use_of_special_runes() { let poetry = "cat file.txt &&&".to_string(); assert_eq!(Poem::read(poetry, &mut Environment::new()).is_err(), true); let poetry = "cat file.txt&&|".to_string(); assert_eq!(Poem::read(poetry, &mut Environment::new()).is_err(), true); let poetry = "cat <".to_string(); assert_eq!(Poem::read(poetry, &mut Environment::new()).is_err(), true); } #[test] fn it_catches_parser_errors_related_to_strings() { let poetry = "echo 'hello".to_string(); assert_eq!(Poem::read(poetry, &mut Environment::new()).is_err(), true); let poetry = "echo \"hello".to_string(); assert_eq!(Poem::read(poetry, &mut Environment::new()).is_err(), true); let poetry = "`true".to_string(); assert_eq!(Poem::read(poetry, &mut Environment::new()).is_err(), true); } #[test] fn it_interprets_tilda_as_home() { let poetry = "cd ~".to_string(); let poem = Poem::read(poetry, &mut Environment::new()).unwrap(); assert_eq!(poem[0].verb(), "cd"); assert_eq!(poem[0].clause(), vec![env!("HOME").to_string()]); let poetry = "cd ~/Code/dwarvish".to_string(); let poem = Poem::read(poetry, &mut Environment::new()).unwrap(); assert_eq!(poem[0].verb(), "cd"); assert_eq!( poem[0].clause(), vec![format!("{}/Code/dwarvish", env!("HOME")).to_string()] ); assert_eq!(poem[0].meter, Rune::None); } }