mirror of
https://github.com/nushell/nushell
synced 2024-12-26 13:03:07 +00:00
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:
parent
ccebdd7a7f
commit
cb18dd5200
21 changed files with 390 additions and 122 deletions
6
Cargo.lock
generated
6
Cargo.lock
generated
|
@ -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]]
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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"));
|
||||
|
|
|
@ -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 ..
|
||||
"#
|
||||
|
|
|
@ -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
|
||||
),
|
||||
)]);
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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 [
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
pub mod commands;
|
||||
pub mod fs;
|
||||
pub mod locale_override;
|
||||
pub mod macros;
|
||||
pub mod playground;
|
||||
|
||||
|
|
46
crates/nu-test-support/src/locale_override.rs
Normal file
46
crates/nu-test-support/src/locale_override.rs
Normal 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),
|
||||
}
|
||||
}
|
|
@ -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]
|
||||
|
|
19
crates/nu-test-support/tests/get_system_locale.rs
Normal file
19
crates/nu-test-support/tests/get_system_locale.rs
Normal 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)
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
|
|
39
crates/nu-utils/src/locale.rs
Normal file
39
crates/nu-utils/src/locale.rs
Normal 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()
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue