Skip to content

Commit

Permalink
ADD support for local config files
Browse files Browse the repository at this point in the history
  • Loading branch information
synoet committed Jun 3, 2024
1 parent a90a742 commit 1ea0991
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 53 deletions.
11 changes: 11 additions & 0 deletions cdwe.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
variables = [
{name = "name", value = "world"},
]

aliases = [
{name = "hello", commands = ["echo Hello, {{name}}!"]},
]

commands = ["git fetch -p"]


2 changes: 1 addition & 1 deletion src/cmd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ mod shell;

pub use cmd::{Cli, Commands};
pub use init::{init_shell, remove_shell};
pub use run::run;
pub use run::{run, run_local};
pub use shell::Shell;
140 changes: 90 additions & 50 deletions src/cmd/run.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,10 @@
use super::Shell;
use crate::cache::{Cache, DirCache};
use crate::config::EnvVariable;
use crate::config::{EnvAlias, EnvVariable, LocalConfig};
use crate::utils::trim_quotes;
use anyhow::{anyhow, Result};
use std::path::Path;

fn trim_quotes(s: &str) -> String {
if s.len() < 2 {
return s.to_string();
}
let mut chars = s.chars();
match (chars.next(), chars.next_back()) {
(Some('"'), Some('"')) => chars.collect(),
(Some('\''), Some('\'')) => chars.collect(),
_ => s.to_string(),
}
}

/// Parses the content of an .env file with the following structure
/// ```
/// SOME_VAR=test
Expand Down Expand Up @@ -60,45 +49,58 @@ fn get_vars_from_env_file(base_path: &str, file_path: &str) -> Option<Vec<EnvVar
}

/// Given a cache unsets the environment variables for the old directory
/// variables are taken from the dir and from any .enf files specified in the config
pub fn unset_variables(dir: &DirCache, path: &str) {
for var in dir.variables.iter() {
/// variables are taken from the dir and from any .env files specified in the config
pub fn unset_variables(
variables: &Vec<EnvVariable>,
load_from: Option<&Vec<String>>,
path: Option<&str>,
) {
for var in variables.iter() {
println!("unset {}", var.name);
}

// Unload variables from .env files specified in config
// for the old directory
for file in &dir.load_from {
let vars = get_vars_from_env_file(path, file);
if let Some(vars) = vars {
for var in vars {
println!("unset {}", var.name);

if let (Some(path), Some(load_from)) = (path, load_from) {
for file in load_from {
let vars = get_vars_from_env_file(path, file);
if let Some(vars) = vars {
for var in vars {
println!("unset {}", var.name);
}
}
}
}
}

/// Given a cache sets the environment variables for the new directory
/// variables are taken from the dir and from any .enf files specified in the config
pub fn set_variables(dir: &DirCache, path: &str) {
for var in &dir.variables {
pub fn set_variables(
variables: &Vec<EnvVariable>,
load_from: Option<&Vec<String>>,
path: Option<&str>,
) {
for var in variables {
println!("export {}=\"{}\"", var.name, var.value);
}

// Load variables from .env files specified in config
for file in &dir.load_from {
let vars = get_vars_from_env_file(path, file);
if let Some(vars) = vars {
for var in vars {
println!("export {}=\"{}\"", var.name, var.value);
if let (Some(path), Some(load_from)) = (path, load_from) {
// Load variables from .env files specified in config
for file in load_from {
let vars = get_vars_from_env_file(path, &file);
if let Some(vars) = vars {
for var in vars {
println!("export {}=\"{}\"", var.name, var.value);
}
}
}
}
}

pub fn set_alias(dir: &DirCache, shell: &str) -> Result<()> {
pub fn set_aliases(aliases: &Vec<EnvAlias>, shell: &str) -> Result<()> {
let (start_str, end_str) = Shell::from_string(shell)?.get_alias_command();
for alias in dir.aliases.iter() {
for alias in aliases.iter() {
let mut alias_string = start_str.clone().replace("{{{alias_name}}}", &alias.name);
for cmd in &alias.commands {
alias_string.push_str(&format!("{}\n", cmd));
Expand All @@ -110,14 +112,14 @@ pub fn set_alias(dir: &DirCache, shell: &str) -> Result<()> {
Ok(())
}

pub fn unset_alias(dir: &DirCache) {
for alias in dir.aliases.iter() {
pub fn unset_aliases(aliases: &Vec<EnvAlias>) {
for alias in aliases.iter() {
println!("unset -f {} &> /dev/null", alias.name);
}
}

pub fn run_command(dir: &DirCache) {
for command in dir.run.iter() {
pub fn run_commands(commands: &Vec<String>) {
for command in commands.iter() {
println!("{}", command);
}
}
Expand All @@ -132,31 +134,69 @@ pub fn run(cache: &Cache, old_path: String, new_path: String) -> Result<()> {

// Unset old environment variables
if let Some(old_dir) = old_dir {
unset_variables(old_dir, &old_path);
unset_alias(old_dir);
unset_variables(
&old_dir.variables,
Some(&old_dir.load_from),
Some(&old_path),
);
unset_aliases(&old_dir.aliases);
}

if let Some(new_dir) = new_dir {
set_variables(new_dir, &new_path);
set_alias(new_dir, &cache.shell)?;
run_command(new_dir);
set_variables(
&new_dir.variables,
Some(&new_dir.load_from),
Some(&new_path),
);
set_aliases(&new_dir.aliases, &cache.shell)?;
run_commands(&new_dir.run);
}

Ok(())
}

#[cfg(test)]
mod tests {
#[test]
fn test_trim_quotes() {
use super::trim_quotes;
assert_eq!(trim_quotes("\"test\""), "test");
assert_eq!(trim_quotes("'test'"), "test");
assert_eq!(trim_quotes("test"), "test");
assert_eq!(trim_quotes("\"test"), "\"test");
assert_eq!(trim_quotes("test'"), "test'");
pub fn run_local(
old_local_config: Option<&LocalConfig>,
new_local_config: Option<&LocalConfig>,
shell: &str,
) -> Result<()> {
if old_local_config.is_none() && new_local_config.is_none() {
return Ok(());
}

if let Some(old_local_config) = old_local_config {
if let Some(vars) = &old_local_config.variables {
unset_variables(vars, None, None);
}

if let Some(aliases) = &old_local_config.aliases {
unset_aliases(aliases);
}
}

if let Some(new_local_config) = new_local_config {
if let Some(vars) = &new_local_config.variables {
set_variables(vars, None, None);
}

if let Some(aliases) = &new_local_config.aliases {
// This might be the only error worth reporting to the user
// since it means that they have misconfigured cdwe
if let Err(err) = set_aliases(aliases, shell) {
eprintln!("ERROR: misconfigured shell { }", err);
}
}

if let Some(commands) = &new_local_config.commands {
run_commands(commands);
}
}

return Ok(());
}

#[cfg(test)]
mod tests {
#[test]
fn test_parse_env_file() {
use super::parse_env_file;
Expand Down
16 changes: 16 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,22 @@ impl From<EnvVariableStruct> for EnvVariableVec {
}
}

#[derive(Deserialize, Serialize, Debug, Clone, Default)]
pub struct LocalConfig {
pub variables: Option<Vec<EnvVariable>>,
pub aliases: Option<Vec<EnvAlias>>,
pub commands: Option<Vec<String>>,
}

impl LocalConfig {
pub fn from_str(content: &str) -> Result<Self> {
let config: LocalConfig =
toml::from_str(content).with_context(|| "Could not parse local config file")?;

Ok(config)
}
}

#[derive(Deserialize, Serialize, Debug, Clone, Default)]
pub struct Config {
pub config: Option<GlobalConfig>,
Expand Down
22 changes: 20 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ mod config;
mod utils;
use anyhow::{Context, Result};
use clap::Parser;
use cmd::{init_shell, remove_shell, run, Cli};
use config::Config;
use cmd::{init_shell, remove_shell, run, run_local, Cli};
use config::{Config, LocalConfig};

#[tokio::main]
async fn main() -> Result<()> {
Expand All @@ -17,18 +17,36 @@ async fn main() -> Result<()> {
match matches.command {
cmd::Commands::Init { shell } => init_shell(None, shell.unwrap())?,
cmd::Commands::Run { old_dir, new_dir } => {
let local_config_path = format!("{}/{}", new_dir, "cdwe.toml");
let old_local_config_path = format!("{}/{}", old_dir, "cdwe.toml");

let contents = std::fs::read_to_string(&config_path)
.with_context(|| format!("Could not read config file at {}", &config_path))?;
let config_hash = utils::get_content_hash(&contents);
let cache_contents: Option<String> = std::fs::read_to_string(cache_path).ok();
let (cache, did_create_cache) =
cache::get_or_create_cache(cache_contents.as_deref(), &contents, &config_hash)?;
let shell = cache.shell.clone();

run(&cache, old_dir, new_dir)?;

if did_create_cache {
cache::write_cache(&cache, &home)?;
}

let old_local_config = match std::fs::read_to_string(&old_local_config_path) {
Ok(contents) => Some(LocalConfig::from_str(&contents)?),
Err(_) => None,
};

let new_local_config = match std::fs::read_to_string(&local_config_path) {
Ok(contents) => Some(LocalConfig::from_str(&contents)?),
Err(_) => None,
};

if old_local_config.is_some() || new_local_config.is_some() {
run_local(old_local_config.as_ref(), new_local_config.as_ref(), &shell)?;
}
}
cmd::Commands::Reload { shell } => {
let config: Config = Config::from_config_file(&config_path)?;
Expand Down
24 changes: 24 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,27 @@ pub fn get_content_hash(content: &str) -> String {

format!("{:?}", hasher.finalize())
}

pub fn trim_quotes(s: &str) -> String {
if s.len() < 2 {
return s.to_string();
}
let mut chars = s.chars();
match (chars.next(), chars.next_back()) {
(Some('"'), Some('"')) => chars.collect(),
(Some('\''), Some('\'')) => chars.collect(),
_ => s.to_string(),
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_trim_quotes() {
use super::trim_quotes;
assert_eq!(trim_quotes("\"test\""), "test");
assert_eq!(trim_quotes("'test'"), "test");
assert_eq!(trim_quotes("test"), "test");
assert_eq!(trim_quotes("\"test"), "\"test");
assert_eq!(trim_quotes("test'"), "test'");
}
}

0 comments on commit 1ea0991

Please sign in to comment.