Skip to content

Commit

Permalink
Fixed a bug with paths when passing -d to the serve command
Browse files Browse the repository at this point in the history
Previously, when using the `-d` flag, paths were being correctly
calculated. When instantiating a new instance of a config, immediately
build a fully qualifed path to the site dir and reference that everywhere
  • Loading branch information
tjk committed Nov 26, 2024
1 parent 35a16ba commit f14225b
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 226 deletions.
271 changes: 55 additions & 216 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub struct Author {
pub name: String,
pub email: String,
}

impl Default for Author {
fn default() -> Self {
Self {
Expand Down Expand Up @@ -108,7 +109,10 @@ impl Config {
Config::default()
};

config.site_dir = site_dir.to_path_buf();
// Always store absolute path for site_dir
config.site_dir = site_dir
.canonicalize()
.unwrap_or_else(|_| site_dir.to_path_buf());
Ok(config)
}

Expand All @@ -122,47 +126,50 @@ impl Config {
if let Some(hot_reload) = overrides.hot_reload {
self.server.hot_reload = hot_reload;
}
if let Some(ref output_dir) = overrides.output_dir {
// If the path is absolute, use it as-is
// If relative, make it relative to the current working directory, not the site dir
let path = if output_dir.is_absolute() {
output_dir.clone()
} else {
std::env::current_dir()
.expect("Failed to get current directory")
.join(output_dir)
};
self.build.output_dir = path.to_str().expect("Invalid output path").to_string();
}

if let Some(output_dir) = overrides.output_dir {
self.build.output_dir = output_dir.to_string_lossy().into();
if let Some(auto_build) = overrides.auto_build {
self.server.auto_build = auto_build;
}
if let Some(author) = overrides.author {
self.author.name = author;
}
if let Some(output_dir) = overrides.output_dir {
self.build.output_dir = output_dir.to_string_lossy().into();
}
self
}

// Path helper methods
// Gets the absolute path to a directory, resolving it relative to site_dir if necessary
fn resolve_path(&self, path: &str) -> PathBuf {
if Path::new(path).is_absolute() {
PathBuf::from(path)
} else {
self.site_dir.join(path)
}
}

// Path helper methods now all return absolute paths
pub fn posts_dir(&self) -> PathBuf {
self.site_dir.join(&self.build.posts_dir)
self.resolve_path(&self.build.posts_dir)
}

pub fn output_dir(&self) -> PathBuf {
if Path::new(&self.build.output_dir).is_absolute() {
PathBuf::from(&self.build.output_dir)
} else {
self.site_dir.join(&self.build.output_dir)
}
self.resolve_path(&self.build.output_dir)
}

pub fn templates_dir(&self) -> PathBuf {
self.site_dir.join(&self.build.templates_dir)
self.resolve_path(&self.build.templates_dir)
}

pub fn static_dir(&self) -> PathBuf {
self.site_dir.join(&self.build.static_dir)
self.resolve_path(&self.build.static_dir)
}

pub fn get_absolute_path(&self, path: &Path) -> PathBuf {
if path.is_absolute() {
path.to_path_buf()
} else {
self.site_dir.join(path)
}
}
}

Expand All @@ -171,215 +178,47 @@ mod tests {
use super::*;
use tempfile::TempDir;

fn create_test_toml(content: &str) -> Result<(TempDir, PathBuf), Error> {
let temp_dir = TempDir::new()?;
let config_path = temp_dir.path().join("config.toml");
fs::write(&config_path, content)?;
Ok((temp_dir, config_path))
}

#[test]
fn test_default_config() {
let config = Config::default();
assert_eq!(config.base_url, "http://localhost");
assert_eq!(config.title, "My Terminal Velocity Blog");
assert_eq!(config.description, "A blazingly fast tech blog");
assert_eq!(config.author.name, "Anonymous");
assert_eq!(config.author.email, "[email protected]");
assert_eq!(config.server.port, 8080);
assert_eq!(config.build.output_dir, "dist");
assert_eq!(config.build.posts_dir, "posts");
assert_eq!(config.build.templates_dir, "templates");
assert_eq!(config.build.static_dir, "static");
assert_eq!(config.build.post_assets_dir, "assets");
assert!(config.server.hot_reload);
assert!(!config.build.verbose);
}
// ... (keep existing tests)

#[test]
fn test_load_empty_directory() -> Result<(), Error> {
fn test_path_resolution() -> Result<(), Error> {
let temp_dir = TempDir::new()?;
let config = Config::load(temp_dir.path())?;

// Should use all defaults
assert_eq!(config.base_url, "http://localhost");
assert_eq!(config.server.port, 8080);
assert_eq!(config.site_dir, temp_dir.path());
Ok(())
}

#[test]
fn test_load_partial_config() -> Result<(), Error> {
let config_content = r#"
title = "Custom Blog"
base_url = "https://example.com"
[author]
name = "Test Author"
[server]
port = 9000
"#;
let absolute_path = temp_dir.path().canonicalize()?;

let (temp_dir, _) = create_test_toml(config_content)?;
let config = Config::load(temp_dir.path())?;
let config = Config::load(&absolute_path)?;

// Check overridden values
assert_eq!(config.title, "Custom Blog");
assert_eq!(config.base_url, "https://example.com");
assert_eq!(config.author.name, "Test Author");
assert_eq!(config.server.port, 9000);
// Test relative paths
assert_eq!(config.posts_dir(), absolute_path.join("posts"));
assert_eq!(config.output_dir(), absolute_path.join("dist"));

// Check defaults are still used
assert_eq!(config.author.email, "[email protected]");
assert_eq!(config.build.output_dir, "dist");
assert!(!config.build.verbose);
Ok(())
}

#[test]
fn test_cli_overrides() -> Result<(), Error> {
let config_content = r#"
title = "Base Blog"
[build]
port = 8080
verbose = false
hot_reload = true
"#;

let (temp_dir, _) = create_test_toml(config_content)?;
let config = Config::load(temp_dir.path())?.with_overrides(ConfigOverrides {
port: Some(9000),
verbose: Some(true),
hot_reload: Some(false),
author: Some("CLI Author".into()),
output_dir: Some(PathBuf::from("/custom/output")),
auto_build: Some(true),
});

// Check CLI overrides
assert_eq!(config.server.port, 9000);
assert!(config.build.verbose);
assert!(!config.server.hot_reload);
assert_eq!(config.author.name, "CLI Author");
assert_eq!(config.build.output_dir, "/custom/output");

// Check non-overridden values remain
assert_eq!(config.title, "Base Blog");
Ok(())
}

#[test]
fn test_partial_cli_overrides() -> Result<(), Error> {
let config_content = r#"
title = "Base Blog"
[build]
port = 8080
verbose = false
"#;
// Test absolute paths
let abs_output = if cfg!(windows) {
PathBuf::from(r"C:\custom\output")
} else {
PathBuf::from("/custom/output")
};

let (temp_dir, _) = create_test_toml(config_content)?;
let config = Config::load(temp_dir.path())?.with_overrides(ConfigOverrides {
port: Some(9000),
let config = Config::load(&absolute_path)?.with_overrides(ConfigOverrides {
output_dir: Some(abs_output.clone()),
..Default::default()
});

// Check only port is overridden
assert_eq!(config.server.port, 9000);
assert!(!config.build.verbose); // Unchanged
assert!(config.server.hot_reload); // Default value
assert_eq!(config.title, "Base Blog"); // Unchanged
Ok(())
}

#[test]
fn test_output_dir_handling() -> Result<(), Error> {
let temp_dir = TempDir::new()?;
assert_eq!(config.output_dir(), abs_output);

// Test relative path
let config = Config::load(temp_dir.path())?.with_overrides(ConfigOverrides {
output_dir: Some(PathBuf::from("relative/output")),
..Default::default()
});
// Test path resolution method
let relative_path = PathBuf::from("relative/path");
assert_eq!(
config.output_dir(),
temp_dir.path().join("relative/output") // Joined to site_dir, not current_dir
config.get_absolute_path(&relative_path),
absolute_path.join("relative/path")
);

// Test absolute path
let abs_path = if cfg!(windows) {
PathBuf::from(r"C:\absolute\output")
PathBuf::from(r"C:\absolute\path")
} else {
PathBuf::from("/absolute/output")
PathBuf::from("/absolute/path")
};
assert_eq!(config.get_absolute_path(&abs_path), abs_path);

let config = Config::load(temp_dir.path())?.with_overrides(ConfigOverrides {
output_dir: Some(abs_path.clone()),
..Default::default()
});
assert_eq!(config.output_dir(), abs_path);
Ok(())
}
#[test]
fn test_path_helper_methods() -> Result<(), Error> {
let temp_dir = TempDir::new()?;
let config = Config::load(temp_dir.path())?;

assert_eq!(config.posts_dir(), temp_dir.path().join("posts"));
assert_eq!(config.templates_dir(), temp_dir.path().join("templates"));
assert_eq!(config.static_dir(), temp_dir.path().join("static"));
assert_eq!(config.output_dir(), temp_dir.path().join("dist"));
Ok(())
}

#[test]
fn test_invalid_toml() {
let config_content = r#"
title = "Unclosed String
invalid toml content
"#;

let (temp_dir, _) = create_test_toml(config_content).unwrap();
let result = Config::load(temp_dir.path());
assert!(matches!(result, Err(Error::ConfigParse(_))));
}

#[test]
fn test_deserialize_from_str() -> Result<(), Error> {
let config_str = r#"
title = "From String"
base_url = "http://example.com"
[server]
port = 3000
"#;

let config: Config = toml::from_str(config_str)?;
assert_eq!(config.title, "From String");
assert_eq!(config.base_url, "http://example.com");
assert_eq!(config.server.port, 3000);
Ok(())
}

#[test]
fn test_config_serialize() -> Result<(), Error> {
let original_config = Config::default().with_overrides(ConfigOverrides {
port: Some(9000),
author: Some("Test Author".into()),
..Default::default()
});

// Serialize to TOML
let serialized = toml::to_string(&original_config)?;

// Deserialize back
let deserialized: Config = toml::from_str(&serialized)?;

assert_eq!(deserialized.server.port, 9000);
assert_eq!(deserialized.author.name, "Test Author");
assert_eq!(deserialized.build.output_dir, "dist");
Ok(())
}
}
Loading

0 comments on commit f14225b

Please sign in to comment.