• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

veeso / termscp / 4926442260

pending completion
4926442260

push

github

veeso
rustfmt.toml

5042 of 5376 relevant lines covered (93.79%)

15.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.57
/src/utils/parser.rs
1
//! ## Parser
×
2
//!
3
//! `parser` is the module which provides utilities for parsing different kind of stuff
4

5
// Locals
6
use std::path::PathBuf;
7
use std::str::FromStr;
8

9
// Ext
10
use bytesize::ByteSize;
11
use lazy_regex::{Lazy, Regex};
12
use tuirealm::tui::style::Color;
13
use tuirealm::utils::parser as tuirealm_parser;
14

15
use crate::filetransfer::params::{AwsS3Params, GenericProtocolParams, ProtocolParams};
16
use crate::filetransfer::{FileTransferParams, FileTransferProtocol};
17
#[cfg(not(test))] // NOTE: don't use configuration during tests
18
use crate::system::config_client::ConfigClient;
19
#[cfg(not(test))] // NOTE: don't use configuration during tests
20
use crate::system::environment;
21

22
// Regex
23

24
/**
25
 * This regex matches the protocol used as option
26
 * Regex matches:
27
 * - group 1: Some(protocol) | None
28
 * - group 2: Some(other args)
29
 */
30
static REMOTE_OPT_PROTOCOL_REGEX: Lazy<Regex> = lazy_regex!(r"(?:([a-z0-9]+)://)?(?:(.+))");
2✔
31

32
/**
33
 * Regex matches:
34
 *  - group 1: Some(user) | None
35
 *  - group 2: Address
36
 *  - group 3: Some(port) | None
37
 *  - group 4: Some(path) | None
38
 */
39
static REMOTE_GENERIC_OPT_REGEX: Lazy<Regex> = lazy_regex!(
1✔
40
    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])))?(?::([^:]+))?"
41
);
1✔
42

43
/**
44
 * Regex matches:
45
 * - group 1: Bucket
46
 * - group 2: Region
47
 * - group 3: Some(profile) | None
48
 * - group 4: Some(path) | None
49
 */
50
static REMOTE_S3_OPT_REGEX: Lazy<Regex> =
51
    lazy_regex!(r"(?:([^@]+)@)(?:([^:]+))(?::([a-zA-Z0-9][^:]+))?(?::([^:]+))?");
1✔
52

53
/**
54
 * Regex matches:
55
 * - group 1: Version
56
 * E.g. termscp-0.3.2 => 0.3.2
57
 *      v0.4.0 => 0.4.0
58
 */
59
static SEMVER_REGEX: Lazy<Regex> = lazy_regex!(r".*(:?[0-9]\.[0-9]\.[0-9])");
2✔
60

61
/**
62
 * Regex matches:
63
 * - group 1: amount (number)
64
 * - group 4: unit (K, M, G, T, P)
65
 */
66
static BYTESIZE_REGEX: Lazy<Regex> = lazy_regex!(r"(:?([0-9])+)( )*(:?[KMGTP])?B$");
1✔
67

68
// -- remote opts
69

70
/// Parse remote option string. Returns in case of success a RemoteOptions struct
71
/// For ssh if username is not provided, current user will be used.
72
/// In case of error, message is returned
73
/// If port is missing default port will be used for each protocol
74
///     SFTP => 22
75
///     FTP => 21
76
/// The option string has the following syntax
77
/// [protocol://][username@]{address}[:port][:path]
78
/// The only argument which is mandatory is address
79
/// NOTE: possible strings
80
/// - 172.26.104.1
81
/// - root@172.26.104.1
82
/// - sftp://root@172.26.104.1
83
/// - sftp://172.26.104.1:4022
84
/// - sftp://172.26.104.1
85
/// - ...
86
pub fn parse_remote_opt(s: &str) -> Result<FileTransferParams, String> {
18✔
87
    // Set protocol to default protocol
88
    #[cfg(not(test))] // NOTE: don't use configuration during tests
89
    let default_protocol: FileTransferProtocol = match environment::init_config_dir() {
90
        Ok(p) => match p {
91
            Some(p) => {
92
                // Create config client
93
                let (config_path, ssh_key_path) = environment::get_config_paths(p.as_path());
94
                match ConfigClient::new(config_path.as_path(), ssh_key_path.as_path()) {
95
                    Ok(cli) => cli.get_default_protocol(),
96
                    Err(_) => FileTransferProtocol::Sftp,
97
                }
98
            }
99
            None => FileTransferProtocol::Sftp,
100
        },
101
        Err(_) => FileTransferProtocol::Sftp,
102
    };
103
    #[cfg(test)] // NOTE: during test set protocol just to Sftp
104
    let default_protocol: FileTransferProtocol = FileTransferProtocol::Sftp;
18✔
105
    // Get protocol
106
    let (protocol, s): (FileTransferProtocol, String) =
17✔
107
        parse_remote_opt_protocol(s, default_protocol)?;
18✔
108
    // Match against regex for protocol type
109
    match protocol {
17✔
110
        FileTransferProtocol::AwsS3 => parse_s3_remote_opt(s.as_str()),
5✔
111
        protocol => parse_generic_remote_opt(s.as_str(), protocol),
12✔
112
    }
113
}
18✔
114

115
/// Parse protocol from CLI option. In case of success, return the protocol to be used and the remaining arguments
116
fn parse_remote_opt_protocol(
18✔
117
    s: &str,
118
    default: FileTransferProtocol,
119
) -> Result<(FileTransferProtocol, String), String> {
120
    match REMOTE_OPT_PROTOCOL_REGEX.captures(s) {
18✔
121
        Some(groups) => {
18✔
122
            // Parse protocol or use default
123
            let protocol = groups.get(1).map(|x| {
30✔
124
                FileTransferProtocol::from_str(x.as_str())
24✔
125
                    .map_err(|_| format!("Unknown protocol \"{}\"", x.as_str()))
13✔
126
            });
12✔
127
            let protocol = match protocol {
18✔
128
                Some(Ok(protocol)) => protocol,
11✔
129
                Some(Err(err)) => return Err(err),
1✔
130
                None => default,
6✔
131
            };
132
            // Return protocol and remaining arguments
133
            Ok((
17✔
134
                protocol,
17✔
135
                groups
17✔
136
                    .get(2)
137
                    .map(|x| x.as_str().to_string())
17✔
138
                    .unwrap_or_default(),
139
            ))
140
        }
18✔
141
        None => Err("Invalid args".to_string()),
×
142
    }
143
}
18✔
144

145
/// Parse generic remote options
146
fn parse_generic_remote_opt(
12✔
147
    s: &str,
148
    protocol: FileTransferProtocol,
149
) -> Result<FileTransferParams, String> {
150
    match REMOTE_GENERIC_OPT_REGEX.captures(s) {
12✔
151
        Some(groups) => {
12✔
152
            // Match user
153
            let username: Option<String> = match groups.get(1) {
12✔
154
                Some(group) => Some(group.as_str().to_string()),
5✔
155
                None => match protocol {
7✔
156
                    // If group is empty, set to current user
157
                    FileTransferProtocol::Scp | FileTransferProtocol::Sftp => {
158
                        Some(whoami::username())
6✔
159
                    }
160
                    _ => None,
1✔
161
                },
162
            };
163
            // Get address
164
            let address: String = match groups.get(2) {
12✔
165
                Some(group) => group.as_str().to_string(),
12✔
166
                None => return Err(String::from("Missing address")),
×
167
            };
168
            // Get port
169
            let port: u16 = match groups.get(3) {
12✔
170
                Some(port) => match port.as_str().parse::<u16>() {
5✔
171
                    // Try to parse port
172
                    Ok(p) => p,
4✔
173
                    Err(err) => return Err(format!("Bad port \"{}\": {}", port.as_str(), err)),
1✔
174
                },
4✔
175
                None => match protocol {
7✔
176
                    // Set port based on protocol
177
                    FileTransferProtocol::Ftp(_) => 21,
2✔
178
                    FileTransferProtocol::Scp => 22,
1✔
179
                    FileTransferProtocol::Sftp => 22,
4✔
180
                    _ => 22, // Doesn't matter
×
181
                },
182
            };
183
            // Get workdir
184
            let entry_directory: Option<PathBuf> =
185
                groups.get(4).map(|group| PathBuf::from(group.as_str()));
14✔
186
            let params: ProtocolParams = ProtocolParams::Generic(
11✔
187
                GenericProtocolParams::default()
44✔
188
                    .address(address)
11✔
189
                    .port(port)
11✔
190
                    .username(username),
11✔
191
            );
192
            Ok(FileTransferParams::new(protocol, params).entry_directory(entry_directory))
11✔
193
        }
12✔
194
        None => Err(String::from("Bad remote host syntax!")),
×
195
    }
196
}
12✔
197

198
/// Parse remote options for s3 protocol
199
fn parse_s3_remote_opt(s: &str) -> Result<FileTransferParams, String> {
5✔
200
    match REMOTE_S3_OPT_REGEX.captures(s) {
5✔
201
        Some(groups) => {
4✔
202
            let bucket: String = groups
4✔
203
                .get(1)
204
                .map(|x| x.as_str().to_string())
4✔
205
                .unwrap_or_default();
206
            let region: String = groups
4✔
207
                .get(2)
208
                .map(|x| x.as_str().to_string())
4✔
209
                .unwrap_or_default();
210
            let profile: Option<String> = groups.get(3).map(|x| x.as_str().to_string());
6✔
211
            let entry_directory: Option<PathBuf> =
212
                groups.get(4).map(|group| PathBuf::from(group.as_str()));
6✔
213
            Ok(FileTransferParams::new(
8✔
214
                FileTransferProtocol::AwsS3,
4✔
215
                ProtocolParams::AwsS3(AwsS3Params::new(bucket, Some(region), profile)),
4✔
216
            )
217
            .entry_directory(entry_directory))
4✔
218
        }
4✔
219
        None => Err(String::from("Bad remote host syntax!")),
1✔
220
    }
221
}
5✔
222

223
/// Parse semver string
224
pub fn parse_semver(haystack: &str) -> Option<String> {
6✔
225
    match SEMVER_REGEX.captures(haystack) {
6✔
226
        Some(groups) => groups.get(1).map(|version| version.as_str().to_string()),
6✔
227
        None => None,
3✔
228
    }
229
}
6✔
230

231
/// Parse color from string into a `Color` enum.
232
///
233
/// Color may be in different format:
234
///
235
/// 1. color name:
236
///     - Black,
237
///     - Blue,
238
///     - Cyan,
239
///     - DarkGray,
240
///     - Gray,
241
///     - Green,
242
///     - LightBlue,
243
///     - LightCyan,
244
///     - LightGreen,
245
///     - LightMagenta,
246
///     - LightRed,
247
///     - LightYellow,
248
///     - Magenta,
249
///     - Red,
250
///     - Reset,
251
///     - White,
252
///     - Yellow,
253
/// 2. Hex format:
254
///     - #f0ab05
255
///     - #AA33BC
256
/// 3. Rgb format:
257
///     - rgb(255, 64, 32)
258
///     - rgb(255,64,32)
259
///     - 255, 64, 32
260
pub fn parse_color(color: &str) -> Option<Color> {
129✔
261
    tuirealm_parser::parse_color(color)
129✔
262
}
129✔
263

264
#[derive(Debug, PartialEq)]
6✔
265
enum ByteUnit {
266
    Byte,
267
    Kilobyte,
268
    Megabyte,
269
    Gigabyte,
270
    Terabyte,
271
    Petabyte,
272
}
273

274
impl FromStr for ByteUnit {
275
    type Err = &'static str;
276

277
    fn from_str(s: &str) -> Result<Self, Self::Err> {
12✔
278
        match s {
279
            "B" => Ok(Self::Byte),
12✔
280
            "KB" => Ok(Self::Kilobyte),
9✔
281
            "MB" => Ok(Self::Megabyte),
7✔
282
            "GB" => Ok(Self::Gigabyte),
6✔
283
            "TB" => Ok(Self::Terabyte),
4✔
284
            "PB" => Ok(Self::Petabyte),
2✔
285
            _ => Err("Invalid unit"),
1✔
286
        }
287
    }
12✔
288
}
289

290
/// Parse bytes repr (e.g. `24 MB`) into `ByteSize`
291
pub fn parse_bytesize<S: AsRef<str>>(bytes: S) -> Option<ByteSize> {
9✔
292
    match BYTESIZE_REGEX.captures(bytes.as_ref()) {
9✔
293
        None => None,
4✔
294
        Some(groups) => {
5✔
295
            let amount = groups
5✔
296
                .get(1)
297
                .map(|x| x.as_str().parse::<u64>().unwrap_or(0))?;
14✔
298
            let unit = groups.get(4).map(|x| x.as_str().to_string());
8✔
299
            let unit = format!("{}B", unit.unwrap_or_default());
5✔
300
            let unit = ByteUnit::from_str(unit.as_str()).unwrap();
5✔
301
            Some(match unit {
10✔
302
                ByteUnit::Byte => ByteSize::b(amount),
2✔
303
                ByteUnit::Gigabyte => ByteSize::gib(amount),
1✔
304
                ByteUnit::Kilobyte => ByteSize::kib(amount),
1✔
305
                ByteUnit::Megabyte => ByteSize::mib(amount),
×
306
                ByteUnit::Petabyte => ByteSize::pib(amount),
×
307
                ByteUnit::Terabyte => ByteSize::tib(amount),
1✔
308
            })
309
        }
5✔
310
    }
311
}
9✔
312

313
#[cfg(test)]
314
mod tests {
315

316
    use pretty_assertions::assert_eq;
317

318
    use super::*;
319

320
    #[test]
321
    fn test_utils_parse_remote_opt() {
2✔
322
        // Base case
323
        let result: FileTransferParams = parse_remote_opt(&String::from("172.26.104.1"))
1✔
324
            .ok()
325
            .unwrap();
1✔
326
        let params = result.params.generic_params().unwrap();
1✔
327
        assert_eq!(result.protocol, FileTransferProtocol::Sftp);
1✔
328
        assert_eq!(params.address, String::from("172.26.104.1"));
1✔
329
        assert_eq!(params.port, 22);
1✔
330
        assert!(params.username.is_some());
1✔
331
        // User case
332
        let result: FileTransferParams = parse_remote_opt(&String::from("root@172.26.104.1"))
1✔
333
            .ok()
334
            .unwrap();
1✔
335
        let params = result.params.generic_params().unwrap();
1✔
336
        assert_eq!(result.protocol, FileTransferProtocol::Sftp);
1✔
337
        assert_eq!(params.address, String::from("172.26.104.1"));
1✔
338
        assert_eq!(params.port, 22);
1✔
339
        assert_eq!(
1✔
340
            params.username.as_deref().unwrap().to_string(),
1✔
341
            String::from("root")
1✔
342
        );
343
        assert!(result.entry_directory.is_none());
1✔
344
        // User + port
345
        let result: FileTransferParams = parse_remote_opt(&String::from("root@172.26.104.1:8022"))
1✔
346
            .ok()
347
            .unwrap();
1✔
348
        let params = result.params.generic_params().unwrap();
1✔
349
        assert_eq!(params.address, String::from("172.26.104.1"));
1✔
350
        assert_eq!(params.port, 8022);
1✔
351
        assert_eq!(
1✔
352
            params.username.as_deref().unwrap().to_string(),
1✔
353
            String::from("root")
1✔
354
        );
355
        assert_eq!(result.protocol, FileTransferProtocol::Sftp);
1✔
356
        assert!(result.entry_directory.is_none());
1✔
357
        // Port only
358
        let result: FileTransferParams = parse_remote_opt(&String::from("172.26.104.1:4022"))
1✔
359
            .ok()
360
            .unwrap();
1✔
361
        let params = result.params.generic_params().unwrap();
1✔
362
        assert_eq!(result.protocol, FileTransferProtocol::Sftp);
1✔
363
        assert_eq!(params.address, String::from("172.26.104.1"));
1✔
364
        assert_eq!(params.port, 4022);
1✔
365
        assert!(params.username.is_some());
1✔
366
        assert!(result.entry_directory.is_none());
1✔
367
        // Protocol
368
        let result: FileTransferParams = parse_remote_opt(&String::from("ftp://172.26.104.1"))
1✔
369
            .ok()
370
            .unwrap();
1✔
371
        let params = result.params.generic_params().unwrap();
1✔
372
        assert_eq!(result.protocol, FileTransferProtocol::Ftp(false));
1✔
373
        assert_eq!(params.address, String::from("172.26.104.1"));
1✔
374
        assert_eq!(params.port, 21); // Fallback to ftp default
1✔
375
        assert!(params.username.is_none()); // Doesn't fall back
1✔
376
        assert!(result.entry_directory.is_none());
1✔
377
        // Protocol
378
        let result: FileTransferParams = parse_remote_opt(&String::from("sftp://172.26.104.1"))
1✔
379
            .ok()
380
            .unwrap();
1✔
381
        let params = result.params.generic_params().unwrap();
1✔
382
        assert_eq!(result.protocol, FileTransferProtocol::Sftp);
1✔
383
        assert_eq!(params.address, String::from("172.26.104.1"));
1✔
384
        assert_eq!(params.port, 22); // Fallback to sftp default
1✔
385
        assert!(params.username.is_some()); // Doesn't fall back
1✔
386
        assert!(result.entry_directory.is_none());
1✔
387
        let result: FileTransferParams = parse_remote_opt(&String::from("scp://172.26.104.1"))
1✔
388
            .ok()
389
            .unwrap();
1✔
390
        let params = result.params.generic_params().unwrap();
1✔
391
        assert_eq!(result.protocol, FileTransferProtocol::Scp);
1✔
392
        assert_eq!(params.address, String::from("172.26.104.1"));
1✔
393
        assert_eq!(params.port, 22); // Fallback to scp default
1✔
394
        assert!(params.username.is_some()); // Doesn't fall back
1✔
395
        assert!(result.entry_directory.is_none());
1✔
396
        // Protocol + user
397
        let result: FileTransferParams =
398
            parse_remote_opt(&String::from("ftps://anon@172.26.104.1"))
1✔
399
                .ok()
400
                .unwrap();
1✔
401
        let params = result.params.generic_params().unwrap();
1✔
402
        assert_eq!(result.protocol, FileTransferProtocol::Ftp(true));
1✔
403
        assert_eq!(params.address, String::from("172.26.104.1"));
1✔
404
        assert_eq!(params.port, 21); // Fallback to ftp default
1✔
405
        assert_eq!(
1✔
406
            params.username.as_deref().unwrap().to_string(),
1✔
407
            String::from("anon")
1✔
408
        );
409
        assert!(result.entry_directory.is_none());
1✔
410
        // Path
411
        let result: FileTransferParams =
412
            parse_remote_opt(&String::from("root@172.26.104.1:8022:/var"))
1✔
413
                .ok()
414
                .unwrap();
1✔
415
        let params = result.params.generic_params().unwrap();
1✔
416
        assert_eq!(result.protocol, FileTransferProtocol::Sftp);
1✔
417
        assert_eq!(params.address, String::from("172.26.104.1"));
1✔
418
        assert_eq!(params.port, 8022);
1✔
419
        assert_eq!(
1✔
420
            params.username.as_deref().unwrap().to_string(),
1✔
421
            String::from("root")
1✔
422
        );
423
        assert_eq!(result.entry_directory.unwrap(), PathBuf::from("/var"));
1✔
424
        // Port only
425
        let result: FileTransferParams = parse_remote_opt(&String::from("172.26.104.1:home"))
1✔
426
            .ok()
427
            .unwrap();
1✔
428
        let params = result.params.generic_params().unwrap();
1✔
429
        assert_eq!(result.protocol, FileTransferProtocol::Sftp);
1✔
430
        assert_eq!(params.address, String::from("172.26.104.1"));
1✔
431
        assert_eq!(params.port, 22);
1✔
432
        assert!(params.username.is_some());
1✔
433
        assert_eq!(result.entry_directory.unwrap(), PathBuf::from("home"));
1✔
434
        // All together now
435
        let result: FileTransferParams =
436
            parse_remote_opt(&String::from("ftp://anon@172.26.104.1:8021:/tmp"))
1✔
437
                .ok()
438
                .unwrap();
1✔
439
        let params = result.params.generic_params().unwrap();
1✔
440
        assert_eq!(result.protocol, FileTransferProtocol::Ftp(false));
1✔
441
        assert_eq!(params.address, String::from("172.26.104.1"));
1✔
442
        assert_eq!(params.port, 8021); // Fallback to ftp default
1✔
443
        assert_eq!(
1✔
444
            params.username.as_deref().unwrap().to_string(),
1✔
445
            String::from("anon")
1✔
446
        );
447
        assert_eq!(result.entry_directory.unwrap(), PathBuf::from("/tmp"));
1✔
448
        // bad syntax
449
        // Bad protocol
450
        assert!(parse_remote_opt(&String::from("omar://172.26.104.1")).is_err());
1✔
451
        // Bad port
452
        assert!(parse_remote_opt(&String::from("scp://172.26.104.1:650000")).is_err());
1✔
453
    }
2✔
454

455
    #[test]
456
    fn parse_aws_s3_opt() {
2✔
457
        // Simple
458
        let result: FileTransferParams =
459
            parse_remote_opt(&String::from("s3://mybucket@eu-central-1"))
1✔
460
                .ok()
461
                .unwrap();
1✔
462
        let params = result.params.s3_params().unwrap();
1✔
463
        assert_eq!(result.protocol, FileTransferProtocol::AwsS3);
1✔
464
        assert_eq!(result.entry_directory, None);
1✔
465
        assert_eq!(params.bucket_name.as_str(), "mybucket");
1✔
466
        assert_eq!(params.region.as_deref().unwrap(), "eu-central-1");
1✔
467
        assert_eq!(params.profile, None);
1✔
468
        // With profile
469
        let result: FileTransferParams =
470
            parse_remote_opt(&String::from("s3://mybucket@eu-central-1:default"))
1✔
471
                .ok()
472
                .unwrap();
1✔
473
        let params = result.params.s3_params().unwrap();
1✔
474
        assert_eq!(result.protocol, FileTransferProtocol::AwsS3);
1✔
475
        assert_eq!(result.entry_directory, None);
1✔
476
        assert_eq!(params.bucket_name.as_str(), "mybucket");
1✔
477
        assert_eq!(params.region.as_deref().unwrap(), "eu-central-1");
1✔
478
        assert_eq!(params.profile.as_deref(), Some("default"));
1✔
479
        // With wrkdir only
480
        let result: FileTransferParams =
481
            parse_remote_opt(&String::from("s3://mybucket@eu-central-1:/foobar"))
1✔
482
                .ok()
483
                .unwrap();
1✔
484
        let params = result.params.s3_params().unwrap();
1✔
485
        assert_eq!(result.protocol, FileTransferProtocol::AwsS3);
1✔
486
        assert_eq!(result.entry_directory, Some(PathBuf::from("/foobar")));
1✔
487
        assert_eq!(params.bucket_name.as_str(), "mybucket");
1✔
488
        assert_eq!(params.region.as_deref().unwrap(), "eu-central-1");
1✔
489
        assert_eq!(params.profile, None);
1✔
490
        // With all arguments
491
        let result: FileTransferParams =
492
            parse_remote_opt(&String::from("s3://mybucket@eu-central-1:default:/foobar"))
1✔
493
                .ok()
494
                .unwrap();
1✔
495
        let params = result.params.s3_params().unwrap();
1✔
496
        assert_eq!(result.protocol, FileTransferProtocol::AwsS3);
1✔
497
        assert_eq!(result.entry_directory, Some(PathBuf::from("/foobar")));
1✔
498
        assert_eq!(params.bucket_name.as_str(), "mybucket");
1✔
499
        assert_eq!(params.region.as_deref().unwrap(), "eu-central-1");
1✔
500
        assert_eq!(params.profile.as_deref(), Some("default"));
1✔
501
        // -- bad args
502
        assert!(parse_remote_opt(&String::from("s3://mybucket:default:/foobar")).is_err());
1✔
503
    }
2✔
504

505
    #[test]
506
    fn test_utils_parse_semver() {
2✔
507
        assert_eq!(
1✔
508
            parse_semver("termscp-0.3.2").unwrap(),
1✔
509
            String::from("0.3.2")
1✔
510
        );
511
        assert_eq!(parse_semver("v0.4.1").unwrap(), String::from("0.4.1"),);
1✔
512
        assert_eq!(parse_semver("1.0.0").unwrap(), String::from("1.0.0"),);
1✔
513
        assert!(parse_semver("v1.1").is_none());
1✔
514
    }
2✔
515

516
    #[test]
517
    fn test_utils_parse_color() {
2✔
518
        assert_eq!(parse_color("Black").unwrap(), Color::Black);
1✔
519
        assert_eq!(parse_color("#f0f0f0").unwrap(), Color::Rgb(240, 240, 240));
1✔
520
        // -- css colors
521
        assert_eq!(parse_color("aliceblue"), Some(Color::Rgb(240, 248, 255)));
1✔
522
        // -- hex and rgb
523
        assert_eq!(
1✔
524
            parse_color("rgb(255, 64, 32)").unwrap(),
1✔
525
            Color::Rgb(255, 64, 32)
526
        );
527
        // bad
528
        assert!(parse_color("redd").is_none());
1✔
529
    }
2✔
530

531
    #[test]
532
    fn parse_byteunit() {
2✔
533
        assert_eq!(ByteUnit::from_str("B").ok().unwrap(), ByteUnit::Byte);
1✔
534
        assert_eq!(ByteUnit::from_str("KB").ok().unwrap(), ByteUnit::Kilobyte);
1✔
535
        assert_eq!(ByteUnit::from_str("MB").ok().unwrap(), ByteUnit::Megabyte);
1✔
536
        assert_eq!(ByteUnit::from_str("GB").ok().unwrap(), ByteUnit::Gigabyte);
1✔
537
        assert_eq!(ByteUnit::from_str("TB").ok().unwrap(), ByteUnit::Terabyte);
1✔
538
        assert_eq!(ByteUnit::from_str("PB").ok().unwrap(), ByteUnit::Petabyte);
1✔
539
        assert!(ByteUnit::from_str("uB").is_err());
1✔
540
    }
2✔
541

542
    #[test]
543
    fn parse_str_as_bytesize() {
2✔
544
        assert_eq!(parse_bytesize("1024 B").unwrap().as_u64(), 1024);
1✔
545
        assert_eq!(parse_bytesize("1024B").unwrap().as_u64(), 1024);
1✔
546
        assert_eq!(parse_bytesize("10240 KB").unwrap().as_u64(), 10485760);
1✔
547
        assert_eq!(parse_bytesize("2 GB").unwrap().as_u64(), 2147483648);
1✔
548
        assert_eq!(parse_bytesize("1 TB").unwrap().as_u64(), 1099511627776);
1✔
549
        assert!(parse_bytesize("1 XB").is_none());
1✔
550
        assert!(parse_bytesize("1 GB aaaaa").is_none());
1✔
551
        assert!(parse_bytesize("1 GBaaaaa").is_none());
1✔
552
        assert!(parse_bytesize("1MBaaaaa").is_none());
1✔
553
    }
2✔
554
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc