shellwords-1.0.0/.gitignore010064400007650000024000000000241315274227000141040ustar0000000000000000/Cargo.lock /target shellwords-1.0.0/Cargo.toml.orig010064400007650000024000000010211335313364000147770ustar0000000000000000[package] authors = ["Jimmy Cuadra "] categories = ["command-line-interface"] description = "Manipulate strings according to the word parsing rules of the UNIX Bourne shell." documentation = "https://docs.rs/shellwords" homepage = "https://github.com/jimmycuadra/rust-shellwords" keywords = ["bash", "sh", "shell"] license = "MIT" name = "shellwords" readme = "README.md" repository = "https://github.com/jimmycuadra/rust-shellwords" version = "1.0.0" [dependencies] lazy_static = "1.1.0" regex = "1.0.5" shellwords-1.0.0/Cargo.toml0000644000000020740000000000000112600ustar00# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g. crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] name = "shellwords" version = "1.0.0" authors = ["Jimmy Cuadra "] description = "Manipulate strings according to the word parsing rules of the UNIX Bourne shell." homepage = "https://github.com/jimmycuadra/rust-shellwords" documentation = "https://docs.rs/shellwords" readme = "README.md" keywords = ["bash", "sh", "shell"] categories = ["command-line-interface"] license = "MIT" repository = "https://github.com/jimmycuadra/rust-shellwords" [dependencies.lazy_static] version = "1.1.0" [dependencies.regex] version = "1.0.5" shellwords-1.0.0/LICENSE010064400007650000024000000020401315274234000131170ustar0000000000000000Copyright (c) 2017 Jimmy Cuadra Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. shellwords-1.0.0/README.md010064400007650000024000000010501315274264700134030ustar0000000000000000# shellwords Crate **shellwords** provides utilities for parsing strings as they would be interpreted by the UNIX Bourne shell. * [shellwords](https://crates.io/crates/shellwords) on crates.io * [Documentation](https://docs.rs/shellwords) for the latest crates.io release ## Examples Split a string into a vector of words in the same way the UNIX Bourne shell does: ``` rust assert_eq!(split("here are \"two words\"").unwrap(), ["here", "are", "two words"]); ``` ## Legal shellwords is released under the MIT license. See `LICENSE` for details. shellwords-1.0.0/src/lib.rs010064400007650000024000000151261335313274600140340ustar0000000000000000//! Crate `shellwords` provides utilities for parsing strings as they would be interpreted by the //! UNIX Bourne shell. #[deny(missing_debug_implementations, missing_docs, warnings)] #[macro_use] extern crate lazy_static; extern crate regex; use regex::Regex; /// Escapes a string so it will be interpreted as a single word by the UNIX Bourne shell. /// /// If the input string is empty, this function returns an empty quoted string. /// /// # Examples /// /// ``` /// # extern crate shellwords; /// # use shellwords::escape; /// # fn main() { /// assert_eq!(escape("special's.txt"), "special\\'s.txt".to_string()); /// # } /// ``` pub fn escape(input: &str) -> String { lazy_static! { static ref ESCAPE_PATTERN: Regex = Regex::new(r"([^A-Za-z0-9_\-.,:/@\n])").unwrap(); static ref LINE_FEED: Regex = Regex::new(r"\n").unwrap(); } if input.len() == 0 { return "''".to_owned(); } let output = &ESCAPE_PATTERN.replace_all(input, "\\$1"); LINE_FEED.replace_all(output, "'\n'").to_string() } /// Builds a command line string from a list of arguments. /// /// The arguments are combined into a single string with each word separated by a space. Each /// individual word is escaped as necessary via `escape`. /// /// # Examples /// /// ``` /// # extern crate shellwords; /// # use shellwords::join; /// # fn main() { /// let args = ["There's", "a", "time", "and", "place", "for", "everything"]; /// assert_eq!(join(&args), "There\\'s a time and place for everything"); /// # } /// ``` pub fn join(args: &[&str]) -> String { let escaped: Vec = args.iter().map(|arg| escape(arg)).collect(); escaped.join(" ") } /// Splits a string into a vector of words in the same way the UNIX Bourne shell does. /// /// This function does not behave like a full command line parser. Only single quotes, double /// quotes, and backslashes are treated as metacharacters. Within double quoted strings, /// backslashes are only treated as metacharacters when followed by one of the following /// characters: /// /// * $ /// * ` /// * " /// * \ /// * newline /// /// # Errors /// /// If the input contains mismatched quotes (a quoted string missing a matching ending quote), /// a `MismatchedQuotes` error is returned. /// /// # Examples /// /// Quoted strings are intepreted as one word: /// /// ``` /// # extern crate shellwords; /// # use shellwords::split; /// # fn main() { /// assert_eq!(split("here are \"two words\"").unwrap(), ["here", "are", "two words"]); /// # } /// ``` /// /// The pipe character has no special meaning: /// /// ``` /// # extern crate shellwords; /// # use shellwords::split; /// # fn main() { /// assert_eq!(split("cat file.txt | less").unwrap(), ["cat", "file.txt", "|", "less"]); /// # } /// ``` /// pub fn split(input: &str) -> Result, MismatchedQuotes> { lazy_static! { static ref MAIN_PATTERN: Regex = Regex::new( r#"(?m:\s*(?:([^\s\\'"]+)|'([^']*)'|"((?:[^"\\]|\\.)*)"|(\\.?)|(\S))(\s|\z)?)"# ).unwrap(); static ref ESCAPE_PATTERN: Regex = Regex::new(r#"\\(.)"#).unwrap(); static ref METACHAR_PATTERN: Regex = Regex::new(r#"\\([$`"\\\n])"#).unwrap(); } let mut words = Vec::new(); let mut field = String::new(); for capture in MAIN_PATTERN.captures_iter(input) { if let Some(word) = capture.get(1) { field.push_str(word.as_str()); } else if let Some(single_quoted_word) = capture.get(2) { field.push_str(single_quoted_word.as_str()); } else if let Some(double_quoted_word) = capture.get(3) { field.push_str(&METACHAR_PATTERN .replace_all(double_quoted_word.as_str(), "$1")); } else if let Some(escape) = capture.get(4) { field.push_str(&ESCAPE_PATTERN.replace_all(escape.as_str(), "$1")); } else if capture.get(5).is_some() { return Err(MismatchedQuotes); } if capture.get(6).is_some() { words.push(field); field = String::new(); } } Ok(words) } /// An error when splitting a string with mismatched quotes. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct MismatchedQuotes; #[cfg(test)] mod tests { use super::{escape, join, split, MismatchedQuotes}; #[test] fn nothing_special() { assert_eq!(split("a b c d").unwrap(), ["a", "b", "c", "d"]); } #[test] fn quoted_strings() { assert_eq!(split("a \"b b\" a").unwrap(), ["a", "b b", "a"]); } #[test] fn escaped_double_quotes() { assert_eq!(split("a \"\\\"b\\\" c\" d").unwrap(), ["a", "\"b\" c", "d"]); } #[test] fn escaped_single_quotes() { assert_eq!(split("a \"'b' c\" d").unwrap(), ["a", "'b' c", "d"]); } #[test] fn escaped_spaces() { assert_eq!(split("a b\\ c d").unwrap(), ["a", "b c", "d"]); } #[test] fn bad_double_quotes() { assert_eq!(split("a \"b c d e").unwrap_err(), MismatchedQuotes); } #[test] fn bad_single_quotes() { assert_eq!(split("a 'b c d e").unwrap_err(), MismatchedQuotes); } #[test] fn bad_quotes() { assert_eq!(split("one '\"\"\"").unwrap_err(), MismatchedQuotes); } #[test] fn trailing_whitespace() { assert_eq!(split("a b c d ").unwrap(), ["a", "b", "c", "d"]); } #[test] fn empty_escape() { assert_eq!(escape(""), "''"); } #[test] fn full_escape() { assert_eq!(escape("foo '\"' bar"), "foo\\ \\'\\\"\\'\\ bar"); } #[test] fn escape_and_join_whitespace() { let empty = "".to_owned(); let space = " ".to_owned(); let newline = "\n".to_owned(); let tab = "\t".to_owned(); let tokens = vec![ empty.clone(), space.clone(), space.clone() + &space, newline.clone(), newline.clone() + &newline, tab.clone(), tab.clone() + &tab, empty.clone(), space + &newline + &tab, empty, ]; for token in tokens.iter() { assert_eq!( vec![token.as_str()], split(escape(token.as_str()).as_str()).unwrap() ); } let borrowed_tokens: Vec<&str> = tokens.iter().map(|token| &token[..]).collect(); assert_eq!( tokens, split(join(borrowed_tokens.as_slice()).as_str()).unwrap() ); } #[test] fn escape_multibyte() { assert_eq!(escape("あい"), "\\あ\\い"); } #[test] fn percent_signs() { assert_eq!(split("abc '%foo bar%'").unwrap(), ["abc", "%foo bar%"]); } } shellwords-1.0.0/.cargo_vcs_info.json0000644000000001120000000000000132510ustar00{ "git": { "sha1": "6acafc03963dd956d80b7f6ba61214f9b081c5bb" } }