docker_pose/
utils.rs

1use crate::{Formats, Verbosity, get_compose_filename};
2use colored::Colorize;
3use regex::Regex;
4use std::cmp::min;
5use std::ops::Add;
6use std::{fs, process};
7
8/// Get the tag value (or None), or exit if the filter
9/// passed doesn't star with "tag=" prefix.
10pub fn unwrap_filter_tag(filter: Option<&str>) -> Option<&str> {
11    filter.map(|f| {
12        if let Some(val) = f.strip_prefix("tag=") {
13            val
14        } else {
15            eprintln!(
16                "{}: wrong filter '{}', only '{}' filter supported",
17                "ERROR".red(),
18                f.yellow(),
19                "tag=".yellow()
20            );
21            process::exit(2);
22        }
23    })
24}
25
26/// Get the regex value expressed in &str, or exit if the filter
27/// passed doesn't star with "regex=" or "regex!=" prefixes.
28/// When expression has "=" the bool is true, when is "!="
29/// the bool is false.
30/// If the option passed is None, this method returns None as well.
31///
32/// ```
33/// use regex::Regex;
34/// use docker_pose::unwrap_filter_regex;
35///
36/// assert!(unwrap_filter_regex(None).is_none());
37///
38/// let expected_regex = Regex::new("mrsarm/").unwrap();
39/// let filter = unwrap_filter_regex(Some("regex=mrsarm/"));
40///
41/// assert!(filter.is_some());
42/// let filter = filter.unwrap();
43/// assert_eq!(filter.0.as_str(), expected_regex.as_str());
44/// assert_eq!(filter.1, true);
45///
46/// let filter = unwrap_filter_regex(Some("regex!=mrsarm/"));
47///
48/// assert!(filter.is_some());
49/// let filter = filter.unwrap();
50/// assert_eq!(filter.0.as_str(), expected_regex.as_str());
51/// assert_eq!(filter.1, false);
52/// ```
53pub fn unwrap_filter_regex(filter: Option<&str>) -> Option<(Regex, bool)> {
54    filter.as_ref().map(|f| {
55        if let Some(val) = f.strip_prefix("regex=") {
56            let regex = Regex::new(val).unwrap_or_else(|e| {
57                invalid_regex_exit(e, val);
58            });
59            return (regex, true);
60        }
61        if let Some(val) = f.strip_prefix("regex!=") {
62            let regex = Regex::new(val).unwrap_or_else(|e| {
63                invalid_regex_exit(e, val);
64            });
65            return (regex, false);
66        }
67        eprintln!(
68            "{}: wrong filter '{}', only '{}' or '{}' filters are supported",
69            "ERROR".red(),
70            f.yellow(),
71            "regex=".yellow(),
72            "regex!=".yellow()
73        );
74        process::exit(2);
75    })
76}
77
78fn invalid_regex_exit(e: regex::Error, val: &str) -> ! {
79    eprintln!(
80        "{}: invalid regex expression '{}' in filter - {}",
81        "ERROR".red(),
82        val.yellow(),
83        e
84    );
85    process::exit(2);
86}
87
88pub fn print_names<'a>(iter: impl Iterator<Item = &'a str>, pretty: Formats) {
89    match pretty {
90        Formats::Full => iter.for_each(|service| println!("{}", service)),
91        Formats::Oneline => println!(
92            "{}",
93            iter.fold(String::with_capacity(1024), |a, b| {
94                let s = if !a.is_empty() { a.add(" ") } else { a };
95                s.add(b)
96            }),
97        ),
98    }
99}
100
101pub fn print_service_not_found(service_not_found: &str) -> ! {
102    eprintln!(
103        "{}: No such service found: {}",
104        "ERROR".red(),
105        service_not_found.yellow()
106    );
107    process::exit(16);
108}
109
110pub fn print_services_not_found(not_found_list: Vec<String>) -> Vec<String> {
111    eprintln!(
112        "{}: No such service/s found: {}",
113        "ERROR".red(),
114        not_found_list
115            .iter()
116            .map(|s| s.as_str())
117            .collect::<Vec<_>>()
118            .join(" ")
119            .yellow()
120    );
121    process::exit(16);
122}
123
124pub fn get_yml_content(filename: Option<&str>, verbosity: Verbosity) -> String {
125    let filename = get_compose_filename(filename, verbosity).unwrap_or_else(|err| {
126        eprintln!("{}: {}", "ERROR".red(), err);
127        if err.contains("no such file or directory") {
128            process::exit(1);
129        }
130        process::exit(10);
131    });
132    fs::read_to_string(filename).unwrap_or_else(|err| {
133        eprintln!("{}: reading compose file: {}", "ERROR".red(), err);
134        process::exit(11);
135    })
136}
137
138/// Get a slug version of the text compatible with
139/// a tag name to be published in a docker registry, with
140/// only number, letters, the symbol "-" or the symbol ".",
141/// and no more than 63 characters long, all in lowercase.
142///
143/// ```
144/// use docker_pose::get_slug;
145///
146/// assert_eq!(get_slug("some/branch"), "some-branch".to_string());
147/// assert_eq!(get_slug("Yeap!spaces and UpperCase  "), "yeap-spaces-and-uppercase".to_string());
148/// ```
149pub fn get_slug(input: &str) -> String {
150    let text = input.trim();
151    let text = text.to_lowercase();
152    let len = min(text.len(), 63);
153    let mut result = String::with_capacity(len);
154
155    for c in text.chars() {
156        if result.len() >= len {
157            break;
158        }
159        if c.is_ascii_alphanumeric() || c == '.' {
160            result.push(c);
161        } else {
162            result.push('-');
163        }
164    }
165    result
166}