docker_pose/
utils.rs

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