Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 29 additions & 14 deletions src/app_init.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::config::{app_config::AppConfig, locations::LocationsProvider};
use crate::migration;
use anyhow::Result;
use std::{
fs::{self, File},
Expand All @@ -14,28 +15,29 @@ pub struct AppInitializer<'a> {
impl<'a> AppInitializer<'a> {
/// Creates config dir and file if they don't exist
pub fn init(&self) -> Result<()> {
migration::migrate_projects_path_if_needed(self.locations_provider)?;

let base_config_dir = self.locations_provider.get_base_config_path()?;
if !base_config_dir.exists() {
let base_config_dir = self.locations_provider.get_configs_config_path();
fs::create_dir_all(base_config_dir)?;
fs::create_dir_all(&base_config_dir)?;
}

let config_file_path = self.locations_provider.get_config_file_path();
if !config_file_path.exists() {
self.create_config_file(&config_file_path)?;
}

let configs_dir_path = self.locations_provider.get_configs_config_path();
if !configs_dir_path.exists() {
fs::create_dir_all(configs_dir_path)?;
let projects_dir_path = self.locations_provider.get_projects_data_path();
if !projects_dir_path.exists() {
fs::create_dir_all(projects_dir_path)?;
}

Ok(())
}

fn create_config_file(&self, file_path: &Path) -> Result<()> {
let file_content = AppConfig::default().to_string()?;
let mut file = File::create(&file_path)?;
let mut file = File::create(file_path)?;
Ok(file.write_all(file_content.as_bytes())?)
}
}
Expand All @@ -49,7 +51,8 @@ mod tests {

#[test]
fn init_when_not_initialized_yet_files_get_created() {
let (locations_provider, base_path, _temp_dir) = get_fake_locationsprovider(false);
let (locations_provider, _config_path, data_path, _temp_dirs) =
get_fake_locationsprovider(false);
let init = AppInitializer {
locations_provider: &locations_provider,
};
Expand All @@ -59,12 +62,12 @@ mod tests {
get_config_without_whitespace(locations_provider.get_config_file_path());

assert_eq!("{\"projects\":[]}", config_file_content);
assert!(base_path.join("configs").exists())
assert!(data_path.join("projects").exists())
}

#[test]
fn init_when_base_dir_exists_but_config_file_doesnt_then_config_file_gets_created() {
let (locations_provider, _, _temp_dir) = get_fake_locationsprovider(true);
let (locations_provider, _, _, _temp_dirs) = get_fake_locationsprovider(true);
let init = AppInitializer {
locations_provider: &locations_provider,
};
Expand All @@ -78,13 +81,25 @@ mod tests {

fn get_fake_locationsprovider(
base_dir_should_exist: bool,
) -> (LocationsProvider, PathBuf, tempfile::TempDir) {
let dir = tempfile::tempdir().unwrap();
let mut path = dir.path().to_path_buf();
) -> (
LocationsProvider,
PathBuf,
PathBuf,
(tempfile::TempDir, tempfile::TempDir),
) {
let config_dir = tempfile::tempdir().unwrap();
let data_dir = tempfile::tempdir().unwrap();
let mut config_path = config_dir.path().to_path_buf();
let data_path = data_dir.path().to_path_buf();
if !base_dir_should_exist {
path = path.join("non-existing-base-dir");
config_path = config_path.join("non-existing-base-dir");
}
(LocationsProvider::new(path.to_path_buf()), path, dir)
(
LocationsProvider::new(config_path.clone(), data_path.clone()),
config_path,
data_path,
(config_dir, data_dir),
)
}

fn get_config_without_whitespace(path: PathBuf) -> String {
Expand Down
6 changes: 5 additions & 1 deletion src/cli_args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@ pub struct AppArgs {
#[arg(short = 'v', long, action = clap::ArgAction::Version)]
version: (),

/// The path that conamn will treat as a base path for all its data storage (configs, projects)
/// The base path for puff's configuration (config.json)
#[arg(long, default_value = "default", env = "PUFF_CONFIG_PATH", hide = true)]
pub config_path: String,

/// The base path for puff's data storage (projects)
#[arg(long, default_value = "default", env = "PUFF_DATA_PATH", hide = true)]
pub data_path: String,

#[command(subcommand)]
pub command: Command,
}
Expand Down
44 changes: 26 additions & 18 deletions src/commands/add_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,13 +153,15 @@ mod tests {

#[test]
fn add_file_when_project_does_not_exist() {
let puff_dir = tempfile::tempdir().unwrap();
let config_dir = tempfile::tempdir().unwrap();
let data_dir = tempfile::tempdir().unwrap();
let current_dir = tempfile::tempdir().unwrap();
fs::create_dir_all(puff_dir.path().join("configs/proj1")).unwrap();
let locations_provider = LocationsProvider::new(puff_dir.path().to_path_buf());
fs::create_dir_all(data_dir.path().join("projects/proj1")).unwrap();
let locations_provider =
LocationsProvider::new(config_dir.path().to_path_buf(), data_dir.path().to_path_buf());

let user_file = current_dir.path().join("file");
let config_file = puff_dir.path().join("config.json");
let config_file = config_dir.path().join("config.json");
let mut file = File::create(&config_file).unwrap();
write!(file, "{{\"projects\":[]}}").unwrap();

Expand All @@ -176,13 +178,15 @@ mod tests {

#[test]
fn add_file_fresh_scenario() {
let puff_dir = tempfile::tempdir().unwrap();
let config_dir = tempfile::tempdir().unwrap();
let data_dir = tempfile::tempdir().unwrap();
let current_dir = tempfile::tempdir().unwrap();
fs::create_dir_all(puff_dir.path().join("configs/proj1")).unwrap();
let locations_provider = LocationsProvider::new(puff_dir.path().to_path_buf());
fs::create_dir_all(data_dir.path().join("projects/proj1")).unwrap();
let locations_provider =
LocationsProvider::new(config_dir.path().to_path_buf(), data_dir.path().to_path_buf());

let user_file = current_dir.path().join("file");
let config_file = puff_dir.path().join("config.json");
let config_file = config_dir.path().join("config.json");
let mut file = File::create(&config_file).unwrap();
write!(
file,
Expand All @@ -198,12 +202,14 @@ mod tests {

#[test]
fn add_file_in_subdirectory() {
let puff_dir = tempfile::tempdir().unwrap();
let config_dir = tempfile::tempdir().unwrap();
let data_dir = tempfile::tempdir().unwrap();
let project_root = tempfile::tempdir().unwrap();
fs::create_dir_all(puff_dir.path().join("configs/proj1")).unwrap();
let locations_provider = LocationsProvider::new(puff_dir.path().to_path_buf());
fs::create_dir_all(data_dir.path().join("projects/proj1")).unwrap();
let locations_provider =
LocationsProvider::new(config_dir.path().to_path_buf(), data_dir.path().to_path_buf());

let config_file = puff_dir.path().join("config.json");
let config_file = config_dir.path().join("config.json");
let mut file = File::create(&config_file).unwrap();
write!(
file,
Expand All @@ -219,18 +225,20 @@ mod tests {
let sut = AddCommand::new(&locations_provider);
sut.add_file(user_file, project_root.path(), false).unwrap();

let managed_file = puff_dir.path().join("configs/proj1/config/secrets.env");
let managed_file = data_dir.path().join("projects/proj1/config/secrets.env");
assert!(managed_file.exists());
}

#[test]
fn add_file_from_subdirectory_cwd() {
let puff_dir = tempfile::tempdir().unwrap();
let config_dir = tempfile::tempdir().unwrap();
let data_dir = tempfile::tempdir().unwrap();
let project_root = tempfile::tempdir().unwrap();
fs::create_dir_all(puff_dir.path().join("configs/proj1")).unwrap();
let locations_provider = LocationsProvider::new(puff_dir.path().to_path_buf());
fs::create_dir_all(data_dir.path().join("projects/proj1")).unwrap();
let locations_provider =
LocationsProvider::new(config_dir.path().to_path_buf(), data_dir.path().to_path_buf());

let config_file = puff_dir.path().join("config.json");
let config_file = config_dir.path().join("config.json");
let mut file = File::create(&config_file).unwrap();
write!(
file,
Expand All @@ -248,7 +256,7 @@ mod tests {
sut.add_file(std::path::PathBuf::from("secrets.env"), &subdir, false)
.unwrap();

let managed_file = puff_dir.path().join("configs/proj1/config/secrets.env");
let managed_file = data_dir.path().join("projects/proj1/config/secrets.env");
assert!(managed_file.exists());

let symlink_target = fs::read_link(&user_file).unwrap();
Expand Down
34 changes: 19 additions & 15 deletions src/config/locations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,31 @@ const APP_NAME: &str = "puff";

pub struct LocationsProvider {
config_base_path: PathBuf,
data_base_path: PathBuf,
}

impl LocationsProvider {
pub fn new(base_config_path: PathBuf) -> LocationsProvider {
pub fn new(config_base_path: PathBuf, data_base_path: PathBuf) -> LocationsProvider {
LocationsProvider {
config_base_path: base_config_path,
config_base_path,
data_base_path,
}
}

pub fn get_base_config_path(&self) -> Result<PathBuf> {
Ok(self.config_base_path.clone())
}

pub fn get_configs_config_path(&self) -> PathBuf {
pub fn get_base_data_path(&self) -> Result<PathBuf> {
Ok(self.data_base_path.clone())
}

pub fn get_projects_data_path(&self) -> PathBuf {
self.data_base_path.join(Path::new("projects"))
}

/// Legacy path used before the config/data split. Used for migration.
pub fn get_legacy_configs_path(&self) -> PathBuf {
self.config_base_path.join(Path::new("configs"))
}

Expand All @@ -30,7 +41,7 @@ impl LocationsProvider {
}

pub fn get_managed_dir(&self, name: &str) -> PathBuf {
self.get_configs_config_path().join(Path::new(name))
self.get_projects_data_path().join(Path::new(name))
}

/// Walks up from `path` through its ancestors and returns the first
Expand All @@ -53,18 +64,11 @@ impl LocationsProvider {

impl Default for LocationsProvider {
fn default() -> Self {
let dirs = ProjectDirs::from("com", "marcinjahn", APP_NAME)
.expect("The default configuration path of puff could not be retrieved");
Self {
config_base_path: get_base_config_path()
.expect("The default configuration path of puff could not be retrieved"),
config_base_path: dirs.config_dir().to_owned(),
data_base_path: dirs.data_dir().to_owned(),
}
}
}

fn get_base_config_path() -> Result<PathBuf> {
match ProjectDirs::from("com", "marcinjahn", APP_NAME) {
Some(dirs) => Ok(dirs.config_dir().to_owned()),
None => Err(anyhow!(
"Could not determine the configuration directory for this system."
)),
}
}
72 changes: 48 additions & 24 deletions src/config/projects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ impl<'a> ProjectsRetriever<'a> {
/// Returns names of all the projects that puff stores (some of them might
/// not be associated yet)
fn get_all_projects(&self) -> Result<Vec<String>> {
let location = self.locations_provider.get_configs_config_path();
let location = self.locations_provider.get_projects_data_path();
let paths = fs::read_dir(location)?;

let mut projects = vec![];
Expand Down Expand Up @@ -163,8 +163,12 @@ mod tests {
#[test]
fn is_associated_when_associated_project_is_provided_true_is_returned() {
let checked_dir = tempfile::tempdir().unwrap();
let base_dir = tempfile::tempdir().unwrap();
let locations_provider = LocationsProvider::new(base_dir.path().to_path_buf());
let config_dir = tempfile::tempdir().unwrap();
let data_dir = tempfile::tempdir().unwrap();
let locations_provider = LocationsProvider::new(
config_dir.path().to_path_buf(),
data_dir.path().to_path_buf(),
);
let app_config = AppConfig {
projects: vec![Project {
name: String::from("proj"),
Expand All @@ -182,8 +186,12 @@ mod tests {

#[test]
fn is_associated_when_not_associated_project_is_provided_false_is_returned() {
let base_dir = tempfile::tempdir().unwrap();
let locations_provider = LocationsProvider::new(base_dir.path().to_path_buf());
let config_dir = tempfile::tempdir().unwrap();
let data_dir = tempfile::tempdir().unwrap();
let locations_provider = LocationsProvider::new(
config_dir.path().to_path_buf(),
data_dir.path().to_path_buf(),
);
let app_config = AppConfig { projects: vec![] };

let sut = ProjectsRetriever::new(app_config, &locations_provider);
Expand All @@ -200,12 +208,16 @@ mod tests {
let proj_2_dir = tempfile::tempdir().unwrap();
let proj_3_dir = tempfile::tempdir().unwrap();

let base_dir = tempfile::tempdir().unwrap();
fs::create_dir_all(base_dir.path().join("configs/proj1")).unwrap();
fs::create_dir_all(base_dir.path().join("configs/proj2")).unwrap();
fs::create_dir_all(base_dir.path().join("configs/proj3")).unwrap();
let data_dir = tempfile::tempdir().unwrap();
let config_dir = tempfile::tempdir().unwrap();
fs::create_dir_all(data_dir.path().join("projects/proj1")).unwrap();
fs::create_dir_all(data_dir.path().join("projects/proj2")).unwrap();
fs::create_dir_all(data_dir.path().join("projects/proj3")).unwrap();

let locations_provider = LocationsProvider::new(base_dir.path().to_path_buf());
let locations_provider = LocationsProvider::new(
config_dir.path().to_path_buf(),
data_dir.path().to_path_buf(),
);
let app_config = AppConfig {
projects: vec![
Project {
Expand Down Expand Up @@ -238,12 +250,16 @@ mod tests {
let proj_1_dir = tempfile::tempdir().unwrap();
let proj_2_dir = tempfile::tempdir().unwrap();

let base_dir = tempfile::tempdir().unwrap();
fs::create_dir_all(base_dir.path().join("configs/proj1")).unwrap();
fs::create_dir_all(base_dir.path().join("configs/proj2")).unwrap();
fs::create_dir_all(base_dir.path().join("configs/proj3")).unwrap();
let data_dir = tempfile::tempdir().unwrap();
let config_dir = tempfile::tempdir().unwrap();
fs::create_dir_all(data_dir.path().join("projects/proj1")).unwrap();
fs::create_dir_all(data_dir.path().join("projects/proj2")).unwrap();
fs::create_dir_all(data_dir.path().join("projects/proj3")).unwrap();

let locations_provider = LocationsProvider::new(base_dir.path().to_path_buf());
let locations_provider = LocationsProvider::new(
config_dir.path().to_path_buf(),
data_dir.path().to_path_buf(),
);
let app_config = AppConfig {
projects: vec![
Project {
Expand Down Expand Up @@ -272,12 +288,16 @@ mod tests {
let proj_1_dir = tempfile::tempdir().unwrap();
let proj_2_dir = tempfile::tempdir().unwrap();

let base_dir = tempfile::tempdir().unwrap();
fs::create_dir_all(base_dir.path().join("configs/proj1")).unwrap();
fs::create_dir_all(base_dir.path().join("configs/proj2")).unwrap();
fs::create_dir_all(base_dir.path().join("configs/proj3")).unwrap();
let data_dir = tempfile::tempdir().unwrap();
let config_dir = tempfile::tempdir().unwrap();
fs::create_dir_all(data_dir.path().join("projects/proj1")).unwrap();
fs::create_dir_all(data_dir.path().join("projects/proj2")).unwrap();
fs::create_dir_all(data_dir.path().join("projects/proj3")).unwrap();

let locations_provider = LocationsProvider::new(base_dir.path().to_path_buf());
let locations_provider = LocationsProvider::new(
config_dir.path().to_path_buf(),
data_dir.path().to_path_buf(),
);
let app_config = AppConfig {
projects: vec![
Project {
Expand Down Expand Up @@ -306,10 +326,14 @@ mod tests {

#[test]
fn get_all_projects_when_there_are_no_projects_then_empty_vector_is_returned() {
let base_dir = tempfile::tempdir().unwrap();
fs::create_dir_all(base_dir.path().join("configs")).unwrap();

let locations_provider = LocationsProvider::new(base_dir.path().to_path_buf());
let data_dir = tempfile::tempdir().unwrap();
let config_dir = tempfile::tempdir().unwrap();
fs::create_dir_all(data_dir.path().join("projects")).unwrap();

let locations_provider = LocationsProvider::new(
config_dir.path().to_path_buf(),
data_dir.path().to_path_buf(),
);
let app_config = AppConfig { projects: vec![] };

let sut = ProjectsRetriever::new(app_config, &locations_provider);
Expand Down
Loading