use std::env::current_dir;
use camino::{Utf8Path, Utf8PathBuf};
use relative_path::{RelativePath, RelativePathBuf};
use tracing::debug;
use crate::{
filesystem::WrapToPath,
next::{config::load_dploy_config, git::git_root_origin_url},
};
use super::{
config::{ConfigAddress, ConfigVar},
errors::{AddressError, StateError, StateErrorKind, WrapStateErr},
fs::get_dirs,
git::get_dir_from_git,
};
#[derive(Debug)]
pub(crate) enum InferContext {
Cwd,
Git,
}
impl Default for InferContext {
fn default() -> Self {
Self::Git
}
}
#[derive(Debug, Clone, Default)]
pub struct LocalAddressIn {
pub resolve_root: Option<String>,
pub state_path: Option<String>,
}
#[derive(Debug, Clone, Default)]
pub struct GitAddressIn {
pub url: Option<String>,
pub local: bool,
pub git_ref: Option<String>,
pub target_resolve_root: Option<String>,
pub state_path: Option<String>,
}
#[derive(Debug, Clone)]
pub enum AddressIn {
Local(LocalAddressIn),
Git(GitAddressIn),
}
impl AddressIn {
pub(crate) fn from_run(resolve_root: Option<String>, state_path: Option<String>) -> Self {
AddressIn::Local(LocalAddressIn {
resolve_root,
state_path,
})
}
pub(crate) fn from_secret(resolve_root: Option<String>, state_path: Option<String>) -> Self {
AddressIn::Local(LocalAddressIn {
resolve_root,
state_path,
})
}
pub(crate) fn from_deploy(
url: Option<String>,
local: bool,
git_ref: Option<String>,
target_resolve_root: Option<String>,
state_path: Option<String>,
) -> Self {
AddressIn::Git(GitAddressIn {
url,
local,
git_ref,
target_resolve_root,
state_path,
})
}
}
pub(crate) fn parse_cli_vars(envs: Vec<String>) -> Vec<ConfigVar> {
envs.chunks_exact(2)
.map(|c| ConfigVar {
key: c.first().unwrap().to_owned(),
env_name: c.get(1).unwrap().to_owned(),
})
.collect()
}
#[derive(Debug, Clone)]
pub(crate) struct Address {
pub(crate) name: String,
pub(crate) root: AddressRoot,
pub(crate) state_path: RelativePathBuf,
}
#[derive(Debug, Clone)]
pub(crate) enum AddressRoot {
Local(Utf8PathBuf),
Git(GitAddress),
}
fn get_current_dir() -> Result<Utf8PathBuf, StateError> {
Utf8PathBuf::from_path_buf(
current_dir().to_state_err("Error getting current directory!".to_owned())?,
)
.map_err(|_e| StateError {
msg: "Current directory is not UTF-8!".to_owned(),
source: StateErrorKind::InvalidPath.into(),
})
}
fn url_local(url: String, local: bool, relative: &Utf8Path) -> String {
if local {
let path = Utf8Path::new(&url);
if path.is_relative() {
RelativePath::new(&url)
.to_utf8_path(relative)
.as_str()
.to_owned()
} else {
url
}
} else {
url
}
}
impl Address {
fn from_config_addr(value: ConfigAddress, resolve_root: &Utf8Path) -> Result<Self, StateError> {
debug!("Converting config_adress {:?} to address!", value);
let addr = match value {
ConfigAddress::Git {
url,
local,
git_ref,
target_path,
state_path,
} => {
let local = local.unwrap_or_default();
let url = url_local(url, local, resolve_root);
let name = parse_url_name(&url).to_state_err("Cannot get name from url.")?;
Address {
name,
root: AddressRoot::Git(GitAddress {
url,
local,
git_ref,
path: RelativePathBuf::from(target_path.unwrap_or_default()),
}),
state_path: RelativePathBuf::from(state_path.unwrap_or_default()),
}
}
ConfigAddress::Local {
path,
state_path,
} => {
let address_root = Utf8PathBuf::from(path.clone());
let address_rel = RelativePathBuf::from_path(&address_root).ok();
let root = if let Some(address_rel) = address_rel {
address_rel.to_utf8_path(resolve_root)
} else {
address_root
};
let name = parse_url_name(root.as_str())
.to_state_err("Error getting name from address root!")?;
Address {
name,
root: AddressRoot::Local(root),
state_path: RelativePathBuf::from(state_path.unwrap_or_default()),
}
}
};
Ok(addr)
}
fn from_addr_in(value: AddressIn, infer_ctx: InferContext) -> Result<Self, StateError> {
debug!("Converting config_adress {:?} to address!", value);
let addr = match value {
AddressIn::Git(GitAddressIn {
url,
local,
git_ref,
state_path,
target_resolve_root,
}) => {
let current_dir = get_current_dir()?;
let url = if let Some(url) = url {
url_local(url, local, ¤t_dir)
} else {
match infer_ctx {
InferContext::Git => git_root_origin_url(¤t_dir)
.to_state_err("Error resolving current Git repository.")?,
InferContext::Cwd => current_dir.to_string(),
}
};
let name = parse_url_name(&url).to_state_err("Cannot get name from url.")?;
Address {
name,
root: AddressRoot::Git(GitAddress {
url,
local,
git_ref: git_ref.unwrap_or_else(|| "HEAD".to_owned()),
path: RelativePathBuf::from(target_resolve_root.unwrap_or_default()),
}),
state_path: RelativePathBuf::from(state_path.unwrap_or_default()),
}
}
AddressIn::Local(LocalAddressIn {
resolve_root,
state_path,
}) => {
let resolve_root = resolve_root.map(Utf8PathBuf::from).unwrap_or_default();
let resolve_root_rel = RelativePathBuf::from_path(&resolve_root).ok();
let resolve_root = match resolve_root_rel {
Some(resolve_root_rel) => {
let current_dir = get_current_dir()?;
let base_dir = match infer_ctx {
InferContext::Cwd => current_dir,
InferContext::Git => {
Utf8PathBuf::from(git_root_origin_url(¤t_dir).to_state_err(
"Error resolving current Git repository.".to_owned(),
)?)
}
};
resolve_root_rel.to_utf8_path(base_dir)
}
None => resolve_root,
};
let name = parse_url_name(resolve_root.as_str())
.to_state_err("Cannot get name from resolve root.")?;
Address {
name,
root: AddressRoot::Local(resolve_root),
state_path: RelativePathBuf::from(state_path.unwrap_or_default()),
}
}
};
debug!("Got address {:?}", addr);
Ok(addr)
}
}
#[derive(Debug, Clone)]
pub(crate) struct GitAddress {
pub(crate) url: String,
pub(crate) local: bool,
pub(crate) git_ref: String,
pub(crate) path: RelativePathBuf,
}
#[derive(Debug, Clone)]
pub(crate) enum StateStep {
None,
Address(Address),
Config,
}
#[derive(Debug, Clone)]
pub(crate) struct State {
pub(crate) name: String,
pub(crate) resolve_root: Utf8PathBuf,
pub(crate) state_path: RelativePathBuf,
pub(crate) step: StateStep, }
impl State {
}
#[derive(Debug)]
pub(crate) struct ResolveState {
pub(crate) resolve_root: Utf8PathBuf,
pub(crate) state_path: RelativePathBuf,
pub(crate) name: String,
pub(crate) sub: String,
pub(crate) hash: String,
}
pub(crate) fn parse_url_name(url: &str) -> Result<String, AddressError> {
let url = url.strip_suffix('/').unwrap_or(url).to_owned();
let split_parts: Vec<&str> = url.split('/').collect();
let last_part = *split_parts
.last()
.ok_or(AddressError::RepoParse(url.to_owned()))?;
let split_parts_dot: Vec<&str> = last_part.split('.').collect();
let name = if split_parts_dot.len() <= 1 {
last_part.to_owned()
} else {
(*split_parts_dot
.first()
.ok_or(AddressError::RepoParse(url.clone()))?)
.to_owned()
};
Ok(name)
}
fn resolve_address(address: Address, store_dir: &Utf8Path) -> Result<State, StateError> {
debug!("Resolving address {:?}", address);
let Address {
name,
state_path,
root,
} = address;
match root {
AddressRoot::Git(addr) => get_dir_from_git(addr, state_path, store_dir),
AddressRoot::Local(path) => Ok(State {
name,
resolve_root: path,
state_path,
step: StateStep::Config,
}),
}
}
pub(crate) struct StateOptions {
pub(crate) store_dir: Utf8PathBuf,
}
impl Default for StateOptions {
fn default() -> Self {
Self {
store_dir: get_dirs().cache.clone(),
}
}
}
pub(crate) fn converge_state(mut state: State, opt: StateOptions) -> Result<State, StateError> {
loop {
match state.step {
StateStep::None => break,
StateStep::Address(address) => state = resolve_address(address, &opt.store_dir)?,
StateStep::Config => {
let config_dir = state.state_path.to_utf8_path(&state.resolve_root);
let config = load_dploy_config(&config_dir)
.to_state_err("Failed to load config.")?
.state;
let addr = config.and_then(|c| c.address);
match addr {
Some(addr) => {
state.step =
StateStep::Address(Address::from_config_addr(addr, &config_dir)?)
}
None => state.step = StateStep::None,
}
}
}
}
Ok(state)
}
pub(crate) fn create_resolve_state(
addr_in: AddressIn,
infer_ctx: InferContext,
opt: StateOptions,
) -> Result<ResolveState, StateError> {
let address = Address::from_addr_in(addr_in, infer_ctx)?;
let state = resolve_address(address, &opt.store_dir)?;
let state = converge_state(state, opt)?;
let resolve_state = ResolveState {
resolve_root: state.resolve_root,
state_path: state.state_path,
name: state.name,
sub: "tidploy_root".to_owned(),
hash: "todo_hash".to_owned(),
};
debug!("Created resolve state as {:?}", resolve_state);
Ok(resolve_state)
}