Add decimals to int when using into string --decimals (#6085)

* Add decimals to int when using `into string --decimals`

* Add tests for `into string` when converting int with `--decimals`

* Apply formatting

* Merge `into_str` test files

* Comment out unused code and add TODOs

* Use decimal separator depending on system locale

* Add test helper to run closure in different locale

* Add tests for int-to-string conversion using different locales

* Add utils function to get system locale

* Add panic message when locking mutex fails

* Catch and resume panic later to prevent Mutex poisoning when test fails

* Move test to `nu-test-support` to keep `nu-utils` free of `nu-*` dependencies

See https://github.com/nushell/nushell/pull/6085#issuecomment-1193131694

* Rename test support fn `with_fake_locale` to `with_locale_override`

* Move `get_system_locale()` to `locale` module

* Allow overriding locale with special env variable (when not in release)

* Use special env var to override locale during testing

* Allow callback to return a value in `with_locale_override()`

* Allow multiple options in `nu!` macro

* Allow to set locale as `nu!` macro option

* Use new `locale` option of `nu!` macro instead of `with_locale_override`

Using the `locale` options does not lock the `LOCALE_OVERRIDE_MUTEX`
mutex in `nu-test-support::locale_override` but instead calls the `nu`
command directly with the `NU_LOCALE_OVERRIDE` environment variable.
This allows for parallel test excecution.

* Fix: Add option identifier for `cwd` in usage of `nu!` macro

* Rely on `Display` trait for formatting `nu!` macro command

- Removed the `DisplayPath` trait
- Implement `Display` for `AbsolutePath`, `RelativePath` and
  `AbsoluteFile`

* Default to locale `en_US.UTF-8` for tests when using `nu!` macro

* Add doc comment to `nu!` macro

* Format code using `cargo fmt --all`

* Pass function directly instead of wrapping the call in a closure

https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure

* Pass function to `or_else()` instead of calling it inside `or()`

https://rust-lang.github.io/rust-clippy/master/index.html#or_fun_call

* Fix: Add option identifier for `cwd` in usage of `nu!` macro
This commit is contained in:
Björn Richter 2022-08-13 04:13:50 +02:00 committed by GitHub
parent ccebdd7a7f
commit cb18dd5200
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 390 additions and 122 deletions

6
Cargo.lock generated
View file

@ -2636,6 +2636,7 @@ dependencies = [
"nu-test-support",
"nu-utils",
"num 0.4.0",
"num-format",
"num-traits",
"pathdiff",
"polars",
@ -2814,9 +2815,12 @@ version = "0.66.4"
dependencies = [
"getset",
"hamcrest2",
"lazy_static",
"nu-glob",
"nu-path",
"nu-utils",
"num-bigint 0.4.3",
"num-format",
"tempfile",
]
@ -2826,6 +2830,8 @@ version = "0.66.4"
dependencies = [
"crossterm_winapi",
"lscolors",
"num-format",
"sys-locale",
]
[[package]]

View file

@ -24,6 +24,7 @@ nu-term-grid = { path = "../nu-term-grid", version = "0.66.4" }
nu-test-support = { path = "../nu-test-support", version = "0.66.4" }
nu-utils = { path = "../nu-utils", version = "0.66.4" }
nu-ansi-term = "0.46.0"
num-format = { version = "0.4.0" }
# Potential dependencies for extras
alphanumeric-sort = "1.4.4"

View file

@ -5,8 +5,8 @@ use nu_protocol::{
into_code, Category, Config, Example, IntoPipelineData, PipelineData, ShellError, Signature,
Span, SyntaxShape, Value,
};
// TODO num_format::SystemLocale once platform-specific dependencies are stable (see Cargo.toml)
use nu_utils::get_system_locale;
use num_format::ToFormattedString;
#[derive(Clone)]
pub struct SubCommand;
@ -216,21 +216,8 @@ pub fn action(
) -> Value {
match input {
Value::Int { val, .. } => {
let res = if group_digits {
format_int(*val) // int.to_formatted_string(*locale)
} else if let Some(dig) = digits {
let mut val_with_trailing_zeroes = val.to_string();
if dig != 0 {
val_with_trailing_zeroes.push('.');
}
for _ in 0..dig {
val_with_trailing_zeroes.push('0');
}
val_with_trailing_zeroes
} else {
val.to_string()
};
let decimal_value = digits.unwrap_or(0) as usize;
let res = format_int(*val, group_digits, decimal_value);
Value::String { val: res, span }
}
Value::Float { val, .. } => {
@ -305,21 +292,29 @@ pub fn action(
},
}
}
fn format_int(int: i64) -> String {
int.to_string()
// TODO once platform-specific dependencies are stable (see Cargo.toml)
// #[cfg(windows)]
// {
// int.to_formatted_string(&Locale::en)
// }
// #[cfg(not(windows))]
// {
// match SystemLocale::default() {
// Ok(locale) => int.to_formatted_string(&locale),
// Err(_) => int.to_formatted_string(&Locale::en),
// }
// }
fn format_int(int: i64, group_digits: bool, decimals: usize) -> String {
let locale = get_system_locale();
let str = if group_digits {
int.to_formatted_string(&locale)
} else {
int.to_string()
};
if decimals > 0 {
let decimal_point = locale.decimal();
format!(
"{}{decimal_point}{dummy:0<decimals$}",
str,
decimal_point = decimal_point,
dummy = "",
decimals = decimals
)
} else {
str
}
}
#[cfg(test)]

View file

@ -31,7 +31,7 @@ fn filesystem_change_from_current_directory_using_absolute_path() {
cd "{}"
echo (pwd)
"#,
dirs.formats()
dirs.formats().display()
);
assert_eq!(PathBuf::from(actual.out), dirs.formats());
@ -52,7 +52,7 @@ fn filesystem_switch_back_to_previous_working_directory() {
cd -
echo (pwd)
"#,
dirs.test()
dirs.test().display()
);
assert_eq!(PathBuf::from(actual.out), dirs.test().join("odin"));
@ -132,7 +132,7 @@ fn filesystem_change_current_directory_to_parent_directory_after_delete_cwd() {
cd ..
echo (pwd)
"#,
dirs.test()
dirs.test().display()
);
let actual = actual.out.split(',').nth(1).unwrap();

View file

@ -10,7 +10,7 @@ fn copies_a_file() {
nu!(
cwd: dirs.root(),
"cp `{}` cp_test_1/sample.ini",
dirs.formats().join("sample.ini")
dirs.formats().join("sample.ini").display()
);
assert!(dirs.test().join("sample.ini").exists());
@ -37,7 +37,7 @@ fn error_if_attempting_to_copy_a_directory_to_another_directory() {
Playground::setup("cp_test_3", |dirs, _| {
let actual = nu!(
cwd: dirs.formats(),
"cp ../formats {}", dirs.test()
"cp ../formats {}", dirs.test().display()
);
assert!(actual.err.contains("../formats"));
@ -128,7 +128,7 @@ fn copies_using_path_with_wildcard() {
Playground::setup("cp_test_6", |dirs, _| {
nu!(
cwd: dirs.formats(),
"cp -r ../formats/* {}", dirs.test()
"cp -r ../formats/* {}", dirs.test().display()
);
assert!(files_exist_at(
@ -150,7 +150,7 @@ fn copies_using_a_glob() {
Playground::setup("cp_test_7", |dirs, _| {
nu!(
cwd: dirs.formats(),
"cp -r * {}", dirs.test()
"cp -r * {}", dirs.test().display()
);
assert!(files_exist_at(
@ -173,13 +173,13 @@ fn copies_same_file_twice() {
nu!(
cwd: dirs.root(),
"cp `{}` cp_test_8/sample.ini",
dirs.formats().join("sample.ini")
dirs.formats().join("sample.ini").display()
);
nu!(
cwd: dirs.root(),
"cp `{}` cp_test_8/sample.ini",
dirs.formats().join("sample.ini")
dirs.formats().join("sample.ini").display()
);
assert!(dirs.test().join("sample.ini").exists());

View file

@ -531,7 +531,7 @@ fn list_directory_contains_invalid_utf8() {
std::fs::create_dir_all(&path).expect("failed to create directory");
let actual = nu!(cwd, "ls");
let actual = nu!(cwd: cwd, "ls");
assert!(actual.out.contains("warning: get non-utf8 filename"));
assert!(actual.err.contains("No matches found for"));

View file

@ -384,7 +384,7 @@ fn mv_directory_with_same_name() {
let cwd = sandbox.cwd().join("testdir");
let actual = nu!(
cwd,
cwd: cwd,
r#"
mv testdir ..
"#

View file

@ -1,4 +1,4 @@
use nu_test_support::fs::{AbsolutePath, DisplayPath, Stub::FileWithContent};
use nu_test_support::fs::{AbsolutePath, Stub::FileWithContent};
use nu_test_support::nu;
use nu_test_support::pipeline;
use nu_test_support::playground::Playground;
@ -18,7 +18,7 @@ fn sources_also_files_under_custom_lib_dirs_path() {
lib_dirs = ["{}"]
skip_welcome_message = true
"#,
library_path.display_path()
library_path
),
)]);

View file

@ -183,3 +183,87 @@ fn from_error() {
assert_eq!(actual.out, "nu::shell::name_not_found");
}
#[test]
fn int_into_string() {
let actual = nu!(
cwd: ".", pipeline(
r#"
10 | into string
"#
));
assert_eq!(actual.out, "10");
}
#[test]
fn int_into_string_decimals_0() {
let actual = nu!(
locale: "en_US.UTF-8",
pipeline(
r#"
10 | into string --decimals 0
"#
)
);
assert_eq!(actual.out, "10");
}
#[test]
fn int_into_string_decimals_1() {
let actual = nu!(
locale: "en_US.UTF-8",
pipeline(
r#"
10 | into string --decimals 1
"#
)
);
assert_eq!(actual.out, "10.0");
}
#[test]
fn int_into_string_decimals_10() {
let actual = nu!(
locale: "en_US.UTF-8",
pipeline(
r#"
10 | into string --decimals 10
"#
)
);
assert_eq!(actual.out, "10.0000000000");
}
#[test]
fn int_into_string_decimals_respects_system_locale_de() {
// Set locale to `de_DE`, which uses `,` (comma) as decimal separator
let actual = nu!(
locale: "de_DE.UTF-8",
pipeline(
r#"
10 | into string --decimals 1
"#
)
);
assert_eq!(actual.out, "10,0");
}
#[test]
fn int_into_string_decimals_respects_system_locale_en() {
// Set locale to `en_US`, which uses `.` (period) as decimal separator
let actual = nu!(
locale: "en_US.UTF-8",
pipeline(
r#"
10 | into string --decimals 1
"#
)
);
assert_eq!(actual.out, "10.0");
}

View file

@ -1,4 +1,4 @@
use nu_test_support::fs::{AbsolutePath, DisplayPath, Stub::FileWithContent};
use nu_test_support::fs::{AbsolutePath, Stub::FileWithContent};
use nu_test_support::nu;
use nu_test_support::pipeline;
use nu_test_support::playground::Playground;
@ -9,7 +9,7 @@ fn use_module_file_within_block() {
let file = AbsolutePath::new(dirs.test().join("spam.nu"));
nu.with_files(vec![FileWithContent(
&file.display_path(),
&file.to_string(),
r#"
export def foo [] {
echo "hello world"
@ -39,7 +39,7 @@ fn use_keeps_doc_comments() {
let file = AbsolutePath::new(dirs.test().join("spam.nu"));
nu.with_files(vec![FileWithContent(
&file.display_path(),
&file.to_string(),
r#"
# this is my foo command
export def foo [

View file

@ -16,7 +16,8 @@ pub use custom_value::CustomValue;
use fancy_regex::Regex;
pub use from_value::FromValue;
use indexmap::map::IndexMap;
use num_format::{Locale, ToFormattedString};
use nu_utils::get_system_locale;
use num_format::ToFormattedString;
pub use range::*;
use serde::{Deserialize, Serialize};
use std::{
@ -28,7 +29,6 @@ use std::{
{cmp::Ordering, fmt::Debug},
};
pub use stream::*;
use sys_locale::get_locale;
pub use unit::*;
/// Core structured values that pass through the pipeline in Nushell.
@ -2760,25 +2760,7 @@ pub fn format_filesize(num_bytes: i64, format_value: &str, filesize_metric: bool
match adj_byte.get_unit() {
byte_unit::ByteUnit::B => {
let locale_string = get_locale().unwrap_or_else(|| String::from("en-US"));
// Since get_locale() and Locale::from_name() don't always return the same items
// we need to try and parse it to match. For instance, a valid locale is de_DE
// however Locale::from_name() wants only de so we split and parse it out.
let locale_string = locale_string.replace('_', "-"); // en_AU -> en-AU
let locale = match Locale::from_name(&locale_string) {
Ok(loc) => loc,
_ => {
let all = num_format::Locale::available_names();
let locale_prefix = &locale_string.split('-').collect::<Vec<&str>>();
if all.contains(&locale_prefix[0]) {
// eprintln!("Found alternate: {}", &locale_prefix[0]);
Locale::from_name(locale_prefix[0]).unwrap_or(Locale::en)
} else {
// eprintln!("Unable to find matching locale. Defaulting to en-US");
Locale::en
}
}
};
let locale = get_system_locale();
let locale_byte = adj_byte.get_value() as u64;
let locale_byte_string = locale_byte.to_formatted_string(&locale);

View file

@ -12,6 +12,9 @@ doctest = false
[dependencies]
nu-path = { path="../nu-path", version = "0.66.4" }
nu-glob = { path = "../nu-glob", version = "0.66.4" }
nu-utils = { path="../nu-utils", version = "0.66.4" }
lazy_static = "1.4.0"
num-format = "0.4.0"
getset = "0.1.1"
num-bigint = { version="0.4.3", features=["serde"] }

View file

@ -1,3 +1,4 @@
use std::fmt::Display;
use std::io::Read;
use std::ops::Div;
use std::path::{Path, PathBuf};
@ -113,45 +114,25 @@ impl<T: AsRef<str>> Div<T> for &RelativePath {
RelativePath::new(result)
}
}
pub trait DisplayPath {
fn display_path(&self) -> String;
}
impl DisplayPath for AbsolutePath {
fn display_path(&self) -> String {
self.inner.display().to_string()
impl Display for AbsoluteFile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.inner.display())
}
}
impl DisplayPath for PathBuf {
fn display_path(&self) -> String {
self.display().to_string()
impl Display for AbsolutePath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.inner.display())
}
}
impl DisplayPath for str {
fn display_path(&self) -> String {
self.to_string()
impl Display for RelativePath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.inner.display())
}
}
impl DisplayPath for &str {
fn display_path(&self) -> String {
(*self).to_string()
}
}
impl DisplayPath for String {
fn display_path(&self) -> String {
self.clone()
}
}
impl DisplayPath for &String {
fn display_path(&self) -> String {
(*self).to_string()
}
}
pub enum Stub<'a> {
FileWithContent(&'a str, &'a str),
FileWithContentToBeTrimmed(&'a str, &'a str),

View file

@ -1,5 +1,6 @@
pub mod commands;
pub mod fs;
pub mod locale_override;
pub mod macros;
pub mod playground;

View file

@ -0,0 +1,46 @@
#![cfg(debug_assertions)]
use std::sync::{Arc, Mutex};
use lazy_static::lazy_static;
use nu_utils::locale::LOCALE_OVERRIDE_ENV_VAR;
lazy_static! {
static ref LOCALE_OVERRIDE_MUTEX: Arc<Mutex<()>> = Arc::new(Mutex::new(()));
}
/// Run a closure in a fake locale environment.
///
/// Before the closure is executed, an environment variable whose name is
/// defined in `nu_utils::locale::LOCALE_OVERRIDE_ENV_VAR` is set to the value
/// provided by `locale_string`. When the closure is done, the previous value is
/// restored.
///
/// Environment variables are global values. So when they are changed by one
/// thread they are changed for all others. To prevent a test from overwriting
/// the environment variable of another test, a mutex is used.
pub fn with_locale_override<T>(locale_string: &str, func: fn() -> T) -> T {
let result = {
let _lock = LOCALE_OVERRIDE_MUTEX
.lock()
.expect("Failed to get mutex lock for locale override");
let saved = std::env::var(LOCALE_OVERRIDE_ENV_VAR).ok();
std::env::set_var(LOCALE_OVERRIDE_ENV_VAR, locale_string);
let result = std::panic::catch_unwind(func);
if let Some(locale_str) = saved {
std::env::set_var(LOCALE_OVERRIDE_ENV_VAR, locale_str);
} else {
std::env::remove_var(LOCALE_OVERRIDE_ENV_VAR);
}
result
};
match result {
Ok(result) => result,
Err(err) => std::panic::resume_unwind(err),
}
}

View file

@ -1,21 +1,120 @@
/// Run a command in nu and get it's output
///
/// The `nu!` macro accepts a number of options like the `cwd` in which the
/// command should be run. It is also possible to specify a different `locale`
/// to test locale dependent commands.
///
/// Pass options as the first arguments in the form of `key_1: value_1, key_1:
/// value_2, ...`. The options are defined in the `NuOpts` struct inside the
/// `nu!` macro.
///
/// The command can be formatted using `{}` just like `println!` or `format!`.
/// Pass the format arguments comma separated after the command itself.
///
/// # Examples
///
/// ```no_run
/// # // NOTE: The `nu!` macro needs the `nu` binary to exist. The test are
/// # // therefore only compiled but not run (thats what the `no_run` at
/// # // the beginning of this code block is for).
/// #
/// use nu_test_support::nu;
///
/// let outcome = nu!(
/// "date now | date to-record | get year"
/// );
///
/// let dir = "/";
/// let outcome = nu!(
/// "ls {} | get name",
/// dir,
/// );
///
/// let outcome = nu!(
/// cwd: "/",
/// "ls | get name",
/// );
///
/// let cell = "size";
/// let outcome = nu!(
/// locale: "de_DE.UTF-8",
/// "ls | into int {}",
/// cell,
/// );
///
/// let decimals = 2;
/// let outcome = nu!(
/// locale: "de_DE.UTF-8",
/// "10 | into string --decimals {}",
/// decimals,
/// );
/// ```
#[macro_export]
macro_rules! nu {
(cwd: $cwd:expr, $path:expr, $($part:expr),*) => {{
use $crate::fs::DisplayPath;
// In the `@options` phase, we restucture all the
// `$field_1: $value_1, $field_2: $value_2, ...`
// pairs to a structure like
// `@options[ $field_1 => $value_1 ; $field_2 => $value_2 ; ... ]`.
// We do this to later distinguish the options from the `$path` and `$part`s.
// (See
// https://users.rust-lang.org/t/i-dont-think-this-local-ambiguity-when-calling-macro-is-ambiguous/79401?u=x3ro
// )
//
// If there is any special treatment needed for the `$value`, we can just
// match for the specific `field` name.
(
@options [ $($options:tt)* ]
cwd: $value:expr,
$($rest:tt)*
) => {
nu!(@options [ $($options)* cwd => $crate::fs::in_directory($value) ; ] $($rest)*)
};
// For all other options, we call `.into()` on the `$value` and hope for the best. ;)
(
@options [ $($options:tt)* ]
$field:ident : $value:expr,
$($rest:tt)*
) => {
nu!(@options [ $($options)* $field => $value.into() ; ] $($rest)*)
};
let path = format!($path, $(
$part.display_path()
),*);
nu!($cwd, &path)
// When the `$field: $value,` pairs are all parsed, the next tokens are the `$path` and any
// number of `$part`s, potentially followed by a trailing comma.
(
@options [ $($options:tt)* ]
$path:expr
$(, $part:expr)*
$(,)*
) => {{
// Here we parse the options into a `NuOpts` struct
let opts = nu!(@nu_opts $($options)*);
// and format the `$path` using the `$part`s
let path = nu!(@format_path $path, $($part),*);
// Then finally we go to the `@main` phase, where the actual work is done.
nu!(@main opts, path)
}};
(cwd: $cwd:expr, $path:expr) => {{
nu!($cwd, $path)
// Create the NuOpts struct from the `field => value ;` pairs
(@nu_opts $( $field:ident => $value:expr ; )*) => {
NuOpts{
$(
$field: Some($value),
)*
..Default::default()
}
};
// Helper to format `$path`.
(@format_path $path:expr $(,)?) => {
// When there are no `$part`s, do not format anything
$path
};
(@format_path $path:expr, $($part:expr),* $(,)?) => {{
format!($path, $( $part ),*)
}};
($cwd:expr, $path:expr) => {{
pub use itertools::Itertools;
// Do the actual work.
(@main $opts:expr, $path:expr) => {{
pub use std::error::Error;
pub use std::io::prelude::*;
pub use std::process::{Command, Stdio};
@ -36,13 +135,6 @@ macro_rules! nu {
output
}
// let commands = &*format!(
// "
// {}
// exit",
// $crate::fs::DisplayPath::display_path(&$path)
// );
let test_bins = $crate::fs::binaries();
let cwd = std::env::current_dir().expect("Could not get current working directory.");
@ -64,21 +156,25 @@ macro_rules! nu {
Err(_) => panic!("Couldn't join paths for PATH var."),
};
let target_cwd = $crate::fs::in_directory(&$cwd);
let target_cwd = $opts.cwd.unwrap_or(".".to_string());
let locale = $opts.locale.unwrap_or("en_US.UTF-8".to_string());
let mut process = match Command::new($crate::fs::executable_path())
let mut command = Command::new($crate::fs::executable_path());
command
.env("PWD", &target_cwd)
.env(nu_utils::locale::LOCALE_OVERRIDE_ENV_VAR, locale)
.current_dir(target_cwd)
.env(NATIVE_PATH_ENV_VAR, paths_joined)
// .arg("--skip-plugins")
// .arg("--no-history")
// .arg("--config-file")
// .arg($crate::fs::DisplayPath::display_path(&$crate::fs::fixtures().join("playground/config/default.toml")))
.arg(format!("-c {}", escape_quote_string($crate::fs::DisplayPath::display_path(&path))))
.arg(format!("-c {}", escape_quote_string(path)))
.stdout(Stdio::piped())
// .stdin(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.stderr(Stdio::piped());
let mut process = match command.spawn()
{
Ok(child) => child,
Err(why) => panic!("Can't run test {:?} {}", $crate::fs::executable_path(), why.to_string()),
@ -100,6 +196,17 @@ macro_rules! nu {
$crate::Outcome::new(out,err.into_owned())
}};
// This is the entrypoint for this macro.
($($token:tt)*) => {{
#[derive(Default)]
struct NuOpts {
cwd: Option<String>,
locale: Option<String>,
}
nu!(@options [ ] $($token)*)
}};
}
#[macro_export]

View file

@ -0,0 +1,19 @@
use nu_test_support::locale_override::with_locale_override;
use nu_utils::get_system_locale;
use num_format::Grouping;
#[test]
fn test_get_system_locale_en() {
let locale = with_locale_override("en_US.UTF-8", || get_system_locale());
assert_eq!(locale.name(), "en");
assert_eq!(locale.grouping(), Grouping::Standard)
}
#[test]
fn test_get_system_locale_de() {
let locale = with_locale_override("de_DE.UTF-8", || get_system_locale());
assert_eq!(locale.name(), "de");
assert_eq!(locale.grouping(), Grouping::Standard)
}

View file

@ -13,6 +13,8 @@ path = "src/main.rs"
[dependencies]
lscolors = { version = "0.12.0", features = ["crossterm"]}
num-format = { version = "0.4.0" }
sys-locale = "0.2.1"
[target.'cfg(windows)'.dependencies]
crossterm_winapi = "0.9.0"

View file

@ -1,5 +1,7 @@
pub mod locale;
pub mod utils;
pub use locale::get_system_locale;
pub use utils::{
enable_vt_processing, get_default_config, get_default_env, get_ls_colors,
stderr_write_all_and_flush, stdout_write_all_and_flush,

View file

@ -0,0 +1,39 @@
use num_format::Locale;
#[cfg(debug_assertions)]
pub const LOCALE_OVERRIDE_ENV_VAR: &str = "NU_TEST_LOCALE_OVERRIDE";
pub fn get_system_locale() -> Locale {
let locale_string = get_system_locale_string().unwrap_or_else(|| String::from("en-US"));
// Since get_locale() and Locale::from_name() don't always return the same items
// we need to try and parse it to match. For instance, a valid locale is de_DE
// however Locale::from_name() wants only de so we split and parse it out.
let locale_string = locale_string.replace('_', "-"); // en_AU -> en-AU
match Locale::from_name(&locale_string) {
Ok(loc) => loc,
_ => {
let all = num_format::Locale::available_names();
let locale_prefix = &locale_string.split('-').collect::<Vec<&str>>();
if all.contains(&locale_prefix[0]) {
// eprintln!("Found alternate: {}", &locale_prefix[0]);
Locale::from_name(locale_prefix[0]).unwrap_or(Locale::en)
} else {
// eprintln!("Unable to find matching locale. Defaulting to en-US");
Locale::en
}
}
}
}
#[cfg(debug_assertions)]
fn get_system_locale_string() -> Option<String> {
std::env::var(LOCALE_OVERRIDE_ENV_VAR)
.ok()
.or_else(sys_locale::get_locale)
}
#[cfg(not(debug_assertions))]
fn get_system_locale_string() -> Option<String> {
sys_locale::get_locale()
}

View file

@ -12,9 +12,9 @@ pub mod support {
pub fn in_path(dirs: &Dirs, block: impl FnOnce() -> Outcome) -> Outcome {
let for_env_manifest = dirs.test().to_string_lossy();
nu!(cwd: dirs.root(), format!("autoenv trust \"{}\"", for_env_manifest));
nu!(cwd: dirs.root(), "autoenv trust \"{}\"", for_env_manifest);
let out = block();
nu!(cwd: dirs.root(), format!("autoenv untrust \"{}\"", for_env_manifest));
nu!(cwd: dirs.root(), "autoenv untrust \"{}\"", for_env_manifest);
out
}