add $nu.data-dir for completions and $nu.cache-dir for other uses (#13122)

# Description

This PR is an attempt to add a standard location for people to put
completions in. I saw this topic come up again recently and IIRC we
decided to create a standard location. I used the dirs-next crate to
dictate where these locations are. I know some people won't like that
but at least this gets the ball rolling in a direction that has a
standard directory.

This is what the default NU_LIB_DIRS looks like now in the
default_env.nu. It should also be like this when starting nushell with
`nu -n`
```nushell
$env.NU_LIB_DIRS = [
    ($nu.default-config-dir | path join 'scripts') # add <nushell-config-dir>/scripts
    ($nu.data-dir | path join 'completions') # default home for nushell completions
]
```

I also added these default folders to the `$nu` variable so now there is
`$nu.data-path` and `$nu.cache-path`.

## Data Dir Default

![image](https://github.com/nushell/nushell/assets/343840/aeeb7cd6-17b4-43e8-bb6f-986a0c7fce23)

While I was in there, I also decided to add a cache dir

## Cache Dir Default

![image](https://github.com/nushell/nushell/assets/343840/87dead66-4911-4f67-bfb2-acb16f386674)

### This is what the default looks like in Ubuntu.

![image](https://github.com/nushell/nushell/assets/343840/bca8eae8-8c18-47e8-b64f-3efe34f0004f)

### This is what it looks like with XDG_CACHE_HOME and XDG_DATA_HOME
overridden
```nushell
XDG_DATA_HOME=/tmp/data_home XDG_CACHE_HOME=/tmp/cache_home cargo r
```

![image](https://github.com/nushell/nushell/assets/343840/fae86d50-9821-41f1-868e-3814eca3730b)

### This is what the defaults look like in Windows (username scrubbed to
protect the innocent)

![image](https://github.com/nushell/nushell/assets/343840/3ebdb5cd-0150-448c-aff5-c57053e4788a)

How my NU_LIB_DIRS is set in the images above
```nushell
$env.NU_LIB_DIRS = [
    ($nu.default-config-dir | path join 'scripts') # add <nushell-config-dir>/scripts
    '/Users/fdncred/src/nu_scripts'
    ($nu.config-path | path dirname)
    ($nu.data-dir | path join 'completions') # default home for nushell completions
]
```

Let the debate begin.

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
This commit is contained in:
Darren Schroeder 2024-06-11 14:10:31 -05:00 committed by GitHub
parent b0d1b4b182
commit 0372e8c53c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 75 additions and 10 deletions

View file

@ -195,6 +195,7 @@ reedline = { workspace = true, features = ["bashisms", "sqlite"] }
crossterm = { workspace = true } crossterm = { workspace = true }
ctrlc = { workspace = true } ctrlc = { workspace = true }
dirs-next = { workspace = true }
log = { workspace = true } log = { workspace = true }
miette = { workspace = true, features = ["fancy-no-backtrace", "fancy"] } miette = { workspace = true, features = ["fancy-no-backtrace", "fancy"] }
mimalloc = { version = "0.1.42", default-features = false, optional = true } mimalloc = { version = "0.1.42", default-features = false, optional = true }

View file

@ -763,11 +763,13 @@ fn variables_completions() {
// Test completions for $nu // Test completions for $nu
let suggestions = completer.complete("$nu.", 4); let suggestions = completer.complete("$nu.", 4);
assert_eq!(15, suggestions.len()); assert_eq!(17, suggestions.len());
let expected: Vec<String> = vec![ let expected: Vec<String> = vec![
"cache-dir".into(),
"config-path".into(), "config-path".into(),
"current-exe".into(), "current-exe".into(),
"data-dir".into(),
"default-config-dir".into(), "default-config-dir".into(),
"env-path".into(), "env-path".into(),
"history-enabled".into(), "history-enabled".into(),

View file

@ -6,18 +6,36 @@ pub fn home_dir() -> Option<PathBuf> {
dirs_next::home_dir() dirs_next::home_dir()
} }
/// Return the data directory for the current platform or XDG_DATA_HOME if specified.
pub fn data_dir() -> Option<PathBuf> {
match std::env::var("XDG_DATA_HOME").map(PathBuf::from) {
Ok(xdg_data) if xdg_data.is_absolute() => Some(canonicalize(&xdg_data).unwrap_or(xdg_data)),
_ => get_canonicalized_path(dirs_next::data_dir()),
}
}
/// Return the cache directory for the current platform or XDG_CACHE_HOME if specified.
pub fn cache_dir() -> Option<PathBuf> {
match std::env::var("XDG_CACHE_HOME").map(PathBuf::from) {
Ok(xdg_cache) if xdg_cache.is_absolute() => {
Some(canonicalize(&xdg_cache).unwrap_or(xdg_cache))
}
_ => get_canonicalized_path(dirs_next::cache_dir()),
}
}
/// Return the config directory for the current platform or XDG_CONFIG_HOME if specified.
pub fn config_dir() -> Option<PathBuf> { pub fn config_dir() -> Option<PathBuf> {
match std::env::var("XDG_CONFIG_HOME").map(PathBuf::from) { match std::env::var("XDG_CONFIG_HOME").map(PathBuf::from) {
Ok(xdg_config) if xdg_config.is_absolute() => { Ok(xdg_config) if xdg_config.is_absolute() => {
Some(canonicalize(&xdg_config).unwrap_or(xdg_config)) Some(canonicalize(&xdg_config).unwrap_or(xdg_config))
} }
_ => config_dir_old(), _ => get_canonicalized_path(dirs_next::config_dir()),
} }
} }
/// Get the old default config directory. Outside of Linux, this will ignore `XDG_CONFIG_HOME` pub fn get_canonicalized_path(path: Option<PathBuf>) -> Option<PathBuf> {
pub fn config_dir_old() -> Option<PathBuf> { let path = path?;
let path = dirs_next::config_dir()?;
Some(canonicalize(&path).unwrap_or(path)) Some(canonicalize(&path).unwrap_or(path))
} }

View file

@ -8,6 +8,6 @@ mod trailing_slash;
pub use components::components; pub use components::components;
pub use expansions::{canonicalize_with, expand_path_with, expand_to_real_path, locate_in_dirs}; pub use expansions::{canonicalize_with, expand_path_with, expand_to_real_path, locate_in_dirs};
pub use helpers::{config_dir, config_dir_old, home_dir}; pub use helpers::{cache_dir, config_dir, data_dir, get_canonicalized_path, home_dir};
pub use tilde::expand_tilde; pub use tilde::expand_tilde;
pub use trailing_slash::{has_trailing_slash, strip_trailing_slash}; pub use trailing_slash::{has_trailing_slash, strip_trailing_slash};

View file

@ -149,6 +149,38 @@ pub(crate) fn create_nu_constant(engine_state: &EngineState, span: Span) -> Valu
}, },
); );
record.push(
"data-dir",
if let Some(path) = nu_path::data_dir() {
let mut canon_data_path = canonicalize_path(engine_state, &path);
canon_data_path.push("nushell");
Value::string(canon_data_path.to_string_lossy(), span)
} else {
Value::error(
ShellError::IOError {
msg: "Could not get data path".into(),
},
span,
)
},
);
record.push(
"cache-dir",
if let Some(path) = nu_path::cache_dir() {
let mut canon_cache_path = canonicalize_path(engine_state, &path);
canon_cache_path.push("nushell");
Value::string(canon_cache_path.to_string_lossy(), span)
} else {
Value::error(
ShellError::IOError {
msg: "Could not get cache path".into(),
},
span,
)
},
);
record.push("temp-path", { record.push("temp-path", {
let canon_temp_path = canonicalize_path(engine_state, &std::env::temp_dir()); let canon_temp_path = canonicalize_path(engine_state, &std::env::temp_dir());
Value::string(canon_temp_path.to_string_lossy(), span) Value::string(canon_temp_path.to_string_lossy(), span)

View file

@ -77,6 +77,7 @@ $env.ENV_CONVERSIONS = {
# The default for this is $nu.default-config-dir/scripts # The default for this is $nu.default-config-dir/scripts
$env.NU_LIB_DIRS = [ $env.NU_LIB_DIRS = [
($nu.default-config-dir | path join 'scripts') # add <nushell-config-dir>/scripts ($nu.default-config-dir | path join 'scripts') # add <nushell-config-dir>/scripts
($nu.data-dir | path join 'completions') # default home for nushell completions
] ]
# Directories to search for plugin binaries when calling register # Directories to search for plugin binaries when calling register

View file

@ -104,7 +104,9 @@ fn main() -> Result<()> {
default: nushell_config_path.display().to_string(), default: nushell_config_path.display().to_string(),
}, },
); );
} else if let Some(old_config) = nu_path::config_dir_old().map(|p| p.join("nushell")) { } else if let Some(old_config) =
nu_path::get_canonicalized_path(dirs_next::config_dir()).map(|p| p.join("nushell"))
{
let xdg_config_empty = nushell_config_path let xdg_config_empty = nushell_config_path
.read_dir() .read_dir()
.map_or(true, |mut dir| dir.next().is_none()); .map_or(true, |mut dir| dir.next().is_none());
@ -125,13 +127,22 @@ fn main() -> Result<()> {
} }
} }
let default_nushell_completions_path = if let Some(mut path) = nu_path::data_dir() {
path.push("nushell");
path.push("completions");
path
} else {
std::path::PathBuf::new()
};
let mut default_nu_lib_dirs_path = nushell_config_path.clone(); let mut default_nu_lib_dirs_path = nushell_config_path.clone();
default_nu_lib_dirs_path.push("scripts"); default_nu_lib_dirs_path.push("scripts");
engine_state.add_env_var( engine_state.add_env_var(
"NU_LIB_DIRS".to_string(), "NU_LIB_DIRS".to_string(),
Value::test_list(vec![Value::test_string( Value::test_list(vec![
default_nu_lib_dirs_path.to_string_lossy(), Value::test_string(default_nu_lib_dirs_path.to_string_lossy()),
)]), Value::test_string(default_nushell_completions_path.to_string_lossy()),
]),
); );
let mut default_nu_plugin_dirs_path = nushell_config_path; let mut default_nu_plugin_dirs_path = nushell_config_path;