use std::cell::{Cell, UnsafeCell};
use std::str::FromStr;
use better_default::Default;
use indextree::{Arena, NodeId};
use is_terminal::IsTerminal;
use strum::VariantNames;
use strum_macros;
use which::which;
use crate::cli::GardenOptions;
use crate::{cli, collections, config, constants, errors, eval, path, syntax};
pub(crate) type IndexMap<K, V> = indexmap::IndexMap<K, V>;
pub(crate) type IndexSet<V> = indexmap::IndexSet<V>;
pub type StringSet = indexmap::IndexSet<String>;
pub type TreeName = String;
pub type GroupName = String;
pub type GardenName = String;
pub type GraftName = String;
pub type ConfigId = NodeId;
pub(crate) type Environment = Vec<(String, String)>;
#[derive(Debug, Default)]
pub struct Variable {
expr: String,
value: UnsafeCell<Option<String>>,
evaluating: Cell<bool>,
}
impl_display_brief!(Variable);
impl Clone for Variable {
fn clone(&self) -> Self {
Self {
expr: self.expr.clone(),
value: UnsafeCell::new(self.get_value().cloned()),
evaluating: Cell::new(false),
}
}
}
impl Variable {
pub(crate) fn new(expr: String, value: Option<String>) -> Self {
Variable {
expr,
value: UnsafeCell::new(value),
evaluating: Cell::new(false),
}
}
pub(crate) fn is_empty(&self) -> bool {
self.expr.is_empty()
}
pub(crate) fn is_evaluating(&self) -> bool {
self.evaluating.get()
}
pub(crate) fn set_evaluating(&self, value: bool) {
self.evaluating.set(value);
}
pub fn get_expr(&self) -> &String {
&self.expr
}
pub(crate) fn get_expr_mut(&mut self) -> &mut String {
&mut self.expr
}
pub(crate) fn set_expr(&mut self, expr: String) {
self.expr = expr;
}
pub(crate) fn set_value(&self, value: String) {
unsafe {
*self.value.get() = Some(value);
}
}
pub fn get_value(&self) -> Option<&String> {
unsafe { (*self.value.get()).as_ref() }
}
pub(crate) fn reset(&self) {
unsafe {
*self.value.get() = None;
}
}
}
pub(crate) type MultiVariableMap = IndexMap<String, Vec<Variable>>;
fn reset_map_variables(vec_variables: &MultiVariableMap) {
for variables in vec_variables.values() {
for variable in variables {
variable.reset();
}
}
}
pub(crate) type VariableMap = IndexMap<String, Variable>;
#[derive(Clone, Debug)]
pub struct MultiVariable {
name: String,
variables: Vec<Variable>,
}
impl MultiVariable {
pub(crate) fn new(name: String, variables: Vec<Variable>) -> Self {
MultiVariable { name, variables }
}
pub fn get(&self, idx: usize) -> &Variable {
&self.variables[idx]
}
pub fn get_name(&self) -> &String {
&self.name
}
pub fn len(&self) -> usize {
self.variables.len()
}
pub fn is_empty(&self) -> bool {
self.variables.is_empty()
}
pub fn reset(&self) {
for var in &self.variables {
var.reset();
}
}
pub fn iter(&self) -> std::slice::Iter<Variable> {
self.variables.iter()
}
}
#[derive(Clone, Debug, Default)]
pub struct Tree {
pub commands: MultiVariableMap,
pub environment: Vec<MultiVariable>,
pub gitconfig: MultiVariableMap,
pub remotes: VariableMap,
pub(crate) symlink: Variable,
pub templates: StringSet,
pub variables: VariableMap,
pub branch: Variable,
pub(crate) branches: VariableMap,
pub worktree: Variable,
#[default(string!("origin"))]
pub(crate) default_remote: String,
pub(crate) clone_depth: i64,
pub(crate) is_single_branch: bool,
pub is_symlink: bool,
pub is_bare_repository: bool,
pub is_worktree: bool,
pub(crate) description: String,
pub(crate) links: Vec<Variable>,
name: String,
path: Variable,
}
impl Tree {
pub fn get_name(&self) -> &String {
&self.name
}
pub fn set_name(&mut self, name: String) {
self.name = name;
}
pub(crate) fn get_name_mut(&mut self) -> &mut String {
&mut self.name
}
pub fn get_path(&self) -> &Variable {
&self.path
}
pub(crate) fn get_path_mut(&mut self) -> &mut Variable {
&mut self.path
}
pub(crate) fn path_is_valid(&self) -> bool {
self.path.get_value().is_some()
}
pub(crate) fn canonical_pathbuf(&self) -> Option<std::path::PathBuf> {
if let Some(pathbuf) = self.pathbuf() {
if let Ok(canon_path) = path::canonicalize(pathbuf) {
return Some(canon_path);
}
}
None
}
pub(crate) fn pathbuf(&self) -> Option<std::path::PathBuf> {
if !self.path_is_valid() {
return None;
}
self.path.get_value().map(std::path::PathBuf::from)
}
pub fn path_as_ref(&self) -> Result<&String, errors::GardenError> {
match self.path.get_value() {
Some(value) => Ok(value),
None => Err(errors::GardenError::ConfigurationError(format!(
"unset tree path for {}",
self.name
))),
}
}
pub(crate) fn symlink_as_ref(&self) -> Result<&String, errors::GardenError> {
match self.symlink.get_value() {
Some(value) => Ok(value),
None => Err(errors::GardenError::ConfigurationError(format!(
"unset tree path for {}",
self.name
))),
}
}
pub(crate) fn add_builtin_variables(&mut self) {
self.variables.insert(
string!(constants::TREE_NAME),
Variable::new(self.get_name().clone(), None),
);
self.variables.insert(
string!(constants::TREE_PATH),
Variable::new(self.get_path().get_expr().clone(), None),
);
}
pub(crate) fn reset_variables(&self) {
for var in self.variables.values() {
var.reset();
}
for env in &self.environment {
env.reset();
}
reset_map_variables(&self.gitconfig);
reset_map_variables(&self.commands);
}
pub(crate) fn clone_from_tree(&mut self, tree: &Tree) {
collections::append_map(&mut self.commands, &tree.commands);
collections::append_map(&mut self.gitconfig, &tree.gitconfig);
collections::append_map(&mut self.variables, &tree.variables);
collections::append_map(&mut self.remotes, &tree.remotes);
collections::append_set(&mut self.templates, &tree.templates);
self.environment.append(&mut tree.environment.clone());
if tree.clone_depth > 0 {
self.clone_depth = tree.clone_depth;
}
if tree.is_bare_repository {
self.is_bare_repository = tree.is_bare_repository;
}
if tree.is_single_branch {
self.is_single_branch = tree.is_single_branch;
}
if tree.is_worktree {
self.is_worktree = tree.is_worktree;
}
if tree.is_symlink {
self.is_symlink = tree.is_symlink;
}
if !tree.branch.is_empty() {
self.branch = tree.branch.clone();
}
if !tree.symlink.is_empty() {
self.symlink = tree.symlink.clone();
}
if !tree.worktree.is_empty() {
self.worktree = tree.worktree.clone();
}
self.default_remote = tree.default_remote.to_string();
self.description = tree.description.to_string();
self.links.clone_from(&tree.links);
self.update_flags();
}
pub(crate) fn update_flags(&mut self) {
if !self.symlink.is_empty() {
self.is_symlink = true;
}
if !self.worktree.is_empty() {
self.is_worktree = true;
}
if self.is_worktree {
self.is_bare_repository = false;
}
}
pub(crate) fn eval_branch(&self, eval_context: &EvalContext) -> String {
self.get_branch(
eval_context.app_context,
eval_context.config,
eval_context.graft_config,
eval_context.tree_context,
)
}
pub(crate) fn get_branch(
&self,
app_context: &ApplicationContext,
config: &Configuration,
graft_config: Option<&Configuration>,
tree_context: &TreeContext,
) -> String {
eval::tree_variable(
app_context,
config,
graft_config,
&tree_context.tree,
tree_context.garden.as_ref(),
&self.branch,
)
}
pub(crate) fn eval_url(&self, eval_context: &EvalContext) -> Option<String> {
self.get_url(
eval_context.app_context,
eval_context.config,
eval_context.graft_config,
eval_context.tree_context,
)
}
pub(crate) fn get_url(
&self,
app_context: &ApplicationContext,
config: &Configuration,
graft_config: Option<&Configuration>,
context: &TreeContext,
) -> Option<String> {
self.remotes.get(&self.default_remote).map(|remote| {
eval::tree_variable(
app_context,
config,
graft_config,
&context.tree,
context.garden.as_ref(),
remote,
)
})
}
pub(crate) fn eval_worktree(&self, eval_context: &EvalContext) -> String {
self.get_worktree(
eval_context.app_context,
eval_context.config,
eval_context.graft_config,
eval_context.tree_context,
)
}
pub(crate) fn get_worktree(
&self,
app_context: &ApplicationContext,
config: &Configuration,
graft_config: Option<&Configuration>,
tree_context: &TreeContext,
) -> String {
eval::tree_variable(
app_context,
config,
graft_config,
&tree_context.tree,
tree_context.garden.as_ref(),
&self.worktree,
)
}
pub(crate) fn get_remote_for_branch(
&self,
eval_context: &EvalContext,
branch: &str,
) -> Option<String> {
let remote_branch = self.get_upstream_branch(eval_context, branch)?;
let remote = remote_branch.split_once('/')?.0;
if self.remotes.contains_key(remote) {
Some(remote.to_string())
} else {
None
}
}
pub(crate) fn get_upstream_branch(
&self,
eval_context: &EvalContext,
branch: &str,
) -> Option<String> {
if branch.is_empty() {
return None;
}
self.branches
.get(branch)
.map(|remote_branch_var| eval_context.tree_variable(remote_branch_var))
}
}
#[derive(Clone, Debug, Default)]
pub struct Group {
name: String,
pub members: StringSet,
}
impl Group {
pub fn get_name(&self) -> &String {
&self.name
}
pub(crate) fn get_name_owned(&self) -> String {
self.get_name().to_owned()
}
pub(crate) fn get_name_mut(&mut self) -> &mut String {
&mut self.name
}
}
pub type GroupMap = IndexMap<GroupName, Group>;
#[derive(Clone, Debug, Default)]
pub struct Template {
pub tree: Tree,
pub extend: StringSet,
name: String,
}
impl Template {
pub fn get_name(&self) -> &String {
&self.name
}
pub(crate) fn get_name_mut(&mut self) -> &mut String {
&mut self.name
}
pub(crate) fn apply(&self, tree: &mut Tree) {
tree.clone_from_tree(&self.tree);
}
}
#[derive(Clone, Debug, Default)]
pub struct Garden {
pub commands: MultiVariableMap,
pub environment: Vec<MultiVariable>,
pub gitconfig: MultiVariableMap,
pub groups: StringSet,
pub trees: StringSet,
pub variables: VariableMap,
name: GardenName,
}
impl Garden {
pub fn get_name(&self) -> &GardenName {
&self.name
}
pub(crate) fn get_name_mut(&mut self) -> &mut String {
&mut self.name
}
}
pub type GardenMap = IndexMap<GardenName, Garden>;
fn get_default_shell() -> String {
if which(constants::SHELL_ZSH).is_ok() {
constants::SHELL_ZSH
} else if which(constants::SHELL_BASH).is_ok() {
constants::SHELL_BASH
} else if which(constants::SHELL_DASH).is_ok() {
constants::SHELL_DASH
} else {
constants::SHELL_SH
}
.to_string()
}
#[derive(Clone, Debug, Default)]
pub struct Configuration {
pub commands: MultiVariableMap,
pub debug: IndexMap<String, u8>,
pub environment: Vec<MultiVariable>,
pub gardens: GardenMap,
pub grafts: IndexMap<GraftName, Graft>,
pub groups: GroupMap,
pub path: Option<std::path::PathBuf>,
pub dirname: Option<std::path::PathBuf>,
pub root: Variable,
pub root_is_dynamic: bool,
pub root_path: std::path::PathBuf,
pub shell: String,
pub interactive_shell: String,
pub templates: IndexMap<String, Template>,
pub tree_search_path: Vec<std::path::PathBuf>,
pub trees: IndexMap<TreeName, Tree>,
pub variables: VariableMap,
pub override_variables: VariableMap,
pub config_verbose: u8,
pub quiet: bool,
pub verbose: u8,
pub(crate) shell_exit_on_error: bool,
pub(crate) shell_word_split: bool,
pub(crate) tree_branches: bool,
pub(crate) parent_id: Option<ConfigId>,
id: Option<ConfigId>,
}
impl_display!(Configuration);
impl Configuration {
pub fn new() -> Self {
Configuration {
id: None,
parent_id: None,
shell: get_default_shell(),
shell_exit_on_error: true,
shell_word_split: true,
tree_branches: true,
..std::default::Default::default()
}
}
pub(crate) fn initialize(&mut self, app_context: &ApplicationContext) {
let expr = self.root.get_expr().to_string();
let mut value = eval::value(app_context, self, &expr);
if expr.is_empty() {
if self.root_is_dynamic {
let current_dir = path::current_dir_string();
self.root.set_expr(current_dir.clone());
self.root.set_value(current_dir);
self.root_path = path::current_dir();
} else {
self.root
.set_expr(string!(constants::GARDEN_CONFIG_DIR_EXPR));
if let Some(ref dirname) = self.dirname {
self.root.set_value(dirname.to_string_lossy().to_string());
self.root_path = dirname.to_path_buf();
}
}
} else {
self.root_path = std::path::PathBuf::from(&value);
if let Ok(root_path_canon) = path::canonicalize(&self.root_path) {
if root_path_canon != self.root_path {
value = root_path_canon
.to_str()
.unwrap_or(value.as_str())
.to_string();
self.root_path = root_path_canon;
}
}
self.root.set_value(value);
}
self.update_tree_paths(app_context); self.synthesize_default_tree(); self.reset();
}
pub(crate) fn graft_id(&self) -> Option<NodeId> {
self.parent_id.and(self.get_id())
}
pub(crate) fn update(
&mut self,
app_context: &ApplicationContext,
config: Option<&std::path::Path>,
root: Option<&std::path::Path>,
config_verbose: u8,
parent: Option<ConfigId>,
) -> Result<(), errors::GardenError> {
if let Some(parent_id) = parent {
self.set_parent(parent_id);
}
self.config_verbose = config_verbose;
self.quiet = app_context.options.quiet;
self.verbose = app_context.options.verbose;
let root_pathbuf_option = root.map(path::abspath);
if let Some(root_path) = root_pathbuf_option {
self.root.set_expr(root_path.to_string_lossy().to_string());
}
let mut basename = string!(constants::GARDEN_CONFIG);
let mut found = false;
if let Some(config_path) = config {
if config_path.is_file() || config_path.is_absolute() {
self.set_path(&config_path);
found = true;
} else {
basename = config_path.to_string_lossy().into();
}
}
if !found {
for entry in config::search_path() {
let mut candidate = entry.to_path_buf();
candidate.push(basename.clone());
if candidate.exists() {
self.set_path(&candidate);
found = true;
break;
}
}
}
if config_verbose > 0 {
debug!(
"config: path: {:?}, root: {:?}, found: {}",
self.path, self.root, found
);
}
if found {
let config_path = self.get_path()?;
if let Ok(config_string) = std::fs::read_to_string(config_path) {
config::parse(app_context, &config_string, config_verbose, self)?;
}
}
Ok(())
}
pub(crate) fn update_options(
&mut self,
options: &cli::MainOptions,
) -> Result<(), errors::GardenError> {
let config_verbose = options.debug_level(constants::DEBUG_LEVEL_CONFIG);
if self.path.is_none() {
error!("unable to find a configuration file -- use --config <path>");
}
if config_verbose > 1 {
eprintln!("config: {:?}", self.get_path()?);
}
if config_verbose > 2 {
debug!("{}", self);
}
for key in &options.debug {
let current = *self.debug.get(key).unwrap_or(&0);
self.debug.insert(key.into(), current + 1);
}
self.apply_defines(&options.define);
Ok(())
}
pub(crate) fn apply_defines(&mut self, defines: &Vec<String>) {
for k_eq_v in defines {
let name: String;
let expr: String;
let values: Vec<&str> = k_eq_v.splitn(2, '=').collect();
if values.len() == 1 {
name = values[0].to_string();
expr = string!("");
} else if values.len() == 2 {
name = values[0].to_string();
expr = values[1].to_string();
} else {
error!("unable to split '{}'", k_eq_v);
}
match name.as_str() {
constants::GARDEN_INTERACTIVE_SHELL => {
self.interactive_shell = expr;
}
constants::GARDEN_SHELL => {
self.shell = expr;
}
constants::GARDEN_SHELL_ERREXIT => {
set_bool(name.as_str(), &expr, &mut self.shell_exit_on_error);
}
constants::GARDEN_SHELL_WORDSPLIT => {
set_bool(name.as_str(), &expr, &mut self.shell_word_split);
}
constants::GARDEN_TREE_BRANCHES => {
set_bool(name.as_str(), &expr, &mut self.tree_branches);
}
_ => {
self.override_variables
.insert(name, Variable::new(expr, None));
}
}
}
}
pub(crate) fn update_quiet_and_verbose_variables(&mut self, quiet: bool, verbose: u8) {
let quiet_string = if self.quiet || quiet { "--quiet" } else { "" }.to_string();
self.variables.insert(
string!(constants::GARDEN_CMD_QUIET),
Variable::new(quiet_string.clone(), Some(quiet_string)),
);
let verbose = self.verbose + verbose;
let verbose_string = if verbose > 0 {
format!("-{}", "v".repeat(verbose.into()))
} else {
string!("")
};
self.variables.insert(
string!(constants::GARDEN_CMD_VERBOSE),
Variable::new(verbose_string.clone(), Some(verbose_string)),
);
}
pub(crate) fn reset(&mut self) {
self.reset_variables();
self.reset_builtin_variables()
}
fn reset_builtin_variables(&mut self) {
if let Some(var) = self.variables.get_mut(constants::GARDEN_ROOT) {
if let Some(value) = self.root.get_value() {
var.set_expr(value.into());
var.set_value(value.into());
}
}
for tree in self.trees.values_mut() {
let tree_name = String::from(tree.get_name());
if let Some(var) = tree.variables.get_mut(constants::TREE_NAME) {
var.set_expr(tree_name.to_string());
var.set_value(tree_name);
}
let tree_path = match tree.path_as_ref() {
Ok(path) => String::from(path),
Err(_) => continue,
};
if let Some(var) = tree.variables.get_mut(constants::TREE_PATH) {
var.set_expr(tree_path.to_string());
var.set_value(tree_path);
}
}
}
fn update_tree_paths(&mut self, app_context: &ApplicationContext) {
let mut path_values = Vec::new();
let mut symlink_values = Vec::new();
for (name, tree) in &self.trees {
path_values.push((name.clone(), tree.path.get_expr().clone()));
if tree.is_symlink {
symlink_values.push((name.clone(), tree.symlink.get_expr().clone()));
}
}
for (name, value) in &path_values {
let result = self.eval_tree_path(app_context, value);
if let Some(tree) = self.trees.get_mut(name) {
tree.path.set_value(result);
}
}
for (name, value) in &symlink_values {
let result = self.eval_tree_path(app_context, value);
if let Some(tree) = self.trees.get_mut(name) {
tree.symlink.set_value(result);
}
}
}
fn synthesize_default_tree(&mut self) {
if !self.commands.is_empty() && self.trees.is_empty() {
let dirname_string = self.dirname_string();
let mut tree = Tree::default();
tree.path.set_expr(dirname_string.clone());
tree.path.set_value(dirname_string);
tree.description = string!("The default tree for garden commands.");
tree.add_builtin_variables();
tree.set_name(string!(constants::DOT));
self.trees.insert(string!(constants::DOT), tree);
}
}
pub(crate) fn tree_path(&self, path: &str) -> String {
if std::path::PathBuf::from(path).is_absolute() {
path.into()
} else {
let mut path_buf = self.root_path.to_path_buf();
path_buf.push(path);
path_buf.to_string_lossy().into()
}
}
pub(crate) fn relative_pathbuf(&self, path: &str) -> std::path::PathBuf {
let pathbuf = std::path::PathBuf::from(path);
if pathbuf.is_absolute() {
if let Ok(pathbuf_canon) = path::canonicalize(&pathbuf) {
pathbuf_canon
} else {
pathbuf
}
} else {
let mut path_buf = self.root_path.to_path_buf();
path_buf.push(path);
path_buf
}
}
fn eval_tree_path(&mut self, app_context: &ApplicationContext, path: &str) -> String {
let value = eval::value(app_context, self, path);
self.tree_path(&value)
}
pub(crate) fn config_pathbuf(&self, path: &str) -> Option<std::path::PathBuf> {
let path_buf = std::path::PathBuf::from(path);
if path_buf.is_absolute() {
Some(path_buf)
} else if let Some(dirname) = self.dirname.as_ref() {
let mut abs_path_buf = dirname.to_path_buf();
abs_path_buf.push(path_buf);
Some(abs_path_buf)
} else {
None
}
}
fn config_pathbuf_from_include(
&self,
include_path: &std::path::Path,
path: &str,
) -> Option<std::path::PathBuf> {
let mut path_buf = std::path::PathBuf::from(path);
if path_buf.is_absolute() {
return Some(path_buf);
}
if let Some(dirname) = include_path.parent() {
path_buf = dirname.to_path_buf();
path_buf.push(path);
if path_buf.exists() {
return Some(path_buf);
}
}
self.config_pathbuf(path)
}
fn config_path(&self, path: &str) -> String {
if let Some(path_buf) = self.config_pathbuf(path) {
path_buf.to_string_lossy().to_string()
} else {
self.tree_path(path)
}
}
fn dirname_string(&self) -> String {
match self.dirname {
Some(ref dirname) => dirname.to_string_lossy().to_string(),
None => path::current_dir_string(),
}
}
pub(crate) fn fallback_execdir_string(&self) -> String {
if self.root_path.exists() {
return self.root_path.to_string_lossy().to_string();
}
if let Some(dirname) = self.dirname.as_ref() {
if dirname.exists() {
return dirname.to_string_lossy().to_string();
}
}
path::current_dir_string()
}
pub(crate) fn eval_config_path(&self, app_context: &ApplicationContext, path: &str) -> String {
let value = eval::value(app_context, self, path);
self.config_path(&value)
}
pub(crate) fn eval_config_pathbuf_from_include(
&self,
app_context: &ApplicationContext,
include_path: Option<&std::path::Path>,
path: &str,
) -> Option<std::path::PathBuf> {
let value = eval::value(app_context, self, path);
if let Some(include_path) = include_path {
self.config_pathbuf_from_include(include_path, &value)
} else {
self.config_pathbuf(&value)
}
.or_else(|| Some(std::path::PathBuf::from(&value)))
}
pub(crate) fn reset_variables(&mut self) {
for var in self.variables.values() {
var.reset();
}
for env in &self.environment {
env.reset();
}
reset_map_variables(&self.commands);
for tree in self.trees.values() {
tree.reset_variables();
}
}
pub(crate) fn set_id(&mut self, id: ConfigId) {
self.id = Some(id);
}
pub(crate) fn get_id(&self) -> Option<ConfigId> {
self.id
}
pub(crate) fn set_parent(&mut self, id: ConfigId) {
self.parent_id = Some(id);
}
pub(crate) fn set_path(&mut self, path: &dyn AsRef<std::path::Path>) {
let config_path = path.as_ref().to_path_buf();
let mut dirname = config_path.clone();
dirname.pop();
self.dirname = Some(dirname);
self.path = Some(config_path);
}
pub(crate) fn get_path(&self) -> Result<&std::path::PathBuf, errors::GardenError> {
self.path.as_ref().ok_or_else(|| {
errors::GardenError::AssertionError("Configuration path is unset".into())
})
}
pub(crate) fn get_path_for_display(&self) -> String {
let default_pathbuf = std::path::PathBuf::from(constants::DOT);
self.path
.as_ref()
.unwrap_or(&default_pathbuf)
.display()
.to_string()
}
pub(crate) fn contains_graft(&self, name: &str) -> bool {
let graft_name = syntax::trim(name);
self.grafts.contains_key(graft_name)
}
pub(crate) fn get_graft(&self, name: &str) -> Result<&Graft, errors::GardenError> {
let graft_name = syntax::trim(name);
self.grafts.get(graft_name).ok_or_else(|| {
errors::GardenError::ConfigurationError(format!("{name}: no such graft"))
})
}
pub(crate) fn get_graft_id<'a>(
&self,
value: &'a str,
) -> Result<(ConfigId, &'a str), errors::GardenError> {
let (graft_name, remainder) = match syntax::split_graft(value) {
Some((graft_name, remainder)) => (graft_name, remainder),
None => {
return Err(errors::GardenError::ConfigurationError(format!(
"{value}: invalid graft expression"
)))
}
};
let graft = self.get_graft(graft_name)?;
let graft_id = graft
.get_id()
.ok_or(errors::GardenError::ConfigurationError(format!(
"{graft_name}: no such graft"
)))?;
Ok((graft_id, remainder))
}
pub fn get_tree(&self, name: &str) -> Option<&Tree> {
self.trees.get(name)
}
pub(crate) fn get_tree_pathbuf(&self, tree_name: &str) -> Option<std::path::PathBuf> {
self.get_tree(tree_name)
.map(|tree| tree.canonical_pathbuf())
.unwrap_or(None)
}
}
fn set_bool(name: &str, expr: &str, output: &mut bool) {
if let Some(value) = syntax::string_to_bool(expr) {
*output = value;
} else {
error!(
"'{}' is not a valid value for \"{}\". Must be true, false, 0 or 1",
name, expr
);
}
}
#[derive(Clone, Debug, Default)]
pub struct Graft {
id: Option<ConfigId>,
name: String,
pub root: String,
pub config: String,
}
impl Graft {
pub fn new(name: String, root: String, config: String) -> Self {
Graft {
id: None,
name,
root,
config,
}
}
pub fn get_name(&self) -> &String {
&self.name
}
pub fn get_id(&self) -> Option<ConfigId> {
self.id
}
pub(crate) fn set_id(&mut self, id: ConfigId) {
self.id = Some(id);
}
}
#[derive(Clone, Debug)]
pub(crate) struct EvalContext<'a> {
pub(crate) app_context: &'a ApplicationContext,
pub(crate) config: &'a Configuration,
pub(crate) graft_config: Option<&'a Configuration>,
pub(crate) tree_context: &'a TreeContext,
}
impl EvalContext<'_> {
pub(crate) fn new<'a>(
app_context: &'a ApplicationContext,
config: &'a Configuration,
graft_config: Option<&'a Configuration>,
tree_context: &'a TreeContext,
) -> EvalContext<'a> {
EvalContext {
app_context,
config,
graft_config,
tree_context,
}
}
pub(crate) fn from_app_context<'a>(
app_context: &'a ApplicationContext,
tree_context: &'a TreeContext,
) -> EvalContext<'a> {
let config = app_context.get_root_config();
let graft_config = tree_context
.config
.map(|config_id| app_context.get_config(config_id));
EvalContext::new(app_context, config, graft_config, tree_context)
}
pub(crate) fn tree_value(&self, value: &str) -> String {
eval::tree_value(
self.app_context,
self.config,
self.graft_config,
value,
&self.tree_context.tree,
self.tree_context.garden.as_ref(),
)
}
pub(crate) fn tree_variable(&self, var: &Variable) -> String {
eval::tree_variable(
self.app_context,
self.config,
self.graft_config,
&self.tree_context.tree,
self.tree_context.garden.as_ref(),
var,
)
}
}
#[derive(Clone, Debug)]
pub struct TreeContext {
pub tree: TreeName,
pub config: Option<ConfigId>,
pub garden: Option<GardenName>,
pub group: Option<String>,
}
impl TreeContext {
pub fn new(
tree: &str,
config: Option<ConfigId>,
garden: Option<GardenName>,
group: Option<String>,
) -> Self {
TreeContext {
tree: TreeName::from(tree),
config,
garden,
group,
}
}
}
#[derive(Debug, Default)]
pub struct TreeQuery {
pub query: String,
pub pattern: glob::Pattern,
pub is_default: bool,
pub is_garden: bool,
pub is_group: bool,
pub is_tree: bool,
pub include_gardens: bool,
pub include_groups: bool,
pub include_trees: bool,
}
impl TreeQuery {
pub fn new(query: &str) -> Self {
let mut is_default = false;
let mut is_tree = false;
let mut is_garden = false;
let mut is_group = false;
let mut include_gardens = true;
let mut include_groups = true;
let mut include_trees = true;
if syntax::is_garden(query) {
is_garden = true;
include_groups = false;
include_trees = false;
} else if syntax::is_group(query) {
is_group = true;
include_gardens = false;
include_trees = false;
} else if syntax::is_tree(query) {
is_tree = true;
include_gardens = false;
include_groups = false;
} else {
is_default = true;
}
let glob_pattern = syntax::trim(query);
let pattern = glob::Pattern::new(glob_pattern).unwrap_or_default();
TreeQuery {
query: query.into(),
is_default,
is_garden,
is_group,
is_tree,
include_gardens,
include_groups,
include_trees,
pattern,
}
}
}
#[derive(
Clone,
Debug,
Default,
PartialEq,
Eq,
strum_macros::EnumString,
strum_macros::Display,
strum_macros::VariantNames,
)]
#[strum(ascii_case_insensitive, serialize_all = "kebab-case")]
pub enum ColorMode {
#[default]
Auto,
#[strum(
serialize = "1",
serialize = "on",
serialize = "true",
serialize = "always",
serialize = "y",
serialize = "yes"
)]
On,
#[strum(
serialize = "0",
serialize = "off",
serialize = "false",
serialize = "never",
serialize = "n",
serialize = "no"
)]
Off,
}
impl ColorMode {
pub fn parse_from_str(string: &str) -> Result<ColorMode, String> {
ColorMode::from_str(string).map_err(|_| format!("choices are {:?}", Self::VARIANTS))
}
pub fn is_enabled(&self) -> bool {
match self {
ColorMode::Auto => std::io::stdout().is_terminal(),
ColorMode::Off => false,
ColorMode::On => true,
}
}
pub fn update(&mut self) {
if *self == ColorMode::Auto {
if self.is_enabled() {
*self = ColorMode::On;
} else {
*self = ColorMode::Off;
}
}
if *self == ColorMode::Off {
yansi::disable();
}
}
}
#[derive(
Clone,
Debug,
Default,
PartialEq,
Eq,
strum_macros::EnumString,
strum_macros::Display,
strum_macros::VariantNames,
)]
#[strum(ascii_case_insensitive, serialize_all = "kebab-case")]
pub enum TreeSortMode {
#[default]
None,
Name,
Time,
}
impl TreeSortMode {
pub(crate) fn parse_from_str(string: &str) -> Result<TreeSortMode, String> {
TreeSortMode::from_str(string).map_err(|_| format!("choices are {:?}", Self::VARIANTS))
}
}
#[derive(Debug)]
pub struct ApplicationContext {
pub options: cli::MainOptions,
arena: UnsafeCell<Arena<Configuration>>,
root_id: ConfigId,
}
unsafe impl Sync for ApplicationContext {}
impl Clone for ApplicationContext {
fn clone(&self) -> Self {
let mut arena: Arena<Configuration> = Arena::new();
if let Some(self_arena) = unsafe { self.arena.get().as_ref() } {
for node in self_arena.iter() {
arena.new_node(node.get().clone());
}
}
Self {
arena: UnsafeCell::new(arena),
options: self.options.clone(),
root_id: self.root_id,
}
}
}
impl ApplicationContext {
pub fn new(options: cli::MainOptions) -> Self {
let mut arena = Arena::new();
let config = Configuration::new();
let root_id = arena.new_node(config);
let app_context = ApplicationContext {
arena: UnsafeCell::new(arena),
root_id,
options,
};
let config = app_context.get_root_config_mut();
config.set_id(root_id);
app_context
}
pub fn from_options(options: &cli::MainOptions) -> Result<Self, errors::GardenError> {
let app_context = Self::new(options.clone());
let config_verbose = options.debug_level(constants::DEBUG_LEVEL_CONFIG);
app_context.get_root_config_mut().update(
&app_context,
options.config.as_deref(),
options.root.as_deref(),
config_verbose,
None,
)?;
app_context.get_root_config_mut().update_options(options)?;
config::read_grafts(&app_context)?;
Ok(app_context)
}
pub fn from_path_and_root(
path: &dyn AsRef<std::path::Path>,
root: Option<&std::path::Path>,
) -> Result<Self, errors::GardenError> {
let options = cli::MainOptions::new();
let app_context = Self::new(options.clone());
let config_verbose = options.debug_level(constants::DEBUG_LEVEL_CONFIG);
app_context.get_root_config_mut().update(
&app_context,
Some(path.as_ref()),
root,
config_verbose,
None,
)?;
config::read_grafts(&app_context)?;
Ok(app_context)
}
pub fn from_path(path: &dyn AsRef<std::path::Path>) -> Result<Self, errors::GardenError> {
if let Some(root_dir) = path.as_ref().parent().map(std::path::Path::to_owned) {
Self::from_path_and_root(path, Some(&root_dir))
} else {
Self::from_path_and_root(path, None)
}
}
pub fn from_path_string(path: &str) -> Result<Self, errors::GardenError> {
Self::from_path(&std::path::PathBuf::from(path))
}
pub fn from_string(string: &str) -> Result<Self, errors::GardenError> {
let options = cli::MainOptions::new();
let app_context = Self::new(options);
config::parse(&app_context, string, 0, app_context.get_root_config_mut())?;
config::read_grafts(&app_context)?;
Ok(app_context)
}
pub fn get_config(&self, id: ConfigId) -> &Configuration {
unsafe { (*self.arena.get()).get(id).unwrap().get() }
}
#[allow(clippy::mut_from_ref)]
pub(crate) fn get_config_mut(&self, id: ConfigId) -> &mut Configuration {
unsafe { (*self.arena.get()).get_mut(id).unwrap().get_mut() }
}
pub fn get_root_id(&self) -> ConfigId {
self.root_id
}
pub fn get_root_config(&self) -> &Configuration {
self.get_config(self.get_root_id())
}
pub fn get_root_config_mut(&self) -> &mut Configuration {
self.get_config_mut(self.get_root_id())
}
pub(crate) fn add_graft(&self, parent: ConfigId, config: Configuration) -> ConfigId {
let graft_id = unsafe { (*self.arena.get()).new_node(config) }; if let Some(arena) = unsafe { self.arena.get().as_mut() } {
parent.append(graft_id, arena);
}
self.get_config_mut(graft_id).set_id(graft_id);
graft_id
}
pub(crate) fn add_graft_config(
&self,
config_id: ConfigId,
graft_name: &str,
path: &std::path::Path,
root: Option<&std::path::Path>,
) -> Result<(), errors::GardenError> {
let path = path.to_path_buf();
let config_verbose = self.options.debug_level(constants::DEBUG_LEVEL_CONFIG);
let mut graft_config = Configuration::new();
graft_config.tree_branches = self.get_config(config_id).tree_branches;
graft_config.shell_exit_on_error = self.get_config(config_id).shell_exit_on_error;
graft_config.shell_word_split = self.get_config(config_id).shell_word_split;
graft_config.update(self, Some(&path), root, config_verbose, Some(config_id))?;
let graft_id = self.add_graft(config_id, graft_config);
if let Some(graft_config) = self.get_config_mut(config_id).grafts.get_mut(graft_name) {
graft_config.set_id(graft_id);
}
config::read_grafts_recursive(self, graft_id)?;
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum GitTreeType {
Parent,
Worktree(std::path::PathBuf),
Tree,
Bare,
}
#[derive(Clone, Debug)]
pub struct GitTreeDetails {
pub branch: String,
pub tree_type: GitTreeType,
}