1
0
mirror of https://github.com/Llewellynvdm/starship.git synced 2024-06-03 17:10:50 +00:00
starship/src/modules/directory.rs
Tilmann Meyer 2233683410
feat: add error messaging (#1576)
This creates a custom logger for the log crate which logs everything to a file (/tmp/starship/session_$STARSHIP_SESSION_KEY.log) and it logs everything above Warn to stderr, but only if the log file does not contain the line that should be logged resulting in an error or warning to be only logged at the first starship invocation after opening the shell.
2020-09-28 16:38:50 -04:00

1170 lines
39 KiB
Rust

#[cfg(not(target_os = "windows"))]
use super::utils::directory_nix as directory_utils;
#[cfg(target_os = "windows")]
use super::utils::directory_win as directory_utils;
use path_slash::PathExt;
use std::collections::HashMap;
use std::iter::FromIterator;
use std::path::{Path, PathBuf};
use unicode_segmentation::UnicodeSegmentation;
use super::{Context, Module};
use super::utils::directory::truncate;
use crate::config::RootModuleConfig;
use crate::configs::directory::DirectoryConfig;
use crate::formatter::StringFormatter;
/// Creates a module with the current directory
///
/// Will perform path contraction, substitution, and truncation.
///
/// **Contraction**
///
/// - Paths beginning with the home directory or with a git repo right inside
/// the home directory will be contracted to `~`
/// - Paths containing a git repo will contract to begin at the repo root
///
/// **Substitution**
/// Paths will undergo user-provided substitutions of substrings
///
/// **Truncation**
/// Paths will be limited in length to `3` path components by default.
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
const HOME_SYMBOL: &str = "~";
let mut module = context.new_module("directory");
let config: DirectoryConfig = DirectoryConfig::try_load(module.config);
// Using environment PWD is the standard approach for determining logical path
// If this is None for any reason, we fall back to reading the os-provided path
let physical_current_dir = if config.use_logical_path {
match context.get_env("PWD") {
Some(x) => Some(PathBuf::from(x)),
None => {
log::debug!("Error getting PWD environment variable!");
None
}
}
} else {
match std::env::current_dir() {
Ok(x) => Some(x),
Err(e) => {
log::debug!("Error getting physical current directory: {}", e);
None
}
}
};
let current_dir = Path::new(
physical_current_dir
.as_ref()
.unwrap_or_else(|| &context.current_dir),
);
let home_dir = dirs_next::home_dir().unwrap();
log::debug!("Current directory: {:?}", current_dir);
let repo = &context.get_repo().ok()?;
let dir_string = match &repo.root {
Some(repo_root) if config.truncate_to_repo && (repo_root != &home_dir) => {
log::debug!("Repo root: {:?}", repo_root);
// Contract the path to the git repo root
contract_repo_path(current_dir, repo_root)
.unwrap_or_else(|| contract_path(current_dir, &home_dir, HOME_SYMBOL))
}
// Contract the path to the home directory
_ => contract_path(current_dir, &home_dir, HOME_SYMBOL),
};
log::debug!("Dir string: {}", dir_string);
let substituted_dir = substitute_path(dir_string, &config.substitutions);
// Truncate the dir string to the maximum number of path components
let truncated_dir_string = truncate(substituted_dir, config.truncation_length as usize);
// Substitutions could have changed the prefix, so don't allow them and
// fish-style path contraction together
let fish_prefix = if config.fish_style_pwd_dir_length > 0 && config.substitutions.is_empty() {
// If user is using fish style path, we need to add the segment first
let contracted_home_dir = contract_path(&current_dir, &home_dir, HOME_SYMBOL);
to_fish_style(
config.fish_style_pwd_dir_length as usize,
contracted_home_dir,
&truncated_dir_string,
)
} else {
String::from("")
};
let final_dir_string = format!("{}{}", fish_prefix, truncated_dir_string);
let lock_symbol = String::from(config.read_only_symbol);
let parsed = StringFormatter::new(config.format).and_then(|formatter| {
formatter
.map_style(|variable| match variable {
"style" => Some(Ok(config.style)),
"read_only_style" => Some(Ok(config.read_only_symbol_style)),
_ => None,
})
.map(|variable| match variable {
"path" => Some(Ok(&final_dir_string)),
"read_only" => {
if is_readonly_dir(&context.current_dir) {
Some(Ok(&lock_symbol))
} else {
None
}
}
_ => None,
})
.parse(None)
});
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `directory`:\n{}", error);
return None;
}
});
Some(module)
}
fn is_readonly_dir(path: &Path) -> bool {
match directory_utils::is_write_allowed(path) {
Ok(res) => !res,
Err(e) => {
log::debug!(
"Failed to determine read only status of directory '{:?}': {}",
path,
e
);
false
}
}
}
/// Contract the root component of a path
///
/// Replaces the `top_level_path` in a given `full_path` with the provided
/// `top_level_replacement`.
fn contract_path(full_path: &Path, top_level_path: &Path, top_level_replacement: &str) -> String {
if !full_path.starts_with(top_level_path) {
return full_path.to_slash().unwrap();
}
if full_path == top_level_path {
return top_level_replacement.to_string();
}
format!(
"{replacement}{separator}{path}",
replacement = top_level_replacement,
separator = "/",
path = full_path
.strip_prefix(top_level_path)
.unwrap()
.to_slash()
.unwrap()
)
}
/// Contract the root component of a path based on the real path
///
/// Replaces the `top_level_path` in a given `full_path` with the provided
/// `top_level_replacement` by walking ancestors and comparing its real path.
fn contract_repo_path(full_path: &Path, top_level_path: &Path) -> Option<String> {
let top_level_real_path = real_path(top_level_path);
// Walk ancestors to preserve logical path in `full_path`.
// If we'd just `full_real_path.strip_prefix(top_level_real_path)`,
// then it wouldn't preserve logical path. It would've returned physical path.
for (i, ancestor) in full_path.ancestors().enumerate() {
let ancestor_real_path = real_path(ancestor);
if ancestor_real_path != top_level_real_path {
continue;
}
let components: Vec<_> = full_path.components().collect();
let repo_name = components[components.len() - i - 1].as_os_str().to_str()?;
if i == 0 {
return Some(repo_name.to_string());
}
let path = PathBuf::from_iter(&components[components.len() - i..]);
return Some(format!(
"{repo_name}{separator}{path}",
repo_name = repo_name,
separator = "/",
path = path.to_slash()?
));
}
None
}
fn real_path<P: AsRef<Path>>(path: P) -> PathBuf {
let path = path.as_ref();
let mut buf = PathBuf::new();
for component in path.components() {
let next = buf.join(component);
if let Ok(realpath) = next.read_link() {
if realpath.is_absolute() {
buf = realpath;
} else {
buf.push(realpath);
}
} else {
buf = next;
}
}
buf.canonicalize().unwrap_or_else(|_| path.into())
}
/// Perform a list of string substitutions on the path
///
/// Given a list of (from, to) pairs, this will perform the string
/// substitutions, in order, on the path. Any non-pair of strings is ignored.
fn substitute_path(dir_string: String, substitutions: &HashMap<String, &str>) -> String {
let mut substituted_dir = dir_string;
for substitution_pair in substitutions.iter() {
substituted_dir = substituted_dir.replace(substitution_pair.0, substitution_pair.1);
}
substituted_dir
}
/// Takes part before contracted path and replaces it with fish style path
///
/// Will take the first letter of each directory before the contracted path and
/// use that in the path instead. See the following example.
///
/// Absolute Path: `/Users/Bob/Projects/work/a_repo`
/// Contracted Path: `a_repo`
/// With Fish Style: `~/P/w/a_repo`
///
/// Absolute Path: `/some/Path/not/in_a/repo/but_nested`
/// Contracted Path: `in_a/repo/but_nested`
/// With Fish Style: `/s/P/n/in_a/repo/but_nested`
fn to_fish_style(pwd_dir_length: usize, dir_string: String, truncated_dir_string: &str) -> String {
let replaced_dir_string = dir_string.trim_end_matches(truncated_dir_string).to_owned();
let components = replaced_dir_string.split('/').collect::<Vec<&str>>();
if components.is_empty() {
return replaced_dir_string;
}
components
.into_iter()
.map(|word| -> String {
let chars = UnicodeSegmentation::graphemes(word, true).collect::<Vec<&str>>();
match word {
"" => "".to_string(),
_ if chars.len() <= pwd_dir_length => word.to_string(),
_ if word.starts_with('.') => chars[..=pwd_dir_length].join(""),
_ => chars[..pwd_dir_length].join(""),
}
})
.collect::<Vec<_>>()
.join("/")
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test::ModuleRenderer;
use ansi_term::Color;
use dirs_next::home_dir;
#[cfg(not(target_os = "windows"))]
use std::os::unix::fs::symlink;
#[cfg(target_os = "windows")]
use std::os::windows::fs::symlink_dir as symlink;
use std::path::Path;
use std::process::Command;
use std::{fs, io};
use tempfile::TempDir;
#[test]
fn contract_home_directory() {
let full_path = Path::new("/Users/astronaut/schematics/rocket");
let home = Path::new("/Users/astronaut");
let output = contract_path(full_path, home, "~");
assert_eq!(output, "~/schematics/rocket");
}
#[test]
fn contract_repo_directory() {
let full_path = Path::new("/Users/astronaut/dev/rocket-controls/src");
let repo_root = Path::new("/Users/astronaut/dev/rocket-controls");
let output = contract_path(full_path, repo_root, "rocket-controls");
assert_eq!(output, "rocket-controls/src");
}
#[test]
#[cfg(target_os = "windows")]
fn contract_windows_style_home_directory() {
let full_path = Path::new("C:\\Users\\astronaut\\schematics\\rocket");
let home = Path::new("C:\\Users\\astronaut");
let output = contract_path(full_path, home, "~");
assert_eq!(output, "~/schematics/rocket");
}
#[test]
#[cfg(target_os = "windows")]
fn contract_windows_style_repo_directory() {
let full_path = Path::new("C:\\Users\\astronaut\\dev\\rocket-controls\\src");
let repo_root = Path::new("C:\\Users\\astronaut\\dev\\rocket-controls");
let output = contract_path(full_path, repo_root, "rocket-controls");
assert_eq!(output, "rocket-controls/src");
}
#[test]
#[cfg(target_os = "windows")]
fn contract_windows_style_no_top_level_directory() {
let full_path = Path::new("C:\\Some\\Other\\Path");
let top_level_path = Path::new("C:\\Users\\astronaut");
let output = contract_path(full_path, top_level_path, "~");
assert_eq!(output, "C:/Some/Other/Path");
}
#[test]
#[cfg(target_os = "windows")]
fn contract_windows_style_root_directory() {
let full_path = Path::new("C:\\");
let top_level_path = Path::new("C:\\Users\\astronaut");
let output = contract_path(full_path, top_level_path, "~");
assert_eq!(output, "C:");
}
#[test]
fn substitute_prefix_and_middle() {
let full_path = "/absolute/path/foo/bar/baz";
let mut substitutions = HashMap::new();
substitutions.insert("/absolute/path".to_string(), "");
substitutions.insert("/bar/".to_string(), "/");
let output = substitute_path(full_path.to_string(), &substitutions);
assert_eq!(output, "/foo/baz");
}
#[test]
fn fish_style_with_user_home_contracted_path() {
let path = "~/starship/engines/booster/rocket";
let output = to_fish_style(1, path.to_string(), "engines/booster/rocket");
assert_eq!(output, "~/s/");
}
#[test]
fn fish_style_with_user_home_contracted_path_and_dot_dir() {
let path = "~/.starship/engines/booster/rocket";
let output = to_fish_style(1, path.to_string(), "engines/booster/rocket");
assert_eq!(output, "~/.s/");
}
#[test]
fn fish_style_with_no_contracted_path() {
// `truncation_length = 2`
let path = "/absolute/Path/not/in_a/repo/but_nested";
let output = to_fish_style(1, path.to_string(), "repo/but_nested");
assert_eq!(output, "/a/P/n/i/");
}
#[test]
fn fish_style_with_pwd_dir_len_no_contracted_path() {
// `truncation_length = 2`
let path = "/absolute/Path/not/in_a/repo/but_nested";
let output = to_fish_style(2, path.to_string(), "repo/but_nested");
assert_eq!(output, "/ab/Pa/no/in/");
}
#[test]
fn fish_style_with_duplicate_directories() {
let path = "~/starship/tmp/C++/C++/C++";
let output = to_fish_style(1, path.to_string(), "C++");
assert_eq!(output, "~/s/t/C/C/");
}
#[test]
fn fish_style_with_unicode() {
let path = "~/starship/tmp/目录/a̐éö̲/目录";
let output = to_fish_style(1, path.to_string(), "目录");
assert_eq!(output, "~/s/t/目/a̐/");
}
fn init_repo(path: &Path) -> io::Result<()> {
Command::new("git")
.args(&["init"])
.current_dir(path)
.output()
.map(|_| ())
}
fn make_known_tempdir(root: &Path) -> io::Result<(TempDir, String)> {
fs::create_dir_all(root)?;
let dir = TempDir::new_in(root)?;
let path = dir
.path()
.file_name()
.unwrap()
.to_string_lossy()
.to_string();
Ok((dir, path))
}
#[cfg(not(target_os = "windows"))]
mod linux {
use super::*;
use std::sync::atomic::{AtomicBool, Ordering};
// As tests are run in parallel we have to keep a lock on which of the
// two tests are currently running as they both modify `HOME` which can
// override the other value resulting in inconsistent runs which is why
// we only run one of these tests at once.
static LOCK: AtomicBool = AtomicBool::new(false);
#[test]
#[ignore]
fn symlinked_subdirectory_git_repo_out_of_tree() -> io::Result<()> {
while LOCK.swap(true, Ordering::Acquire) {}
let tmp_dir = TempDir::new_in(home_dir().unwrap().as_path())?;
let repo_dir = tmp_dir.path().join("above-repo").join("rocket-controls");
let src_dir = repo_dir.join("src/meters/fuel-gauge");
let symlink_dir = tmp_dir.path().join("fuel-gauge");
fs::create_dir_all(&src_dir)?;
init_repo(&repo_dir)?;
symlink(&src_dir, &symlink_dir)?;
// We can't mock `HOME` since dirs-next uses it which does not care about our mocking
let previous_home = home_dir().unwrap();
std::env::set_var("HOME", tmp_dir.path());
let actual = ModuleRenderer::new("directory").path(symlink_dir).collect();
let expected = Some(format!("{} ", Color::Cyan.bold().paint("~/fuel-gauge")));
std::env::set_var("HOME", previous_home.as_path());
assert_eq!(expected, actual);
LOCK.store(false, Ordering::Release);
tmp_dir.close()
}
#[test]
#[ignore]
fn git_repo_in_home_directory_truncate_to_repo_true() -> io::Result<()> {
while LOCK.swap(true, Ordering::Acquire) {}
let tmp_dir = TempDir::new_in(home_dir().unwrap().as_path())?;
let dir = tmp_dir.path().join("src/fuel-gauge");
fs::create_dir_all(&dir)?;
init_repo(&tmp_dir.path())?;
// We can't mock `HOME` since dirs-next uses it which does not care about our mocking
let previous_home = home_dir().unwrap();
std::env::set_var("HOME", tmp_dir.path());
let actual = ModuleRenderer::new("directory")
.config(toml::toml! {
[directory]
// `truncate_to_repo = true` should attempt to display the truncated path
truncate_to_repo = true
truncation_length = 5
})
.path(dir)
.collect();
let expected = Some(format!("{} ", Color::Cyan.bold().paint("~/src/fuel-gauge")));
std::env::set_var("HOME", previous_home.as_path());
assert_eq!(expected, actual);
LOCK.store(false, Ordering::Release);
tmp_dir.close()
}
#[test]
fn directory_in_root() -> io::Result<()> {
let actual = ModuleRenderer::new("directory").path("/etc").collect();
let expected = Some(format!(
"{}{} ",
Color::Cyan.bold().paint("/etc"),
Color::Red.normal().paint("🔒")
));
assert_eq!(expected, actual);
Ok(())
}
}
#[test]
fn home_directory() -> io::Result<()> {
let actual = ModuleRenderer::new("directory")
.path(home_dir().unwrap())
.config(toml::toml! { // Necessary if homedir is a git repo
[directory]
truncate_to_repo = false
})
.collect();
let expected = Some(format!("{} ", Color::Cyan.bold().paint("~")));
assert_eq!(expected, actual);
Ok(())
}
#[test]
fn substituted_truncated_path() -> io::Result<()> {
let actual = ModuleRenderer::new("directory")
.path("/some/long/network/path/workspace/a/b/c/dev")
.config(toml::toml! {
[directory]
truncation_length = 4
[directory.substitutions]
"/some/long/network/path" = "/some/net"
"a/b/c" = "d"
})
.collect();
let expected = Some(format!(
"{} ",
Color::Cyan.bold().paint("net/workspace/d/dev")
));
assert_eq!(expected, actual);
Ok(())
}
#[test]
fn strange_substitution() -> io::Result<()> {
let strange_sub = "/\\/;,!";
let actual = ModuleRenderer::new("directory")
.path("/foo/bar/regular/path")
.config(toml::toml! {
[directory]
truncation_length = 0
fish_style_pwd_dir_length = 2 // Overridden by substitutions
[directory.substitutions]
"regular" = strange_sub
})
.collect();
let expected = Some(format!(
"{} ",
Color::Cyan
.bold()
.paint(format!("/foo/bar/{}/path", strange_sub))
));
assert_eq!(expected, actual);
Ok(())
}
#[test]
fn directory_in_home() -> io::Result<()> {
let (tmp_dir, name) = make_known_tempdir(home_dir().unwrap().as_path())?;
let dir = tmp_dir.path().join("starship");
fs::create_dir_all(&dir)?;
let actual = ModuleRenderer::new("directory").path(dir).collect();
let expected = Some(format!(
"{} ",
Color::Cyan.bold().paint(format!("~/{}/starship", name))
));
assert_eq!(expected, actual);
tmp_dir.close()
}
#[test]
fn truncated_directory_in_home() -> io::Result<()> {
let (tmp_dir, name) = make_known_tempdir(home_dir().unwrap().as_path())?;
let dir = tmp_dir.path().join("engine/schematics");
fs::create_dir_all(&dir)?;
let actual = ModuleRenderer::new("directory").path(dir).collect();
let expected = Some(format!(
"{} ",
Color::Cyan
.bold()
.paint(format!("{}/engine/schematics", name))
));
assert_eq!(expected, actual);
tmp_dir.close()
}
#[test]
fn fish_directory_in_home() -> io::Result<()> {
let (tmp_dir, name) = make_known_tempdir(home_dir().unwrap().as_path())?;
let dir = tmp_dir.path().join("starship/schematics");
fs::create_dir_all(&dir)?;
let actual = ModuleRenderer::new("directory")
.config(toml::toml! {
[directory]
truncation_length = 1
fish_style_pwd_dir_length = 2
})
.path(&dir)
.collect();
let expected = Some(format!(
"{} ",
Color::Cyan
.bold()
.paint(format!("~/{}/st/schematics", name.split_at(3).0))
));
assert_eq!(expected, actual);
tmp_dir.close()
}
#[test]
fn root_directory() -> io::Result<()> {
let actual = ModuleRenderer::new("directory").path("/").collect();
#[cfg(not(target_os = "windows"))]
let expected = Some(format!(
"{}{} ",
Color::Cyan.bold().paint("/"),
Color::Red.normal().paint("🔒")
));
#[cfg(target_os = "windows")]
let expected = Some(format!("{} ", Color::Cyan.bold().paint("/")));
assert_eq!(expected, actual);
Ok(())
}
#[test]
fn truncated_directory_in_root() -> io::Result<()> {
let (tmp_dir, name) = make_known_tempdir(Path::new("/tmp"))?;
let dir = tmp_dir.path().join("thrusters/rocket");
fs::create_dir_all(&dir)?;
let actual = ModuleRenderer::new("directory").path(dir).collect();
let expected = Some(format!(
"{} ",
Color::Cyan
.bold()
.paint(format!("{}/thrusters/rocket", name))
));
assert_eq!(expected, actual);
tmp_dir.close()
}
#[test]
fn truncated_directory_config_large() -> io::Result<()> {
let (tmp_dir, _) = make_known_tempdir(Path::new("/tmp"))?;
let dir = tmp_dir.path().join("thrusters/rocket");
fs::create_dir_all(&dir)?;
let actual = ModuleRenderer::new("directory")
.config(toml::toml! {
[directory]
truncation_length = 100
})
.path(&dir)
.collect();
let expected = Some(format!(
"{} ",
Color::Cyan
.bold()
.paint(truncate(dir.to_slash_lossy(), 100))
));
assert_eq!(expected, actual);
tmp_dir.close()
}
#[test]
fn fish_style_directory_config_large() -> io::Result<()> {
let (tmp_dir, _) = make_known_tempdir(Path::new("/tmp"))?;
let dir = tmp_dir.path().join("thrusters/rocket");
fs::create_dir_all(&dir)?;
let actual = ModuleRenderer::new("directory")
.config(toml::toml! {
[directory]
truncation_length = 1
fish_style_pwd_dir_length = 100
})
.path(&dir)
.collect();
let expected = Some(format!(
"{} ",
Color::Cyan
.bold()
.paint(to_fish_style(100, dir.to_slash_lossy(), ""))
));
assert_eq!(expected, actual);
tmp_dir.close()
}
#[test]
fn truncated_directory_config_small() -> io::Result<()> {
let (tmp_dir, name) = make_known_tempdir(Path::new("/tmp"))?;
let dir = tmp_dir.path().join("rocket");
fs::create_dir_all(&dir)?;
let actual = ModuleRenderer::new("directory")
.config(toml::toml! {
[directory]
truncation_length = 2
})
.path(dir)
.collect();
let expected = Some(format!(
"{} ",
Color::Cyan.bold().paint(format!("{}/rocket", name))
));
assert_eq!(expected, actual);
tmp_dir.close()
}
#[test]
fn fish_directory_config_small() -> io::Result<()> {
let (tmp_dir, _) = make_known_tempdir(Path::new("/tmp"))?;
let dir = tmp_dir.path().join("thrusters/rocket");
fs::create_dir_all(&dir)?;
let actual = ModuleRenderer::new("directory")
.config(toml::toml! {
[directory]
truncation_length = 2
fish_style_pwd_dir_length = 1
})
.path(&dir)
.collect();
let expected = Some(format!(
"{} ",
Color::Cyan.bold().paint(format!(
"{}/thrusters/rocket",
to_fish_style(1, dir.to_slash_lossy(), "/thrusters/rocket")
))
));
assert_eq!(expected, actual);
tmp_dir.close()
}
#[test]
#[ignore]
fn git_repo_root() -> io::Result<()> {
let tmp_dir = TempDir::new()?;
let repo_dir = tmp_dir.path().join("rocket-controls");
fs::create_dir(&repo_dir)?;
init_repo(&repo_dir).unwrap();
let actual = ModuleRenderer::new("directory").path(repo_dir).collect();
let expected = Some(format!("{} ", Color::Cyan.bold().paint("rocket-controls")));
assert_eq!(expected, actual);
tmp_dir.close()
}
#[test]
#[ignore]
fn directory_in_git_repo() -> io::Result<()> {
let tmp_dir = TempDir::new()?;
let repo_dir = tmp_dir.path().join("rocket-controls");
let dir = repo_dir.join("src");
fs::create_dir_all(&dir)?;
init_repo(&repo_dir).unwrap();
let actual = ModuleRenderer::new("directory").path(dir).collect();
let expected = Some(format!(
"{} ",
Color::Cyan.bold().paint("rocket-controls/src")
));
assert_eq!(expected, actual);
tmp_dir.close()
}
#[test]
#[ignore]
fn truncated_directory_in_git_repo() -> io::Result<()> {
let tmp_dir = TempDir::new()?;
let repo_dir = tmp_dir.path().join("rocket-controls");
let dir = repo_dir.join("src/meters/fuel-gauge");
fs::create_dir_all(&dir)?;
init_repo(&repo_dir).unwrap();
let actual = ModuleRenderer::new("directory").path(dir).collect();
let expected = Some(format!(
"{} ",
Color::Cyan.bold().paint("src/meters/fuel-gauge")
));
assert_eq!(expected, actual);
tmp_dir.close()
}
#[test]
#[ignore]
fn directory_in_git_repo_truncate_to_repo_false() -> io::Result<()> {
let tmp_dir = TempDir::new()?;
let repo_dir = tmp_dir.path().join("above-repo").join("rocket-controls");
let dir = repo_dir.join("src/meters/fuel-gauge");
fs::create_dir_all(&dir)?;
init_repo(&repo_dir).unwrap();
let actual = ModuleRenderer::new("directory")
.config(toml::toml! {
[directory]
// Don't truncate the path at all.
truncation_length = 5
truncate_to_repo = false
})
.path(dir)
.collect();
let expected = Some(format!(
"{} ",
Color::Cyan
.bold()
.paint("above-repo/rocket-controls/src/meters/fuel-gauge")
));
assert_eq!(expected, actual);
tmp_dir.close()
}
#[test]
#[ignore]
fn fish_path_directory_in_git_repo_truncate_to_repo_false() -> io::Result<()> {
let (tmp_dir, _) = make_known_tempdir(Path::new("/tmp"))?;
let repo_dir = tmp_dir.path().join("above-repo").join("rocket-controls");
let dir = repo_dir.join("src/meters/fuel-gauge");
fs::create_dir_all(&dir)?;
init_repo(&repo_dir).unwrap();
let actual = ModuleRenderer::new("directory")
.config(toml::toml! {
[directory]
// Don't truncate the path at all.
truncation_length = 5
truncate_to_repo = false
fish_style_pwd_dir_length = 1
})
.path(dir)
.collect();
let expected = Some(format!(
"{} ",
Color::Cyan.bold().paint(format!(
"{}/above-repo/rocket-controls/src/meters/fuel-gauge",
to_fish_style(1, tmp_dir.path().to_slash_lossy(), "")
))
));
assert_eq!(expected, actual);
tmp_dir.close()
}
#[test]
#[ignore]
fn fish_path_directory_in_git_repo_truncate_to_repo_true() -> io::Result<()> {
let (tmp_dir, _) = make_known_tempdir(Path::new("/tmp"))?;
let repo_dir = tmp_dir.path().join("above-repo").join("rocket-controls");
let dir = repo_dir.join("src/meters/fuel-gauge");
fs::create_dir_all(&dir)?;
init_repo(&repo_dir).unwrap();
let actual = ModuleRenderer::new("directory")
.config(toml::toml! {
[directory]
// `truncate_to_repo = true` should display the truncated path
truncation_length = 5
truncate_to_repo = true
fish_style_pwd_dir_length = 1
})
.path(dir)
.collect();
let expected = Some(format!(
"{} ",
Color::Cyan.bold().paint(format!(
"{}/rocket-controls/src/meters/fuel-gauge",
to_fish_style(1, tmp_dir.path().join("above-repo").to_slash_lossy(), "")
))
));
assert_eq!(expected, actual);
tmp_dir.close()
}
#[test]
#[ignore]
fn directory_in_git_repo_truncate_to_repo_true() -> io::Result<()> {
let (tmp_dir, _) = make_known_tempdir(Path::new("/tmp"))?;
let repo_dir = tmp_dir.path().join("above-repo").join("rocket-controls");
let dir = repo_dir.join("src/meters/fuel-gauge");
fs::create_dir_all(&dir)?;
init_repo(&repo_dir).unwrap();
let actual = ModuleRenderer::new("directory")
.config(toml::toml! {
[directory]
// `truncate_to_repo = true` should display the truncated path
truncation_length = 5
truncate_to_repo = true
})
.path(dir)
.collect();
let expected = Some(format!(
"{} ",
Color::Cyan
.bold()
.paint("rocket-controls/src/meters/fuel-gauge")
));
assert_eq!(expected, actual);
tmp_dir.close()
}
#[test]
#[ignore]
fn symlinked_git_repo_root() -> io::Result<()> {
let (tmp_dir, _) = make_known_tempdir(Path::new("/tmp"))?;
let repo_dir = tmp_dir.path().join("rocket-controls");
let symlink_dir = tmp_dir.path().join("rocket-controls-symlink");
fs::create_dir(&repo_dir)?;
init_repo(&repo_dir).unwrap();
symlink(&repo_dir, &symlink_dir)?;
let actual = ModuleRenderer::new("directory").path(symlink_dir).collect();
let expected = Some(format!(
"{} ",
Color::Cyan.bold().paint("rocket-controls-symlink")
));
assert_eq!(expected, actual);
tmp_dir.close()
}
#[test]
#[ignore]
fn directory_in_symlinked_git_repo() -> io::Result<()> {
let (tmp_dir, _) = make_known_tempdir(Path::new("/tmp"))?;
let repo_dir = tmp_dir.path().join("rocket-controls");
let src_dir = repo_dir.join("src");
let symlink_dir = tmp_dir.path().join("rocket-controls-symlink");
let symlink_src_dir = symlink_dir.join("src");
fs::create_dir_all(&src_dir)?;
init_repo(&repo_dir).unwrap();
symlink(&repo_dir, &symlink_dir)?;
let actual = ModuleRenderer::new("directory")
.path(symlink_src_dir)
.collect();
let expected = Some(format!(
"{} ",
Color::Cyan.bold().paint("rocket-controls-symlink/src")
));
assert_eq!(expected, actual);
tmp_dir.close()
}
#[test]
#[ignore]
fn truncated_directory_in_symlinked_git_repo() -> io::Result<()> {
let (tmp_dir, _) = make_known_tempdir(Path::new("/tmp"))?;
let repo_dir = tmp_dir.path().join("rocket-controls");
let src_dir = repo_dir.join("src/meters/fuel-gauge");
let symlink_dir = tmp_dir.path().join("rocket-controls-symlink");
let symlink_src_dir = symlink_dir.join("src/meters/fuel-gauge");
fs::create_dir_all(&src_dir)?;
init_repo(&repo_dir).unwrap();
symlink(&repo_dir, &symlink_dir)?;
let actual = ModuleRenderer::new("directory")
.path(symlink_src_dir)
.collect();
let expected = Some(format!(
"{} ",
Color::Cyan.bold().paint("src/meters/fuel-gauge")
));
assert_eq!(expected, actual);
tmp_dir.close()
}
#[test]
#[ignore]
fn directory_in_symlinked_git_repo_truncate_to_repo_false() -> io::Result<()> {
let (tmp_dir, _) = make_known_tempdir(Path::new("/tmp"))?;
let repo_dir = tmp_dir.path().join("above-repo").join("rocket-controls");
let src_dir = repo_dir.join("src/meters/fuel-gauge");
let symlink_dir = tmp_dir
.path()
.join("above-repo")
.join("rocket-controls-symlink");
let symlink_src_dir = symlink_dir.join("src/meters/fuel-gauge");
fs::create_dir_all(&src_dir)?;
init_repo(&repo_dir).unwrap();
symlink(&repo_dir, &symlink_dir)?;
let actual = ModuleRenderer::new("directory")
.config(toml::toml! {
[directory]
// Don't truncate the path at all.
truncation_length = 5
truncate_to_repo = false
})
.path(symlink_src_dir)
.collect();
let expected = Some(format!(
"{} ",
Color::Cyan
.bold()
.paint("above-repo/rocket-controls-symlink/src/meters/fuel-gauge")
));
assert_eq!(expected, actual);
tmp_dir.close()
}
#[test]
#[ignore]
fn fish_path_directory_in_symlinked_git_repo_truncate_to_repo_false() -> io::Result<()> {
let (tmp_dir, _) = make_known_tempdir(Path::new("/tmp"))?;
let repo_dir = tmp_dir.path().join("above-repo").join("rocket-controls");
let src_dir = repo_dir.join("src/meters/fuel-gauge");
let symlink_dir = tmp_dir
.path()
.join("above-repo")
.join("rocket-controls-symlink");
let symlink_src_dir = symlink_dir.join("src/meters/fuel-gauge");
fs::create_dir_all(&src_dir)?;
init_repo(&repo_dir).unwrap();
symlink(&repo_dir, &symlink_dir)?;
let actual = ModuleRenderer::new("directory")
.config(toml::toml! {
[directory]
// Don't truncate the path at all.
truncation_length = 5
truncate_to_repo = false
fish_style_pwd_dir_length = 1
})
.path(symlink_src_dir)
.collect();
let expected = Some(format!(
"{} ",
Color::Cyan.bold().paint(format!(
"{}/above-repo/rocket-controls-symlink/src/meters/fuel-gauge",
to_fish_style(1, tmp_dir.path().to_slash_lossy(), "")
))
));
assert_eq!(expected, actual);
tmp_dir.close()
}
#[test]
#[ignore]
fn fish_path_directory_in_symlinked_git_repo_truncate_to_repo_true() -> io::Result<()> {
let (tmp_dir, _) = make_known_tempdir(Path::new("/tmp"))?;
let repo_dir = tmp_dir.path().join("above-repo").join("rocket-controls");
let src_dir = repo_dir.join("src/meters/fuel-gauge");
let symlink_dir = tmp_dir
.path()
.join("above-repo")
.join("rocket-controls-symlink");
let symlink_src_dir = symlink_dir.join("src/meters/fuel-gauge");
fs::create_dir_all(&src_dir)?;
init_repo(&repo_dir).unwrap();
symlink(&repo_dir, &symlink_dir)?;
let actual = ModuleRenderer::new("directory")
.config(toml::toml! {
[directory]
// `truncate_to_repo = true` should display the truncated path
truncation_length = 5
truncate_to_repo = true
fish_style_pwd_dir_length = 1
})
.path(symlink_src_dir)
.collect();
let expected = Some(format!(
"{} ",
Color::Cyan.bold().paint(format!(
"{}/rocket-controls-symlink/src/meters/fuel-gauge",
to_fish_style(1, tmp_dir.path().join("above-repo").to_slash_lossy(), "")
))
));
assert_eq!(expected, actual);
tmp_dir.close()
}
#[test]
#[ignore]
fn directory_in_symlinked_git_repo_truncate_to_repo_true() -> io::Result<()> {
let (tmp_dir, _) = make_known_tempdir(Path::new("/tmp"))?;
let repo_dir = tmp_dir.path().join("above-repo").join("rocket-controls");
let src_dir = repo_dir.join("src/meters/fuel-gauge");
let symlink_dir = tmp_dir
.path()
.join("above-repo")
.join("rocket-controls-symlink");
let symlink_src_dir = symlink_dir.join("src/meters/fuel-gauge");
fs::create_dir_all(&src_dir)?;
init_repo(&repo_dir).unwrap();
symlink(&repo_dir, &symlink_dir)?;
let actual = ModuleRenderer::new("directory")
.config(toml::toml! {
[directory]
// `truncate_to_repo = true` should display the truncated path
truncation_length = 5
truncate_to_repo = true
})
.path(symlink_src_dir)
.collect();
let expected = Some(format!(
"{} ",
Color::Cyan
.bold()
.paint("rocket-controls-symlink/src/meters/fuel-gauge")
));
assert_eq!(expected, actual);
tmp_dir.close()
}
#[test]
#[ignore]
fn symlinked_directory_in_git_repo() -> io::Result<()> {
let (tmp_dir, _) = make_known_tempdir(Path::new("/tmp"))?;
let repo_dir = tmp_dir.path().join("rocket-controls");
let dir = repo_dir.join("src");
fs::create_dir_all(&dir)?;
init_repo(&repo_dir).unwrap();
symlink(&dir, repo_dir.join("src/loop"))?;
let actual = ModuleRenderer::new("directory")
.config(toml::toml! {
[directory]
// `truncate_to_repo = true` should display the truncated path
truncation_length = 5
truncate_to_repo = true
})
.path(repo_dir.join("src/loop/loop"))
.collect();
let expected = Some(format!(
"{} ",
Color::Cyan.bold().paint("rocket-controls/src/loop/loop")
));
assert_eq!(expected, actual);
tmp_dir.close()
}
}