1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
//  SPEC.rs
//    by Lut99
//
//  Created:
//    28 Nov 2022, 15:56:23
//  Last edited:
//    07 Nov 2023, 16:29:39
//  Auto updated?
//    Yes
//
//  Description:
//!   Defines (public) interfaces and structs in the `brane-cli` crate.
//

use std::collections::HashMap;
use std::fmt::{Debug, Display, Formatter, Result as FResult};
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;

use brane_exe::spec::CustomGlobalState;
use brane_tsk::docker::DockerOptions;
use parking_lot::Mutex;
use specifications::data::DataIndex;
use specifications::package::PackageIndex;
use specifications::version::Version;

use crate::errors::HostnameParseError;


/***** STATICS *****/
lazy_static::lazy_static! {
    /// The default Docker API version that we're using.
    pub static ref API_DEFAULT_VERSION: String = format!("{}", brane_tsk::docker::API_DEFAULT_VERSION);
}





/***** LIBRARY *****/
/// An auxillary struct that defines a hostname-only argument, optionally with some scheme.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct Hostname {
    /// The name of the host
    pub hostname: String,
    /// The scheme, if any.
    pub scheme:   Option<String>,
}

impl Hostname {
    /// Constructor for the Hostname that creates it without any scheme.
    ///
    /// # Arguments
    /// - `hostname`: The hostname of the host to store in this struct.
    ///
    /// # Returns
    /// A new Hostname instance.
    #[inline]
    pub fn new(hostname: impl Into<String>) -> Self { Self { hostname: hostname.into(), scheme: None } }

    /// Contsructor for the Hostname that creates it with the given hostname and scheme set.
    ///
    /// # Arguments
    /// - `hostname`: The hostname of the host to store in this struct.
    /// - `scheme`: The scheme to connect to the host to.
    ///
    /// # Returns
    /// A new Hostname instance.
    #[inline]
    pub fn with_scheme(hostname: impl Into<String>, scheme: impl Into<String>) -> Self {
        Self { hostname: hostname.into(), scheme: Some(scheme.into()) }
    }
}

impl Display for Hostname {
    #[inline]
    fn fmt(&self, f: &mut Formatter<'_>) -> FResult {
        match &self.scheme {
            Some(scheme) => write!(f, "{}://{}", scheme, self.hostname),
            None => write!(f, "{}", self.hostname),
        }
    }
}
impl FromStr for Hostname {
    type Err = HostnameParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        // See if we can split the thing into a scheme and a non-scheme part
        let scheme_sep_pos: Option<usize> = s.find("://");
        let (scheme, hostname): (Option<String>, &str) = if let Some(scheme_sep_pos) = scheme_sep_pos {
            // Split into the scheme and non-scheme
            let scheme: &str = &s[..scheme_sep_pos];
            let host: &str = &s[scheme_sep_pos + 3..];

            // Assert the scheme only has alphanumeric characters
            for c in scheme.chars() {
                if !c.is_ascii_digit() && !c.is_ascii_lowercase() && !c.is_ascii_uppercase() {
                    return Err(HostnameParseError::IllegalSchemeChar { raw: scheme.into(), c });
                }
            }

            // Return them
            (Some(scheme.into()), host)
        } else {
            (None, s)
        };

        // Assert the host has no paths in it
        if hostname.find('/').is_some() {
            return Err(HostnameParseError::HostnameContainsPath { raw: hostname.into() });
        }

        // Alright good enough for now
        Ok(Self { hostname: hostname.into(), scheme })
    }
}



/// Parses a version number that scopes a particular operation down. In other words, can be a specific version number or `all`.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct VersionFix(pub Option<Version>);
impl Display for VersionFix {
    #[inline]
    fn fmt(&self, f: &mut Formatter<'_>) -> FResult { write!(f, "{}", if let Some(version) = self.0 { version.to_string() } else { "all".into() }) }
}
impl FromStr for VersionFix {
    type Err = specifications::version::ParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        // Parse the auto first
        if s == "all" {
            return Ok(Self(None));
        }
        // Otherwise, delegate to the version parser
        Ok(Self(Some(Version::from_str(s)?)))
    }
}



/// The global state for the OfflineVm.
#[derive(Clone, Debug)]
pub struct GlobalState {
    /// The information we want to know for Docker
    pub docker_opts:     DockerOptions,
    /// Whether to keep containers after execution or not
    pub keep_containers: bool,

    /// The path to the directory where packages (and thus container images) are stored for this session.
    pub package_dir: PathBuf,
    /// The path to the directory where datasets (where we wanna copy results) are stored for this session.
    pub dataset_dir: PathBuf,
    /// The path to the directory where intermediate results will be stored for this session.
    pub results_dir: PathBuf,

    /// The package index that contains info about each package.
    pub pindex:  Arc<PackageIndex>,
    /// The data index that contains info about each package.
    pub dindex:  Arc<DataIndex>,
    /// A list of results we planned in the previous timestep.
    pub results: Arc<Mutex<HashMap<String, String>>>,
}
impl CustomGlobalState for GlobalState {}

/// The local state for the OfflineVm is unused.
pub type LocalState = ();