//! ## Parser //! //! `parser` is the module which provides utilities for parsing different kind of stuff /** * MIT License * * termscp - Copyright (c) 2021 Christian Visintin * * 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. */ // Locals use crate::filetransfer::{ params::{AwsS3Params, GenericProtocolParams, ProtocolParams}, FileTransferParams, FileTransferProtocol, }; #[cfg(not(test))] // NOTE: don't use configuration during tests use crate::system::config_client::ConfigClient; #[cfg(not(test))] // NOTE: don't use configuration during tests use crate::system::environment; // Ext use bytesize::ByteSize; use chrono::format::ParseError; use chrono::prelude::*; use regex::Regex; use std::path::PathBuf; use std::str::FromStr; use std::time::{Duration, SystemTime}; use tuirealm::tui::style::Color; // Regex lazy_static! { /** * This regex matches the protocol used as option * Regex matches: * - group 1: Some(protocol) | None * - group 2: Some(other args) */ static ref REMOTE_OPT_PROTOCOL_REGEX: Regex = Regex::new(r"(?:([a-z0-9]+)://)?(?:(.+))").unwrap(); /** * Regex matches: * - group 1: Some(user) | None * - group 2: Address * - group 3: Some(port) | None * - group 4: Some(path) | None */ static ref REMOTE_GENERIC_OPT_REGEX: Regex = Regex::new(r"(?:([^@]+)@)?(?:([^:]+))(?::((?:[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(?:[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])))?(?::([^:]+))?").ok().unwrap(); /** * Regex matches: * - group 1: Bucket * - group 2: Region * - group 3: Some(profile) | None * - group 4: Some(path) | None */ static ref REMOTE_S3_OPT_REGEX: Regex = Regex::new(r"(?:([^@]+)@)(?:([^:]+))(?::([a-zA-Z0-9][^:]+))?(?::([^:]+))?").unwrap(); /** * Regex matches: * - group 1: Version * E.g. termscp-0.3.2 => 0.3.2 * v0.4.0 => 0.4.0 */ static ref SEMVER_REGEX: Regex = Regex::new(r".*(:?[0-9]\.[0-9]\.[0-9])").unwrap(); /** * Regex matches: * - group 1: Red * - group 2: Green * - group 3: Blue */ static ref COLOR_HEX_REGEX: Regex = Regex::new(r"#(:?[0-9a-fA-F]{2})(:?[0-9a-fA-F]{2})(:?[0-9a-fA-F]{2})").unwrap(); /** * Regex matches: * - group 2: Red * - group 4: Green * - group 6: blue */ static ref COLOR_RGB_REGEX: Regex = Regex::new(r"^(rgb)?\(?([01]?\d\d?|2[0-4]\d|25[0-5])(\W+)([01]?\d\d?|2[0-4]\d|25[0-5])\W+(([01]?\d\d?|2[0-4]\d|25[0-5])\)?)").unwrap(); /** * Regex matches: * - group 1: amount (number) * - group 4: unit (K, M, G, T, P) */ static ref BYTESIZE_REGEX: Regex = Regex::new(r"(:?([0-9])+)( )*(:?[KMGTP])?B").unwrap(); } // -- remote opts /// ### parse_remote_opt /// /// Parse remote option string. Returns in case of success a RemoteOptions struct /// For ssh if username is not provided, current user will be used. /// In case of error, message is returned /// If port is missing default port will be used for each protocol /// SFTP => 22 /// FTP => 21 /// The option string has the following syntax /// [protocol://][username@]{address}[:port][:path] /// The only argument which is mandatory is address /// NOTE: possible strings /// - 172.26.104.1 /// - root@172.26.104.1 /// - sftp://root@172.26.104.1 /// - sftp://172.26.104.1:4022 /// - sftp://172.26.104.1 /// - ... pub fn parse_remote_opt(s: &str) -> Result { // Set protocol to default protocol #[cfg(not(test))] // NOTE: don't use configuration during tests let default_protocol: FileTransferProtocol = match environment::init_config_dir() { Ok(p) => match p { Some(p) => { // Create config client let (config_path, ssh_key_path) = environment::get_config_paths(p.as_path()); match ConfigClient::new(config_path.as_path(), ssh_key_path.as_path()) { Ok(cli) => cli.get_default_protocol(), Err(_) => FileTransferProtocol::Sftp, } } None => FileTransferProtocol::Sftp, }, Err(_) => FileTransferProtocol::Sftp, }; #[cfg(test)] // NOTE: during test set protocol just to Sftp let default_protocol: FileTransferProtocol = FileTransferProtocol::Sftp; // Get protocol let (protocol, s): (FileTransferProtocol, String) = parse_remote_opt_protocol(s, default_protocol)?; // Match against regex for protocol type match protocol { FileTransferProtocol::AwsS3 => parse_s3_remote_opt(s.as_str()), protocol => parse_generic_remote_opt(s.as_str(), protocol), } } /// ### parse_remote_opt_protocol /// /// Parse protocol from CLI option. In case of success, return the protocol to be used and the remaining arguments fn parse_remote_opt_protocol( s: &str, default: FileTransferProtocol, ) -> Result<(FileTransferProtocol, String), String> { match REMOTE_OPT_PROTOCOL_REGEX.captures(s) { Some(groups) => { // Parse protocol or use default let protocol = groups.get(1).map(|x| { FileTransferProtocol::from_str(x.as_str()) .map_err(|_| format!("Unknown protocol \"{}\"", x.as_str())) }); let protocol = match protocol { Some(Ok(protocol)) => protocol, Some(Err(err)) => return Err(err), None => default, }; // Return protocol and remaining arguments Ok(( protocol, groups .get(2) .map(|x| x.as_str().to_string()) .unwrap_or_default(), )) } None => Err("Invalid args".to_string()), } } /// ### parse_generic_remote_opt /// /// Parse generic remote options fn parse_generic_remote_opt( s: &str, protocol: FileTransferProtocol, ) -> Result { match REMOTE_GENERIC_OPT_REGEX.captures(s) { Some(groups) => { // Match user let username: Option = match groups.get(1) { Some(group) => Some(group.as_str().to_string()), None => match protocol { // If group is empty, set to current user FileTransferProtocol::Scp | FileTransferProtocol::Sftp => { Some(whoami::username()) } _ => None, }, }; // Get address let address: String = match groups.get(2) { Some(group) => group.as_str().to_string(), None => return Err(String::from("Missing address")), }; // Get port let port: u16 = match groups.get(3) { Some(port) => match port.as_str().parse::() { // Try to parse port Ok(p) => p, Err(err) => return Err(format!("Bad port \"{}\": {}", port.as_str(), err)), }, None => match protocol { // Set port based on protocol FileTransferProtocol::Ftp(_) => 21, FileTransferProtocol::Scp => 22, FileTransferProtocol::Sftp => 22, _ => 22, // Doesn't matter }, }; // Get workdir let entry_directory: Option = groups.get(4).map(|group| PathBuf::from(group.as_str())); let params: ProtocolParams = ProtocolParams::Generic( GenericProtocolParams::default() .address(address) .port(port) .username(username), ); Ok(FileTransferParams::new(protocol, params).entry_directory(entry_directory)) } None => Err(String::from("Bad remote host syntax!")), } } /// ### parse_s3_remote_opt /// /// Parse remote options for s3 protocol fn parse_s3_remote_opt(s: &str) -> Result { match REMOTE_S3_OPT_REGEX.captures(s) { Some(groups) => { let bucket: String = groups .get(1) .map(|x| x.as_str().to_string()) .unwrap_or_default(); let region: String = groups .get(2) .map(|x| x.as_str().to_string()) .unwrap_or_default(); let profile: Option = groups.get(3).map(|x| x.as_str().to_string()); let entry_directory: Option = groups.get(4).map(|group| PathBuf::from(group.as_str())); Ok(FileTransferParams::new( FileTransferProtocol::AwsS3, ProtocolParams::AwsS3(AwsS3Params::new(bucket, region, profile)), ) .entry_directory(entry_directory)) } None => Err(String::from("Bad remote host syntax!")), } } /// ### parse_lstime /// /// Convert ls syntax time to System Time /// ls time has two possible syntax: /// 1. if year is current: %b %d %H:%M (e.g. Nov 5 13:46) /// 2. else: %b %d %Y (e.g. Nov 5 2019) pub fn parse_lstime(tm: &str, fmt_year: &str, fmt_hours: &str) -> Result { let datetime: NaiveDateTime = match NaiveDate::parse_from_str(tm, fmt_year) { Ok(date) => { // Case 2. // Return NaiveDateTime from NaiveDate with time 00:00:00 date.and_hms(0, 0, 0) } Err(_) => { // Might be case 1. // We need to add Current Year at the end of the string let this_year: i32 = Utc::now().year(); let date_time_str: String = format!("{} {}", tm, this_year); // Now parse NaiveDateTime::parse_from_str( date_time_str.as_ref(), format!("{} %Y", fmt_hours).as_ref(), )? } }; // Convert datetime to system time let sys_time: SystemTime = SystemTime::UNIX_EPOCH; Ok(sys_time .checked_add(Duration::from_secs(datetime.timestamp() as u64)) .unwrap_or(SystemTime::UNIX_EPOCH)) } /// ### parse_datetime /// /// Parse date time string representation and transform it into `SystemTime` #[allow(dead_code)] pub fn parse_datetime(tm: &str, fmt: &str) -> Result { match NaiveDateTime::parse_from_str(tm, fmt) { Ok(dt) => { let sys_time: SystemTime = SystemTime::UNIX_EPOCH; Ok(sys_time .checked_add(Duration::from_secs(dt.timestamp() as u64)) .unwrap_or(SystemTime::UNIX_EPOCH)) } Err(err) => Err(err), } } /// ### parse_semver /// /// Parse semver string pub fn parse_semver(haystack: &str) -> Option { match SEMVER_REGEX.captures(haystack) { Some(groups) => groups.get(1).map(|version| version.as_str().to_string()), None => None, } } /// ### parse_color /// /// Parse color from string into a `Color` enum. /// /// Color may be in different format: /// /// 1. color name: /// - Black, /// - Blue, /// - Cyan, /// - DarkGray, /// - Gray, /// - Green, /// - LightBlue, /// - LightCyan, /// - LightGreen, /// - LightMagenta, /// - LightRed, /// - LightYellow, /// - Magenta, /// - Red, /// - Reset, /// - White, /// - Yellow, /// 2. Hex format: /// - #f0ab05 /// - #AA33BC /// 3. Rgb format: /// - rgb(255, 64, 32) /// - rgb(255,64,32) /// - 255, 64, 32 pub fn parse_color(color: &str) -> Option { match color.to_lowercase().as_str() { // -- lib colors "black" => Some(Color::Black), "blue" => Some(Color::Blue), "cyan" => Some(Color::Cyan), "darkgray" | "darkgrey" => Some(Color::DarkGray), "default" => Some(Color::Reset), "gray" => Some(Color::Gray), "green" => Some(Color::Green), "lightblue" => Some(Color::LightBlue), "lightcyan" => Some(Color::LightCyan), "lightgreen" => Some(Color::LightGreen), "lightmagenta" => Some(Color::LightMagenta), "lightred" => Some(Color::LightRed), "lightyellow" => Some(Color::LightYellow), "magenta" => Some(Color::Magenta), "red" => Some(Color::Red), "white" => Some(Color::White), "yellow" => Some(Color::Yellow), // -- css colors "aliceblue" => Some(Color::Rgb(240, 248, 255)), "antiquewhite" => Some(Color::Rgb(250, 235, 215)), "aqua" => Some(Color::Rgb(0, 255, 255)), "aquamarine" => Some(Color::Rgb(127, 255, 212)), "azure" => Some(Color::Rgb(240, 255, 255)), "beige" => Some(Color::Rgb(245, 245, 220)), "bisque" => Some(Color::Rgb(255, 228, 196)), "blanchedalmond" => Some(Color::Rgb(255, 235, 205)), "blueviolet" => Some(Color::Rgb(138, 43, 226)), "brown" => Some(Color::Rgb(165, 42, 42)), "burlywood" => Some(Color::Rgb(222, 184, 135)), "cadetblue" => Some(Color::Rgb(95, 158, 160)), "chartreuse" => Some(Color::Rgb(127, 255, 0)), "chocolate" => Some(Color::Rgb(210, 105, 30)), "coral" => Some(Color::Rgb(255, 127, 80)), "cornflowerblue" => Some(Color::Rgb(100, 149, 237)), "cornsilk" => Some(Color::Rgb(255, 248, 220)), "crimson" => Some(Color::Rgb(220, 20, 60)), "darkblue" => Some(Color::Rgb(0, 0, 139)), "darkcyan" => Some(Color::Rgb(0, 139, 139)), "darkgoldenrod" => Some(Color::Rgb(184, 134, 11)), "darkgreen" => Some(Color::Rgb(0, 100, 0)), "darkkhaki" => Some(Color::Rgb(189, 183, 107)), "darkmagenta" => Some(Color::Rgb(139, 0, 139)), "darkolivegreen" => Some(Color::Rgb(85, 107, 47)), "darkorange" => Some(Color::Rgb(255, 140, 0)), "darkorchid" => Some(Color::Rgb(153, 50, 204)), "darkred" => Some(Color::Rgb(139, 0, 0)), "darksalmon" => Some(Color::Rgb(233, 150, 122)), "darkseagreen" => Some(Color::Rgb(143, 188, 143)), "darkslateblue" => Some(Color::Rgb(72, 61, 139)), "darkslategray" | "darkslategrey" => Some(Color::Rgb(47, 79, 79)), "darkturquoise" => Some(Color::Rgb(0, 206, 209)), "darkviolet" => Some(Color::Rgb(148, 0, 211)), "deeppink" => Some(Color::Rgb(255, 20, 147)), "deepskyblue" => Some(Color::Rgb(0, 191, 255)), "dimgray" | "dimgrey" => Some(Color::Rgb(105, 105, 105)), "dodgerblue" => Some(Color::Rgb(30, 144, 255)), "firebrick" => Some(Color::Rgb(178, 34, 34)), "floralwhite" => Some(Color::Rgb(255, 250, 240)), "forestgreen" => Some(Color::Rgb(34, 139, 34)), "fuchsia" => Some(Color::Rgb(255, 0, 255)), "gainsboro" => Some(Color::Rgb(220, 220, 220)), "ghostwhite" => Some(Color::Rgb(248, 248, 255)), "gold" => Some(Color::Rgb(255, 215, 0)), "goldenrod" => Some(Color::Rgb(218, 165, 32)), "greenyellow" => Some(Color::Rgb(173, 255, 47)), "grey" => Some(Color::Rgb(128, 128, 128)), "honeydew" => Some(Color::Rgb(240, 255, 240)), "hotpink" => Some(Color::Rgb(255, 105, 180)), "indianred" => Some(Color::Rgb(205, 92, 92)), "indigo" => Some(Color::Rgb(75, 0, 130)), "ivory" => Some(Color::Rgb(255, 255, 240)), "khaki" => Some(Color::Rgb(240, 230, 140)), "lavender" => Some(Color::Rgb(230, 230, 250)), "lavenderblush" => Some(Color::Rgb(255, 240, 245)), "lawngreen" => Some(Color::Rgb(124, 252, 0)), "lemonchiffon" => Some(Color::Rgb(255, 250, 205)), "lightcoral" => Some(Color::Rgb(240, 128, 128)), "lightgoldenrodyellow" => Some(Color::Rgb(250, 250, 210)), "lightgray" | "lightgrey" => Some(Color::Rgb(211, 211, 211)), "lightpink" => Some(Color::Rgb(255, 182, 193)), "lightsalmon" => Some(Color::Rgb(255, 160, 122)), "lightseagreen" => Some(Color::Rgb(32, 178, 170)), "lightskyblue" => Some(Color::Rgb(135, 206, 250)), "lightslategray" | "lightslategrey" => Some(Color::Rgb(119, 136, 153)), "lightsteelblue" => Some(Color::Rgb(176, 196, 222)), "lime" => Some(Color::Rgb(0, 255, 0)), "limegreen" => Some(Color::Rgb(50, 205, 50)), "linen" => Some(Color::Rgb(250, 240, 230)), "maroon" => Some(Color::Rgb(128, 0, 0)), "mediumaquamarine" => Some(Color::Rgb(102, 205, 170)), "mediumblue" => Some(Color::Rgb(0, 0, 205)), "mediumorchid" => Some(Color::Rgb(186, 85, 211)), "mediumpurple" => Some(Color::Rgb(147, 112, 219)), "mediumseagreen" => Some(Color::Rgb(60, 179, 113)), "mediumslateblue" => Some(Color::Rgb(123, 104, 238)), "mediumspringgreen" => Some(Color::Rgb(0, 250, 154)), "mediumturquoise" => Some(Color::Rgb(72, 209, 204)), "mediumvioletred" => Some(Color::Rgb(199, 21, 133)), "midnightblue" => Some(Color::Rgb(25, 25, 112)), "mintcream" => Some(Color::Rgb(245, 255, 250)), "mistyrose" => Some(Color::Rgb(255, 228, 225)), "moccasin" => Some(Color::Rgb(255, 228, 181)), "navajowhite" => Some(Color::Rgb(255, 222, 173)), "navy" => Some(Color::Rgb(0, 0, 128)), "oldlace" => Some(Color::Rgb(253, 245, 230)), "olive" => Some(Color::Rgb(128, 128, 0)), "olivedrab" => Some(Color::Rgb(107, 142, 35)), "orange" => Some(Color::Rgb(255, 165, 0)), "orangered" => Some(Color::Rgb(255, 69, 0)), "orchid" => Some(Color::Rgb(218, 112, 214)), "palegoldenrod" => Some(Color::Rgb(238, 232, 170)), "palegreen" => Some(Color::Rgb(152, 251, 152)), "paleturquoise" => Some(Color::Rgb(175, 238, 238)), "palevioletred" => Some(Color::Rgb(219, 112, 147)), "papayawhip" => Some(Color::Rgb(255, 239, 213)), "peachpuff" => Some(Color::Rgb(255, 218, 185)), "peru" => Some(Color::Rgb(205, 133, 63)), "pink" => Some(Color::Rgb(255, 192, 203)), "plum" => Some(Color::Rgb(221, 160, 221)), "powderblue" => Some(Color::Rgb(176, 224, 230)), "purple" => Some(Color::Rgb(128, 0, 128)), "rebeccapurple" => Some(Color::Rgb(102, 51, 153)), "rosybrown" => Some(Color::Rgb(188, 143, 143)), "royalblue" => Some(Color::Rgb(65, 105, 225)), "saddlebrown" => Some(Color::Rgb(139, 69, 19)), "salmon" => Some(Color::Rgb(250, 128, 114)), "sandybrown" => Some(Color::Rgb(244, 164, 96)), "seagreen" => Some(Color::Rgb(46, 139, 87)), "seashell" => Some(Color::Rgb(255, 245, 238)), "sienna" => Some(Color::Rgb(160, 82, 45)), "silver" => Some(Color::Rgb(192, 192, 192)), "skyblue" => Some(Color::Rgb(135, 206, 235)), "slateblue" => Some(Color::Rgb(106, 90, 205)), "slategray" | "slategrey" => Some(Color::Rgb(112, 128, 144)), "snow" => Some(Color::Rgb(255, 250, 250)), "springgreen" => Some(Color::Rgb(0, 255, 127)), "steelblue" => Some(Color::Rgb(70, 130, 180)), "tan" => Some(Color::Rgb(210, 180, 140)), "teal" => Some(Color::Rgb(0, 128, 128)), "thistle" => Some(Color::Rgb(216, 191, 216)), "tomato" => Some(Color::Rgb(255, 99, 71)), "turquoise" => Some(Color::Rgb(64, 224, 208)), "violet" => Some(Color::Rgb(238, 130, 238)), "wheat" => Some(Color::Rgb(245, 222, 179)), "whitesmoke" => Some(Color::Rgb(245, 245, 245)), "yellowgreen" => Some(Color::Rgb(154, 205, 50)), // -- hex and rgb other => { // Try as hex if let Some(color) = parse_hex_color(other) { Some(color) } else { parse_rgb_color(other) } } } } /// ### parse_hex_color /// /// Try to parse a color in hex format, such as: /// /// - #f0ab05 /// - #AA33BC fn parse_hex_color(color: &str) -> Option { COLOR_HEX_REGEX.captures(color).map(|groups| { Color::Rgb( u8::from_str_radix(groups.get(1).unwrap().as_str(), 16) .ok() .unwrap(), u8::from_str_radix(groups.get(2).unwrap().as_str(), 16) .ok() .unwrap(), u8::from_str_radix(groups.get(3).unwrap().as_str(), 16) .ok() .unwrap(), ) }) } /// ### parse_rgb_color /// /// Try to parse a color in rgb format, such as: /// /// - rgb(255, 64, 32) /// - rgb(255,64,32) /// - 255, 64, 32 fn parse_rgb_color(color: &str) -> Option { COLOR_RGB_REGEX.captures(color).map(|groups| { Color::Rgb( u8::from_str(groups.get(2).unwrap().as_str()).ok().unwrap(), u8::from_str(groups.get(4).unwrap().as_str()).ok().unwrap(), u8::from_str(groups.get(6).unwrap().as_str()).ok().unwrap(), ) }) } #[derive(Debug, PartialEq)] enum ByteUnit { Byte, Kilobyte, Megabyte, Gigabyte, Terabyte, Petabyte, } impl FromStr for ByteUnit { type Err = &'static str; fn from_str(s: &str) -> Result { match s { "B" => Ok(Self::Byte), "KB" => Ok(Self::Kilobyte), "MB" => Ok(Self::Megabyte), "GB" => Ok(Self::Gigabyte), "TB" => Ok(Self::Terabyte), "PB" => Ok(Self::Petabyte), _ => Err("Invalid unit"), } } } /// ### parse_bytesize /// /// Parse bytes repr (e.g. `24 MB`) into `ByteSize` pub fn parse_bytesize>(bytes: S) -> Option { match BYTESIZE_REGEX.captures(bytes.as_ref()) { None => None, Some(groups) => { let amount = groups .get(1) .map(|x| x.as_str().parse::().unwrap_or(0))?; let unit = groups.get(4).map(|x| x.as_str().to_string()); let unit = format!("{}B", unit.unwrap_or_default()); let unit = ByteUnit::from_str(unit.as_str()).unwrap(); Some(match unit { ByteUnit::Byte => ByteSize::b(amount), ByteUnit::Gigabyte => ByteSize::gib(amount), ByteUnit::Kilobyte => ByteSize::kib(amount), ByteUnit::Megabyte => ByteSize::mib(amount), ByteUnit::Petabyte => ByteSize::pib(amount), ByteUnit::Terabyte => ByteSize::tib(amount), }) } } } #[cfg(test)] mod tests { use super::*; use crate::utils::fmt::fmt_time; use pretty_assertions::assert_eq; #[test] fn test_utils_parse_remote_opt() { // Base case let result: FileTransferParams = parse_remote_opt(&String::from("172.26.104.1")) .ok() .unwrap(); let params = result.params.generic_params().unwrap(); assert_eq!(result.protocol, FileTransferProtocol::Sftp); assert_eq!(params.address, String::from("172.26.104.1")); assert_eq!(params.port, 22); assert!(params.username.is_some()); // User case let result: FileTransferParams = parse_remote_opt(&String::from("root@172.26.104.1")) .ok() .unwrap(); let params = result.params.generic_params().unwrap(); assert_eq!(result.protocol, FileTransferProtocol::Sftp); assert_eq!(params.address, String::from("172.26.104.1")); assert_eq!(params.port, 22); assert_eq!( params.username.as_deref().unwrap().to_string(), String::from("root") ); assert!(result.entry_directory.is_none()); // User + port let result: FileTransferParams = parse_remote_opt(&String::from("root@172.26.104.1:8022")) .ok() .unwrap(); let params = result.params.generic_params().unwrap(); assert_eq!(params.address, String::from("172.26.104.1")); assert_eq!(params.port, 8022); assert_eq!( params.username.as_deref().unwrap().to_string(), String::from("root") ); assert_eq!(result.protocol, FileTransferProtocol::Sftp); assert!(result.entry_directory.is_none()); // Port only let result: FileTransferParams = parse_remote_opt(&String::from("172.26.104.1:4022")) .ok() .unwrap(); let params = result.params.generic_params().unwrap(); assert_eq!(result.protocol, FileTransferProtocol::Sftp); assert_eq!(params.address, String::from("172.26.104.1")); assert_eq!(params.port, 4022); assert!(params.username.is_some()); assert!(result.entry_directory.is_none()); // Protocol let result: FileTransferParams = parse_remote_opt(&String::from("ftp://172.26.104.1")) .ok() .unwrap(); let params = result.params.generic_params().unwrap(); assert_eq!(result.protocol, FileTransferProtocol::Ftp(false)); assert_eq!(params.address, String::from("172.26.104.1")); assert_eq!(params.port, 21); // Fallback to ftp default assert!(params.username.is_none()); // Doesn't fall back assert!(result.entry_directory.is_none()); // Protocol let result: FileTransferParams = parse_remote_opt(&String::from("sftp://172.26.104.1")) .ok() .unwrap(); let params = result.params.generic_params().unwrap(); assert_eq!(result.protocol, FileTransferProtocol::Sftp); assert_eq!(params.address, String::from("172.26.104.1")); assert_eq!(params.port, 22); // Fallback to sftp default assert!(params.username.is_some()); // Doesn't fall back assert!(result.entry_directory.is_none()); let result: FileTransferParams = parse_remote_opt(&String::from("scp://172.26.104.1")) .ok() .unwrap(); let params = result.params.generic_params().unwrap(); assert_eq!(result.protocol, FileTransferProtocol::Scp); assert_eq!(params.address, String::from("172.26.104.1")); assert_eq!(params.port, 22); // Fallback to scp default assert!(params.username.is_some()); // Doesn't fall back assert!(result.entry_directory.is_none()); // Protocol + user let result: FileTransferParams = parse_remote_opt(&String::from("ftps://anon@172.26.104.1")) .ok() .unwrap(); let params = result.params.generic_params().unwrap(); assert_eq!(result.protocol, FileTransferProtocol::Ftp(true)); assert_eq!(params.address, String::from("172.26.104.1")); assert_eq!(params.port, 21); // Fallback to ftp default assert_eq!( params.username.as_deref().unwrap().to_string(), String::from("anon") ); assert!(result.entry_directory.is_none()); // Path let result: FileTransferParams = parse_remote_opt(&String::from("root@172.26.104.1:8022:/var")) .ok() .unwrap(); let params = result.params.generic_params().unwrap(); assert_eq!(result.protocol, FileTransferProtocol::Sftp); assert_eq!(params.address, String::from("172.26.104.1")); assert_eq!(params.port, 8022); assert_eq!( params.username.as_deref().unwrap().to_string(), String::from("root") ); assert_eq!(result.entry_directory.unwrap(), PathBuf::from("/var")); // Port only let result: FileTransferParams = parse_remote_opt(&String::from("172.26.104.1:home")) .ok() .unwrap(); let params = result.params.generic_params().unwrap(); assert_eq!(result.protocol, FileTransferProtocol::Sftp); assert_eq!(params.address, String::from("172.26.104.1")); assert_eq!(params.port, 22); assert!(params.username.is_some()); assert_eq!(result.entry_directory.unwrap(), PathBuf::from("home")); // All together now let result: FileTransferParams = parse_remote_opt(&String::from("ftp://anon@172.26.104.1:8021:/tmp")) .ok() .unwrap(); let params = result.params.generic_params().unwrap(); assert_eq!(result.protocol, FileTransferProtocol::Ftp(false)); assert_eq!(params.address, String::from("172.26.104.1")); assert_eq!(params.port, 8021); // Fallback to ftp default assert_eq!( params.username.as_deref().unwrap().to_string(), String::from("anon") ); assert_eq!(result.entry_directory.unwrap(), PathBuf::from("/tmp")); // bad syntax // Bad protocol assert!(parse_remote_opt(&String::from("omar://172.26.104.1")).is_err()); // Bad port assert!(parse_remote_opt(&String::from("scp://172.26.104.1:650000")).is_err()); } #[test] fn parse_aws_s3_opt() { // Simple let result: FileTransferParams = parse_remote_opt(&String::from("s3://mybucket@eu-central-1")) .ok() .unwrap(); let params = result.params.s3_params().unwrap(); assert_eq!(result.protocol, FileTransferProtocol::AwsS3); assert_eq!(result.entry_directory, None); assert_eq!(params.bucket_name.as_str(), "mybucket"); assert_eq!(params.region.as_str(), "eu-central-1"); assert_eq!(params.profile, None); // With profile let result: FileTransferParams = parse_remote_opt(&String::from("s3://mybucket@eu-central-1:default")) .ok() .unwrap(); let params = result.params.s3_params().unwrap(); assert_eq!(result.protocol, FileTransferProtocol::AwsS3); assert_eq!(result.entry_directory, None); assert_eq!(params.bucket_name.as_str(), "mybucket"); assert_eq!(params.region.as_str(), "eu-central-1"); assert_eq!(params.profile.as_deref(), Some("default")); // With wrkdir only let result: FileTransferParams = parse_remote_opt(&String::from("s3://mybucket@eu-central-1:/foobar")) .ok() .unwrap(); let params = result.params.s3_params().unwrap(); assert_eq!(result.protocol, FileTransferProtocol::AwsS3); assert_eq!(result.entry_directory, Some(PathBuf::from("/foobar"))); assert_eq!(params.bucket_name.as_str(), "mybucket"); assert_eq!(params.region.as_str(), "eu-central-1"); assert_eq!(params.profile, None); // With all arguments let result: FileTransferParams = parse_remote_opt(&String::from("s3://mybucket@eu-central-1:default:/foobar")) .ok() .unwrap(); let params = result.params.s3_params().unwrap(); assert_eq!(result.protocol, FileTransferProtocol::AwsS3); assert_eq!(result.entry_directory, Some(PathBuf::from("/foobar"))); assert_eq!(params.bucket_name.as_str(), "mybucket"); assert_eq!(params.region.as_str(), "eu-central-1"); assert_eq!(params.profile.as_deref(), Some("default")); // -- bad args assert!(parse_remote_opt(&String::from("s3://mybucket:default:/foobar")).is_err()); } #[test] fn test_utils_parse_lstime() { // Good cases assert_eq!( fmt_time( parse_lstime("Nov 5 16:32", "%b %d %Y", "%b %d %H:%M") .ok() .unwrap(), "%m %d %M" ) .as_str(), "11 05 32" ); assert_eq!( fmt_time( parse_lstime("Dec 2 21:32", "%b %d %Y", "%b %d %H:%M") .ok() .unwrap(), "%m %d %M" ) .as_str(), "12 02 32" ); assert_eq!( parse_lstime("Nov 5 2018", "%b %d %Y", "%b %d %H:%M") .ok() .unwrap() .duration_since(SystemTime::UNIX_EPOCH) .ok() .unwrap(), Duration::from_secs(1541376000) ); assert_eq!( parse_lstime("Mar 18 2018", "%b %d %Y", "%b %d %H:%M") .ok() .unwrap() .duration_since(SystemTime::UNIX_EPOCH) .ok() .unwrap(), Duration::from_secs(1521331200) ); // bad cases assert!(parse_lstime("Oma 31 2018", "%b %d %Y", "%b %d %H:%M").is_err()); assert!(parse_lstime("Feb 31 2018", "%b %d %Y", "%b %d %H:%M").is_err()); assert!(parse_lstime("Feb 15 25:32", "%b %d %Y", "%b %d %H:%M").is_err()); } #[test] fn test_utils_parse_datetime() { assert_eq!( parse_datetime("04-08-14 03:09PM", "%d-%m-%y %I:%M%p") .ok() .unwrap() .duration_since(SystemTime::UNIX_EPOCH) .ok() .unwrap(), Duration::from_secs(1407164940) ); // Not enough argument for datetime assert!(parse_datetime("04-08-14", "%d-%m-%y").is_err()); } #[test] fn test_utils_parse_semver() { assert_eq!( parse_semver("termscp-0.3.2").unwrap(), String::from("0.3.2") ); assert_eq!(parse_semver("v0.4.1").unwrap(), String::from("0.4.1"),); assert_eq!(parse_semver("1.0.0").unwrap(), String::from("1.0.0"),); assert!(parse_semver("v1.1").is_none()); } #[test] fn test_utils_parse_color_hex() { assert_eq!( parse_hex_color("#f0f0f0").unwrap(), Color::Rgb(240, 240, 240) ); assert_eq!( parse_hex_color("#60AAcc").unwrap(), Color::Rgb(96, 170, 204) ); assert!(parse_hex_color("#fatboy").is_none()); } #[test] fn test_utils_parse_color_rgb() { assert_eq!( parse_rgb_color("rgb(255, 64, 32)").unwrap(), Color::Rgb(255, 64, 32) ); assert_eq!( parse_rgb_color("rgb(255,64,32)").unwrap(), Color::Rgb(255, 64, 32) ); assert_eq!( parse_rgb_color("(255,64,32)").unwrap(), Color::Rgb(255, 64, 32) ); assert_eq!( parse_rgb_color("255,64,32").unwrap(), Color::Rgb(255, 64, 32) ); assert!(parse_rgb_color("(300, 128, 512)").is_none()); } #[test] fn test_utils_parse_color() { assert_eq!(parse_color("Black").unwrap(), Color::Black); assert_eq!(parse_color("BLUE").unwrap(), Color::Blue); assert_eq!(parse_color("Cyan").unwrap(), Color::Cyan); assert_eq!(parse_color("DarkGray").unwrap(), Color::DarkGray); assert_eq!(parse_color("Gray").unwrap(), Color::Gray); assert_eq!(parse_color("Green").unwrap(), Color::Green); assert_eq!(parse_color("LightBlue").unwrap(), Color::LightBlue); assert_eq!(parse_color("LightCyan").unwrap(), Color::LightCyan); assert_eq!(parse_color("LightGreen").unwrap(), Color::LightGreen); assert_eq!(parse_color("LightMagenta").unwrap(), Color::LightMagenta); assert_eq!(parse_color("LightRed").unwrap(), Color::LightRed); assert_eq!(parse_color("LightYellow").unwrap(), Color::LightYellow); assert_eq!(parse_color("Magenta").unwrap(), Color::Magenta); assert_eq!(parse_color("Red").unwrap(), Color::Red); assert_eq!(parse_color("Default").unwrap(), Color::Reset); assert_eq!(parse_color("White").unwrap(), Color::White); assert_eq!(parse_color("Yellow").unwrap(), Color::Yellow); assert_eq!(parse_color("#f0f0f0").unwrap(), Color::Rgb(240, 240, 240)); // -- css colors assert_eq!(parse_color("aliceblue"), Some(Color::Rgb(240, 248, 255))); assert_eq!(parse_color("antiquewhite"), Some(Color::Rgb(250, 235, 215))); assert_eq!(parse_color("aqua"), Some(Color::Rgb(0, 255, 255))); assert_eq!(parse_color("aquamarine"), Some(Color::Rgb(127, 255, 212))); assert_eq!(parse_color("azure"), Some(Color::Rgb(240, 255, 255))); assert_eq!(parse_color("beige"), Some(Color::Rgb(245, 245, 220))); assert_eq!(parse_color("bisque"), Some(Color::Rgb(255, 228, 196))); assert_eq!( parse_color("blanchedalmond"), Some(Color::Rgb(255, 235, 205)) ); assert_eq!(parse_color("blueviolet"), Some(Color::Rgb(138, 43, 226))); assert_eq!(parse_color("brown"), Some(Color::Rgb(165, 42, 42))); assert_eq!(parse_color("burlywood"), Some(Color::Rgb(222, 184, 135))); assert_eq!(parse_color("cadetblue"), Some(Color::Rgb(95, 158, 160))); assert_eq!(parse_color("chartreuse"), Some(Color::Rgb(127, 255, 0))); assert_eq!(parse_color("chocolate"), Some(Color::Rgb(210, 105, 30))); assert_eq!(parse_color("coral"), Some(Color::Rgb(255, 127, 80))); assert_eq!( parse_color("cornflowerblue"), Some(Color::Rgb(100, 149, 237)) ); assert_eq!(parse_color("cornsilk"), Some(Color::Rgb(255, 248, 220))); assert_eq!(parse_color("crimson"), Some(Color::Rgb(220, 20, 60))); assert_eq!(parse_color("darkblue"), Some(Color::Rgb(0, 0, 139))); assert_eq!(parse_color("darkcyan"), Some(Color::Rgb(0, 139, 139))); assert_eq!(parse_color("darkgoldenrod"), Some(Color::Rgb(184, 134, 11))); assert_eq!(parse_color("darkgreen"), Some(Color::Rgb(0, 100, 0))); assert_eq!(parse_color("darkkhaki"), Some(Color::Rgb(189, 183, 107))); assert_eq!(parse_color("darkmagenta"), Some(Color::Rgb(139, 0, 139))); assert_eq!(parse_color("darkolivegreen"), Some(Color::Rgb(85, 107, 47))); assert_eq!(parse_color("darkorange"), Some(Color::Rgb(255, 140, 0))); assert_eq!(parse_color("darkorchid"), Some(Color::Rgb(153, 50, 204))); assert_eq!(parse_color("darkred"), Some(Color::Rgb(139, 0, 0))); assert_eq!(parse_color("darksalmon"), Some(Color::Rgb(233, 150, 122))); assert_eq!(parse_color("darkseagreen"), Some(Color::Rgb(143, 188, 143))); assert_eq!(parse_color("darkslateblue"), Some(Color::Rgb(72, 61, 139))); assert_eq!(parse_color("darkslategray"), Some(Color::Rgb(47, 79, 79))); assert_eq!(parse_color("darkslategrey"), Some(Color::Rgb(47, 79, 79))); assert_eq!(parse_color("darkturquoise"), Some(Color::Rgb(0, 206, 209))); assert_eq!(parse_color("darkviolet"), Some(Color::Rgb(148, 0, 211))); assert_eq!(parse_color("deeppink"), Some(Color::Rgb(255, 20, 147))); assert_eq!(parse_color("deepskyblue"), Some(Color::Rgb(0, 191, 255))); assert_eq!(parse_color("dimgray"), Some(Color::Rgb(105, 105, 105))); assert_eq!(parse_color("dimgrey"), Some(Color::Rgb(105, 105, 105))); assert_eq!(parse_color("dodgerblue"), Some(Color::Rgb(30, 144, 255))); assert_eq!(parse_color("firebrick"), Some(Color::Rgb(178, 34, 34))); assert_eq!(parse_color("floralwhite"), Some(Color::Rgb(255, 250, 240))); assert_eq!(parse_color("forestgreen"), Some(Color::Rgb(34, 139, 34))); assert_eq!(parse_color("fuchsia"), Some(Color::Rgb(255, 0, 255))); assert_eq!(parse_color("gainsboro"), Some(Color::Rgb(220, 220, 220))); assert_eq!(parse_color("ghostwhite"), Some(Color::Rgb(248, 248, 255))); assert_eq!(parse_color("gold"), Some(Color::Rgb(255, 215, 0))); assert_eq!(parse_color("goldenrod"), Some(Color::Rgb(218, 165, 32))); assert_eq!(parse_color("greenyellow"), Some(Color::Rgb(173, 255, 47))); assert_eq!(parse_color("honeydew"), Some(Color::Rgb(240, 255, 240))); assert_eq!(parse_color("hotpink"), Some(Color::Rgb(255, 105, 180))); assert_eq!(parse_color("indianred"), Some(Color::Rgb(205, 92, 92))); assert_eq!(parse_color("indigo"), Some(Color::Rgb(75, 0, 130))); assert_eq!(parse_color("ivory"), Some(Color::Rgb(255, 255, 240))); assert_eq!(parse_color("khaki"), Some(Color::Rgb(240, 230, 140))); assert_eq!(parse_color("lavender"), Some(Color::Rgb(230, 230, 250))); assert_eq!( parse_color("lavenderblush"), Some(Color::Rgb(255, 240, 245)) ); assert_eq!(parse_color("lawngreen"), Some(Color::Rgb(124, 252, 0))); assert_eq!(parse_color("lemonchiffon"), Some(Color::Rgb(255, 250, 205))); assert_eq!(parse_color("lightcoral"), Some(Color::Rgb(240, 128, 128))); assert_eq!( parse_color("lightgoldenrodyellow"), Some(Color::Rgb(250, 250, 210)) ); assert_eq!(parse_color("lightpink"), Some(Color::Rgb(255, 182, 193))); assert_eq!(parse_color("lightsalmon"), Some(Color::Rgb(255, 160, 122))); assert_eq!(parse_color("lightseagreen"), Some(Color::Rgb(32, 178, 170))); assert_eq!(parse_color("lightskyblue"), Some(Color::Rgb(135, 206, 250))); assert_eq!( parse_color("lightslategray"), Some(Color::Rgb(119, 136, 153)) ); assert_eq!( parse_color("lightslategrey"), Some(Color::Rgb(119, 136, 153)) ); assert_eq!( parse_color("lightsteelblue"), Some(Color::Rgb(176, 196, 222)) ); assert_eq!(parse_color("lime"), Some(Color::Rgb(0, 255, 0))); assert_eq!(parse_color("limegreen"), Some(Color::Rgb(50, 205, 50))); assert_eq!(parse_color("linen"), Some(Color::Rgb(250, 240, 230))); assert_eq!(parse_color("maroon"), Some(Color::Rgb(128, 0, 0))); assert_eq!( parse_color("mediumaquamarine"), Some(Color::Rgb(102, 205, 170)) ); assert_eq!(parse_color("mediumblue"), Some(Color::Rgb(0, 0, 205))); assert_eq!(parse_color("mediumorchid"), Some(Color::Rgb(186, 85, 211))); assert_eq!(parse_color("mediumpurple"), Some(Color::Rgb(147, 112, 219))); assert_eq!( parse_color("mediumseagreen"), Some(Color::Rgb(60, 179, 113)) ); assert_eq!( parse_color("mediumslateblue"), Some(Color::Rgb(123, 104, 238)) ); assert_eq!( parse_color("mediumspringgreen"), Some(Color::Rgb(0, 250, 154)) ); assert_eq!( parse_color("mediumturquoise"), Some(Color::Rgb(72, 209, 204)) ); assert_eq!( parse_color("mediumvioletred"), Some(Color::Rgb(199, 21, 133)) ); assert_eq!(parse_color("midnightblue"), Some(Color::Rgb(25, 25, 112))); assert_eq!(parse_color("mintcream"), Some(Color::Rgb(245, 255, 250))); assert_eq!(parse_color("mistyrose"), Some(Color::Rgb(255, 228, 225))); assert_eq!(parse_color("moccasin"), Some(Color::Rgb(255, 228, 181))); assert_eq!(parse_color("navajowhite"), Some(Color::Rgb(255, 222, 173))); assert_eq!(parse_color("navy"), Some(Color::Rgb(0, 0, 128))); assert_eq!(parse_color("oldlace"), Some(Color::Rgb(253, 245, 230))); assert_eq!(parse_color("olive"), Some(Color::Rgb(128, 128, 0))); assert_eq!(parse_color("olivedrab"), Some(Color::Rgb(107, 142, 35))); assert_eq!(parse_color("orange"), Some(Color::Rgb(255, 165, 0))); assert_eq!(parse_color("orangered"), Some(Color::Rgb(255, 69, 0))); assert_eq!(parse_color("orchid"), Some(Color::Rgb(218, 112, 214))); assert_eq!( parse_color("palegoldenrod"), Some(Color::Rgb(238, 232, 170)) ); assert_eq!(parse_color("palegreen"), Some(Color::Rgb(152, 251, 152))); assert_eq!( parse_color("paleturquoise"), Some(Color::Rgb(175, 238, 238)) ); assert_eq!( parse_color("palevioletred"), Some(Color::Rgb(219, 112, 147)) ); assert_eq!(parse_color("papayawhip"), Some(Color::Rgb(255, 239, 213))); assert_eq!(parse_color("peachpuff"), Some(Color::Rgb(255, 218, 185))); assert_eq!(parse_color("peru"), Some(Color::Rgb(205, 133, 63))); assert_eq!(parse_color("pink"), Some(Color::Rgb(255, 192, 203))); assert_eq!(parse_color("plum"), Some(Color::Rgb(221, 160, 221))); assert_eq!(parse_color("powderblue"), Some(Color::Rgb(176, 224, 230))); assert_eq!(parse_color("purple"), Some(Color::Rgb(128, 0, 128))); assert_eq!(parse_color("rebeccapurple"), Some(Color::Rgb(102, 51, 153))); assert_eq!(parse_color("rosybrown"), Some(Color::Rgb(188, 143, 143))); assert_eq!(parse_color("royalblue"), Some(Color::Rgb(65, 105, 225))); assert_eq!(parse_color("saddlebrown"), Some(Color::Rgb(139, 69, 19))); assert_eq!(parse_color("salmon"), Some(Color::Rgb(250, 128, 114))); assert_eq!(parse_color("sandybrown"), Some(Color::Rgb(244, 164, 96))); assert_eq!(parse_color("seagreen"), Some(Color::Rgb(46, 139, 87))); assert_eq!(parse_color("seashell"), Some(Color::Rgb(255, 245, 238))); assert_eq!(parse_color("sienna"), Some(Color::Rgb(160, 82, 45))); assert_eq!(parse_color("silver"), Some(Color::Rgb(192, 192, 192))); assert_eq!(parse_color("skyblue"), Some(Color::Rgb(135, 206, 235))); assert_eq!(parse_color("slateblue"), Some(Color::Rgb(106, 90, 205))); assert_eq!(parse_color("slategray"), Some(Color::Rgb(112, 128, 144))); assert_eq!(parse_color("slategrey"), Some(Color::Rgb(112, 128, 144))); assert_eq!(parse_color("snow"), Some(Color::Rgb(255, 250, 250))); assert_eq!(parse_color("springgreen"), Some(Color::Rgb(0, 255, 127))); assert_eq!(parse_color("steelblue"), Some(Color::Rgb(70, 130, 180))); assert_eq!(parse_color("tan"), Some(Color::Rgb(210, 180, 140))); assert_eq!(parse_color("teal"), Some(Color::Rgb(0, 128, 128))); assert_eq!(parse_color("thistle"), Some(Color::Rgb(216, 191, 216))); assert_eq!(parse_color("tomato"), Some(Color::Rgb(255, 99, 71))); assert_eq!(parse_color("turquoise"), Some(Color::Rgb(64, 224, 208))); assert_eq!(parse_color("violet"), Some(Color::Rgb(238, 130, 238))); assert_eq!(parse_color("wheat"), Some(Color::Rgb(245, 222, 179))); assert_eq!(parse_color("whitesmoke"), Some(Color::Rgb(245, 245, 245))); assert_eq!(parse_color("yellowgreen"), Some(Color::Rgb(154, 205, 50))); // -- hex and rgb assert_eq!( parse_color("rgb(255, 64, 32)").unwrap(), Color::Rgb(255, 64, 32) ); assert!(parse_color("redd").is_none()); } #[test] fn parse_byteunit() { assert_eq!(ByteUnit::from_str("B").ok().unwrap(), ByteUnit::Byte); assert_eq!(ByteUnit::from_str("KB").ok().unwrap(), ByteUnit::Kilobyte); assert_eq!(ByteUnit::from_str("MB").ok().unwrap(), ByteUnit::Megabyte); assert_eq!(ByteUnit::from_str("GB").ok().unwrap(), ByteUnit::Gigabyte); assert_eq!(ByteUnit::from_str("TB").ok().unwrap(), ByteUnit::Terabyte); assert_eq!(ByteUnit::from_str("PB").ok().unwrap(), ByteUnit::Petabyte); assert!(ByteUnit::from_str("uB").is_err()); } #[test] fn parse_str_as_bytesize() { assert_eq!(parse_bytesize("1024 B").unwrap().as_u64(), 1024); assert_eq!(parse_bytesize("1024B").unwrap().as_u64(), 1024); assert_eq!(parse_bytesize("10240 KB").unwrap().as_u64(), 10485760); assert_eq!(parse_bytesize("2 GB").unwrap().as_u64(), 2147483648); assert_eq!(parse_bytesize("1 TB").unwrap().as_u64(), 1099511627776); assert!(parse_bytesize("1 XB").is_none()); } }