forked from extern/nushell
Playground infraestructure (tests, etc) additions. (#3179)
* Playground infraestructure (tests, etc) additions. A few things to note: * Nu can be started with a custom configuration file (`nu --config-file /path/to/sample_config.toml`). Useful for mocking the configuration on test runs. * When given a custom configuration file Nu will save any changes to the file supplied appropiately. * The `$nu.config-path` variable either shows the default configuration file (or the custom one, if given) * We can now run end to end tests with finer grained control (currently, since this is baseline work, standard out) This will allow to check things like exit status, assert the contents with a format, etc) * Remove (for another PR)
This commit is contained in:
parent
82b6300dcb
commit
d2213d18fa
14
Cargo.lock
generated
14
Cargo.lock
generated
@ -2097,6 +2097,16 @@ dependencies = [
|
|||||||
"tracing-futures",
|
"tracing-futures",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hamcrest2"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49f837c62de05dc9cc71ff6486cd85de8856a330395ae338a04bfcefe5e91075"
|
||||||
|
dependencies = [
|
||||||
|
"num",
|
||||||
|
"regex 1.4.3",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.9.1"
|
version = "0.9.1"
|
||||||
@ -3065,6 +3075,7 @@ dependencies = [
|
|||||||
"ctrlc",
|
"ctrlc",
|
||||||
"dunce",
|
"dunce",
|
||||||
"futures 0.3.13",
|
"futures 0.3.13",
|
||||||
|
"hamcrest2",
|
||||||
"itertools",
|
"itertools",
|
||||||
"log 0.4.14",
|
"log 0.4.14",
|
||||||
"nu-cli",
|
"nu-cli",
|
||||||
@ -3246,6 +3257,7 @@ dependencies = [
|
|||||||
"futures_codec",
|
"futures_codec",
|
||||||
"getset",
|
"getset",
|
||||||
"glob",
|
"glob",
|
||||||
|
"hamcrest2",
|
||||||
"htmlescape",
|
"htmlescape",
|
||||||
"ical",
|
"ical",
|
||||||
"ichwh",
|
"ichwh",
|
||||||
@ -3362,6 +3374,7 @@ dependencies = [
|
|||||||
"futures_codec",
|
"futures_codec",
|
||||||
"getset",
|
"getset",
|
||||||
"glob",
|
"glob",
|
||||||
|
"hamcrest2",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"itertools",
|
"itertools",
|
||||||
"log 0.4.14",
|
"log 0.4.14",
|
||||||
@ -3522,6 +3535,7 @@ dependencies = [
|
|||||||
"dunce",
|
"dunce",
|
||||||
"getset",
|
"getset",
|
||||||
"glob",
|
"glob",
|
||||||
|
"hamcrest2",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"nu-errors",
|
"nu-errors",
|
||||||
"nu-protocol",
|
"nu-protocol",
|
||||||
|
@ -60,6 +60,7 @@ pretty_env_logger = "0.4.0"
|
|||||||
nu-test-support = { version = "0.28.0", path = "./crates/nu-test-support" }
|
nu-test-support = { version = "0.28.0", path = "./crates/nu-test-support" }
|
||||||
dunce = "1.0.1"
|
dunce = "1.0.1"
|
||||||
serial_test = "0.5.1"
|
serial_test = "0.5.1"
|
||||||
|
hamcrest2 = "0.3.0"
|
||||||
|
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
|
@ -15,8 +15,10 @@ use crate::line_editor::{
|
|||||||
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use nu_data::config;
|
use nu_data::config;
|
||||||
use nu_source::{Tag, Text};
|
use nu_data::config::{Conf, NuConfig};
|
||||||
|
use nu_source::{AnchorLocation, Tag, Text};
|
||||||
use nu_stream::InputStream;
|
use nu_stream::InputStream;
|
||||||
|
use std::ffi::{OsStr, OsString};
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
@ -33,6 +35,67 @@ use std::error::Error;
|
|||||||
use std::iter::Iterator;
|
use std::iter::Iterator;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
pub struct Options {
|
||||||
|
pub config: Option<OsString>,
|
||||||
|
pub stdin: bool,
|
||||||
|
pub scripts: Vec<NuScript>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Options {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Options {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
config: None,
|
||||||
|
stdin: false,
|
||||||
|
scripts: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NuScript {
|
||||||
|
pub filepath: Option<OsString>,
|
||||||
|
pub contents: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NuScript {
|
||||||
|
pub fn code<'a>(content: impl Iterator<Item = &'a str>) -> Result<Self, ShellError> {
|
||||||
|
let text = content
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
filepath: None,
|
||||||
|
contents: text,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_code(&self) -> &str {
|
||||||
|
&self.contents
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn source_file(path: &OsStr) -> Result<Self, ShellError> {
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
|
let path = path.to_os_string();
|
||||||
|
let mut file = File::open(&path)?;
|
||||||
|
let mut buffer = String::new();
|
||||||
|
|
||||||
|
file.read_to_string(&mut buffer)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
filepath: Some(path),
|
||||||
|
contents: buffer,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn search_paths() -> Vec<std::path::PathBuf> {
|
pub fn search_paths() -> Vec<std::path::PathBuf> {
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
@ -62,12 +125,9 @@ pub fn search_paths() -> Vec<std::path::PathBuf> {
|
|||||||
search_paths
|
search_paths
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run_script_file(
|
pub async fn run_script_file(options: Options) -> Result<(), Box<dyn Error>> {
|
||||||
file_contents: String,
|
|
||||||
redirect_stdin: bool,
|
|
||||||
) -> Result<(), Box<dyn Error>> {
|
|
||||||
let mut syncer = EnvironmentSyncer::new();
|
|
||||||
let mut context = create_default_context(false)?;
|
let mut context = create_default_context(false)?;
|
||||||
|
let mut syncer = create_environment_syncer(&context, &options);
|
||||||
let config = syncer.get_config();
|
let config = syncer.get_config();
|
||||||
|
|
||||||
context.configure(&config, |_, ctx| {
|
context.configure(&config, |_, ctx| {
|
||||||
@ -85,15 +145,38 @@ pub async fn run_script_file(
|
|||||||
|
|
||||||
let _ = run_startup_commands(&mut context, &config).await;
|
let _ = run_startup_commands(&mut context, &config).await;
|
||||||
|
|
||||||
run_script_standalone(file_contents, redirect_stdin, &context, true).await?;
|
let script = options
|
||||||
|
.scripts
|
||||||
|
.get(0)
|
||||||
|
.ok_or_else(|| ShellError::unexpected("Nu source code not available"))?;
|
||||||
|
|
||||||
|
run_script_standalone(script.get_code().to_string(), options.stdin, &context, true).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The entry point for the CLI. Will register all known internal commands, load experimental commands, load plugins, then prepare the prompt and line reader for input.
|
fn create_environment_syncer(context: &EvaluationContext, options: &Options) -> EnvironmentSyncer {
|
||||||
|
if let Some(config_file) = &options.config {
|
||||||
|
let location = Some(AnchorLocation::File(
|
||||||
|
config_file.to_string_lossy().to_string(),
|
||||||
|
));
|
||||||
|
let tag = Tag::unknown().anchored(location);
|
||||||
|
|
||||||
|
context.scope.add_var(
|
||||||
|
"config-path",
|
||||||
|
UntaggedValue::filepath(PathBuf::from(&config_file)).into_value(tag),
|
||||||
|
);
|
||||||
|
|
||||||
|
EnvironmentSyncer::with_config(Box::new(NuConfig::with(Some(config_file.into()))))
|
||||||
|
} else {
|
||||||
|
EnvironmentSyncer::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "rustyline-support")]
|
#[cfg(feature = "rustyline-support")]
|
||||||
pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
|
pub async fn cli(mut context: EvaluationContext, options: Options) -> Result<(), Box<dyn Error>> {
|
||||||
let mut syncer = EnvironmentSyncer::new();
|
let mut syncer = create_environment_syncer(&context, &options);
|
||||||
|
|
||||||
let configuration = syncer.get_config();
|
let configuration = syncer.get_config();
|
||||||
|
|
||||||
let mut rl = default_rustyline_editor_configuration();
|
let mut rl = default_rustyline_editor_configuration();
|
||||||
|
@ -28,6 +28,7 @@ pub mod types;
|
|||||||
pub use crate::cli::cli;
|
pub use crate::cli::cli;
|
||||||
|
|
||||||
pub use crate::cli::{parse_and_eval, register_plugins, run_script_file};
|
pub use crate::cli::{parse_and_eval, register_plugins, run_script_file};
|
||||||
|
pub use crate::cli::{NuScript, Options};
|
||||||
|
|
||||||
pub use crate::env::environment_syncer::EnvironmentSyncer;
|
pub use crate::env::environment_syncer::EnvironmentSyncer;
|
||||||
pub use nu_command::commands::default_context::create_default_context;
|
pub use nu_command::commands::default_context::create_default_context;
|
||||||
|
@ -124,6 +124,7 @@ shadow-rs = "0.5"
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
quickcheck = "1.0.3"
|
quickcheck = "1.0.3"
|
||||||
quickcheck_macros = "1.0.0"
|
quickcheck_macros = "1.0.0"
|
||||||
|
hamcrest2 = "0.3.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
clipboard-cli = ["arboard"]
|
clipboard-cli = ["arboard"]
|
||||||
|
@ -152,7 +152,7 @@ pub(crate) use char_::Char;
|
|||||||
pub(crate) use chart::Chart;
|
pub(crate) use chart::Chart;
|
||||||
pub(crate) use compact::Compact;
|
pub(crate) use compact::Compact;
|
||||||
pub(crate) use config::{
|
pub(crate) use config::{
|
||||||
Config, ConfigClear, ConfigGet, ConfigLoad, ConfigPath, ConfigRemove, ConfigSet, ConfigSetInto,
|
Config, ConfigClear, ConfigGet, ConfigPath, ConfigRemove, ConfigSet, ConfigSetInto,
|
||||||
};
|
};
|
||||||
pub(crate) use cp::Cpy;
|
pub(crate) use cp::Cpy;
|
||||||
pub(crate) use date::{Date, DateFormat, DateListTimeZone, DateNow, DateToTable, DateToTimeZone};
|
pub(crate) use date::{Date, DateFormat, DateListTimeZone, DateNow, DateToTable, DateToTimeZone};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_engine::WholeStreamCommand;
|
use nu_engine::WholeStreamCommand;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
|
||||||
|
|
||||||
pub struct SubCommand;
|
pub struct SubCommand;
|
||||||
|
|
||||||
@ -35,13 +35,19 @@ impl WholeStreamCommand for SubCommand {
|
|||||||
pub async fn clear(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
pub async fn clear(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
let name_span = args.call_info.name_tag.clone();
|
let name_span = args.call_info.name_tag.clone();
|
||||||
|
|
||||||
// NOTE: None because we are not loading a new config file, we just want to read from the
|
let path = match args.scope.get_var("config-path") {
|
||||||
// existing config
|
Some(Value {
|
||||||
let mut result = nu_data::config::read(name_span, &None)?;
|
value: UntaggedValue::Primitive(Primitive::FilePath(path)),
|
||||||
|
..
|
||||||
|
}) => Some(path),
|
||||||
|
_ => nu_data::config::default_path().ok(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut result = nu_data::config::read(name_span, &path)?;
|
||||||
|
|
||||||
result.clear();
|
result.clear();
|
||||||
|
|
||||||
config::write(&result, &None)?;
|
config::write(&result, &path)?;
|
||||||
|
|
||||||
Ok(OutputStream::one(ReturnSuccess::value(
|
Ok(OutputStream::one(ReturnSuccess::value(
|
||||||
UntaggedValue::Row(result.into()).into_value(args.call_info.name_tag),
|
UntaggedValue::Row(result.into()).into_value(args.call_info.name_tag),
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_engine::WholeStreamCommand;
|
use nu_engine::WholeStreamCommand;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
use nu_protocol::{
|
||||||
|
ColumnPath, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct SubCommand;
|
pub struct SubCommand;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct GetArgs {
|
pub struct Arguments {
|
||||||
path: ColumnPath,
|
column_path: ColumnPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@ -42,14 +44,21 @@ impl WholeStreamCommand for SubCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
pub async fn get(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
let name_tag = args.call_info.name_tag.clone();
|
let name = args.call_info.name_tag.clone();
|
||||||
let (GetArgs { path }, _) = args.process().await?;
|
let scope = args.scope.clone();
|
||||||
|
let (Arguments { column_path }, _) = args.process().await?;
|
||||||
|
|
||||||
// NOTE: None because we are not loading a new config file, we just want to read from the
|
let path = match scope.get_var("config-path") {
|
||||||
// existing config
|
Some(Value {
|
||||||
let result = UntaggedValue::row(nu_data::config::read(&name_tag, &None)?).into_value(&name_tag);
|
value: UntaggedValue::Primitive(Primitive::FilePath(path)),
|
||||||
|
..
|
||||||
|
}) => Some(path),
|
||||||
|
_ => nu_data::config::default_path().ok(),
|
||||||
|
};
|
||||||
|
|
||||||
let value = crate::commands::get::get_column_path(&path, &result)?;
|
let result = UntaggedValue::row(nu_data::config::read(&name, &path)?).into_value(&name);
|
||||||
|
|
||||||
|
let value = crate::commands::get::get_column_path(&column_path, &result)?;
|
||||||
|
|
||||||
Ok(match value {
|
Ok(match value {
|
||||||
Value {
|
Value {
|
||||||
|
@ -1,51 +0,0 @@
|
|||||||
use crate::prelude::*;
|
|
||||||
use nu_engine::WholeStreamCommand;
|
|
||||||
use nu_errors::ShellError;
|
|
||||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
|
||||||
use nu_source::Tagged;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
pub struct SubCommand;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct LoadArgs {
|
|
||||||
load: Tagged<PathBuf>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl WholeStreamCommand for SubCommand {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"config load"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
|
||||||
Signature::build("config load").required(
|
|
||||||
"load",
|
|
||||||
SyntaxShape::FilePath,
|
|
||||||
"Path to load the config from",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"Loads the config from the path given"
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|
||||||
set(args).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn set(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|
||||||
let name = args.call_info.name_tag.clone();
|
|
||||||
let name_span = args.call_info.name_tag.clone();
|
|
||||||
let (LoadArgs { load }, _) = args.process().await?;
|
|
||||||
|
|
||||||
let configuration = load.item().clone();
|
|
||||||
|
|
||||||
let result = nu_data::config::read(name_span, &Some(configuration))?;
|
|
||||||
|
|
||||||
Ok(futures::stream::iter(vec![ReturnSuccess::value(
|
|
||||||
UntaggedValue::Row(result.into()).into_value(name),
|
|
||||||
)])
|
|
||||||
.to_output_stream())
|
|
||||||
}
|
|
@ -1,7 +1,6 @@
|
|||||||
pub mod clear;
|
pub mod clear;
|
||||||
pub mod command;
|
pub mod command;
|
||||||
pub mod get;
|
pub mod get;
|
||||||
pub mod load;
|
|
||||||
pub mod path;
|
pub mod path;
|
||||||
pub mod remove;
|
pub mod remove;
|
||||||
pub mod set;
|
pub mod set;
|
||||||
@ -10,7 +9,6 @@ pub mod set_into;
|
|||||||
pub use clear::SubCommand as ConfigClear;
|
pub use clear::SubCommand as ConfigClear;
|
||||||
pub use command::Command as Config;
|
pub use command::Command as Config;
|
||||||
pub use get::SubCommand as ConfigGet;
|
pub use get::SubCommand as ConfigGet;
|
||||||
pub use load::SubCommand as ConfigLoad;
|
|
||||||
pub use path::SubCommand as ConfigPath;
|
pub use path::SubCommand as ConfigPath;
|
||||||
pub use remove::SubCommand as ConfigRemove;
|
pub use remove::SubCommand as ConfigRemove;
|
||||||
pub use set::SubCommand as ConfigSet;
|
pub use set::SubCommand as ConfigSet;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_engine::WholeStreamCommand;
|
use nu_engine::WholeStreamCommand;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue};
|
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
|
||||||
|
|
||||||
pub struct SubCommand;
|
pub struct SubCommand;
|
||||||
|
|
||||||
@ -33,9 +33,18 @@ impl WholeStreamCommand for SubCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn path(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
pub async fn path(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
let path = config::default_path()?;
|
|
||||||
|
|
||||||
Ok(OutputStream::one(ReturnSuccess::value(
|
Ok(OutputStream::one(ReturnSuccess::value(
|
||||||
UntaggedValue::Primitive(Primitive::FilePath(path)).into_value(args.call_info.name_tag),
|
match args.scope.get_var("config-path") {
|
||||||
|
Some(
|
||||||
|
path
|
||||||
|
@
|
||||||
|
Value {
|
||||||
|
value: UntaggedValue::Primitive(Primitive::FilePath(_)),
|
||||||
|
..
|
||||||
|
},
|
||||||
|
) => path,
|
||||||
|
_ => UntaggedValue::Primitive(Primitive::FilePath(nu_data::config::default_path()?))
|
||||||
|
.into_value(args.call_info.name_tag),
|
||||||
|
},
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_engine::WholeStreamCommand;
|
use nu_engine::WholeStreamCommand;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
use nu_source::Tagged;
|
use nu_source::Tagged;
|
||||||
|
|
||||||
pub struct SubCommand;
|
pub struct SubCommand;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct RemoveArgs {
|
pub struct Arguments {
|
||||||
remove: Tagged<String>,
|
remove: Tagged<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,15 +44,24 @@ impl WholeStreamCommand for SubCommand {
|
|||||||
|
|
||||||
pub async fn remove(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
pub async fn remove(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
let name_span = args.call_info.name_tag.clone();
|
let name_span = args.call_info.name_tag.clone();
|
||||||
let (RemoveArgs { remove }, _) = args.process().await?;
|
let scope = args.scope.clone();
|
||||||
|
let (Arguments { remove }, _) = args.process().await?;
|
||||||
|
|
||||||
let mut result = nu_data::config::read(name_span, &None)?;
|
let path = match scope.get_var("config-path") {
|
||||||
|
Some(Value {
|
||||||
|
value: UntaggedValue::Primitive(Primitive::FilePath(path)),
|
||||||
|
..
|
||||||
|
}) => Some(path),
|
||||||
|
_ => nu_data::config::default_path().ok(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut result = nu_data::config::read(name_span, &path)?;
|
||||||
|
|
||||||
let key = remove.to_string();
|
let key = remove.to_string();
|
||||||
|
|
||||||
if result.contains_key(&key) {
|
if result.contains_key(&key) {
|
||||||
result.swap_remove(&key);
|
result.swap_remove(&key);
|
||||||
config::write(&result, &None)?;
|
config::write(&result, &path)?;
|
||||||
Ok(futures::stream::iter(vec![ReturnSuccess::value(
|
Ok(futures::stream::iter(vec![ReturnSuccess::value(
|
||||||
UntaggedValue::Row(result.into()).into_value(remove.tag()),
|
UntaggedValue::Row(result.into()).into_value(remove.tag()),
|
||||||
)])
|
)])
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_engine::WholeStreamCommand;
|
use nu_engine::WholeStreamCommand;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
use nu_protocol::{
|
||||||
|
ColumnPath, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct SubCommand;
|
pub struct SubCommand;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct SetArgs {
|
pub struct Arguments {
|
||||||
path: ColumnPath,
|
column_path: ColumnPath,
|
||||||
value: Value,
|
value: Value,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,13 +60,26 @@ impl WholeStreamCommand for SubCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
pub async fn set(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
let name_tag = args.call_info.name_tag.clone();
|
let name = args.call_info.name_tag.clone();
|
||||||
let (SetArgs { path, mut value }, _) = args.process().await?;
|
let scope = args.scope.clone();
|
||||||
|
let (
|
||||||
|
Arguments {
|
||||||
|
column_path,
|
||||||
|
mut value,
|
||||||
|
},
|
||||||
|
_,
|
||||||
|
) = args.process().await?;
|
||||||
|
|
||||||
// NOTE: None because we are not loading a new config file, we just want to read from the
|
let path = match scope.get_var("config-path") {
|
||||||
// existing config
|
Some(Value {
|
||||||
let raw_entries = nu_data::config::read(&name_tag, &None)?;
|
value: UntaggedValue::Primitive(Primitive::FilePath(path)),
|
||||||
let configuration = UntaggedValue::row(raw_entries).into_value(&name_tag);
|
..
|
||||||
|
}) => Some(path),
|
||||||
|
_ => nu_data::config::default_path().ok(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let raw_entries = nu_data::config::read(&name, &path)?;
|
||||||
|
let configuration = UntaggedValue::row(raw_entries).into_value(&name);
|
||||||
|
|
||||||
if let UntaggedValue::Table(rows) = &value.value {
|
if let UntaggedValue::Table(rows) = &value.value {
|
||||||
if rows.len() == 1 && rows[0].is_row() {
|
if rows.len() == 1 && rows[0].is_row() {
|
||||||
@ -72,15 +87,15 @@ pub async fn set(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match configuration.forgiving_insert_data_at_column_path(&path, value) {
|
match configuration.forgiving_insert_data_at_column_path(&column_path, value) {
|
||||||
Ok(Value {
|
Ok(Value {
|
||||||
value: UntaggedValue::Row(changes),
|
value: UntaggedValue::Row(changes),
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
config::write(&changes.entries, &None)?;
|
config::write(&changes.entries, &path)?;
|
||||||
|
|
||||||
Ok(OutputStream::one(ReturnSuccess::value(
|
Ok(OutputStream::one(ReturnSuccess::value(
|
||||||
UntaggedValue::Row(changes).into_value(name_tag),
|
UntaggedValue::Row(changes).into_value(name),
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
Ok(_) => Ok(OutputStream::empty()),
|
Ok(_) => Ok(OutputStream::empty()),
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_engine::WholeStreamCommand;
|
use nu_engine::WholeStreamCommand;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
use nu_source::Tagged;
|
use nu_source::Tagged;
|
||||||
|
|
||||||
pub struct SubCommand;
|
pub struct SubCommand;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct SetIntoArgs {
|
pub struct Arguments {
|
||||||
set_into: Tagged<String>,
|
set_into: Tagged<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,17 +43,19 @@ impl WholeStreamCommand for SubCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_into(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
pub async fn set_into(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
let name_span = args.call_info.name_tag.clone();
|
|
||||||
let name = args.call_info.name_tag.clone();
|
let name = args.call_info.name_tag.clone();
|
||||||
|
let scope = args.scope.clone();
|
||||||
|
let (Arguments { set_into: v }, input) = args.process().await?;
|
||||||
|
|
||||||
let (SetIntoArgs { set_into: v }, input) = args.process().await?;
|
let path = match scope.get_var("config-path") {
|
||||||
|
Some(Value {
|
||||||
|
value: UntaggedValue::Primitive(Primitive::FilePath(path)),
|
||||||
|
..
|
||||||
|
}) => Some(path),
|
||||||
|
_ => nu_data::config::default_path().ok(),
|
||||||
|
};
|
||||||
|
|
||||||
// NOTE: None because we are not loading a new config file, we just want to read from the
|
let mut result = nu_data::config::read(&name, &path)?;
|
||||||
// existing config
|
|
||||||
let mut result = nu_data::config::read(name_span, &None)?;
|
|
||||||
|
|
||||||
// In the original code, this is set to `Some` if the `load flag is set`
|
|
||||||
let configuration = None;
|
|
||||||
|
|
||||||
let rows: Vec<Value> = input.collect().await;
|
let rows: Vec<Value> = input.collect().await;
|
||||||
let key = v.to_string();
|
let key = v.to_string();
|
||||||
@ -70,7 +72,7 @@ pub async fn set_into(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||||||
|
|
||||||
result.insert(key, value.clone());
|
result.insert(key, value.clone());
|
||||||
|
|
||||||
config::write(&result, &configuration)?;
|
config::write(&result, &path)?;
|
||||||
|
|
||||||
OutputStream::one(ReturnSuccess::value(
|
OutputStream::one(ReturnSuccess::value(
|
||||||
UntaggedValue::Row(result.into()).into_value(name),
|
UntaggedValue::Row(result.into()).into_value(name),
|
||||||
@ -81,7 +83,7 @@ pub async fn set_into(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||||||
|
|
||||||
result.insert(key, value);
|
result.insert(key, value);
|
||||||
|
|
||||||
config::write(&result, &configuration)?;
|
config::write(&result, &path)?;
|
||||||
|
|
||||||
OutputStream::one(ReturnSuccess::value(
|
OutputStream::one(ReturnSuccess::value(
|
||||||
UntaggedValue::Row(result.into()).into_value(name),
|
UntaggedValue::Row(result.into()).into_value(name),
|
||||||
|
@ -29,7 +29,6 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
|
|||||||
whole_stream_command(ConfigSet),
|
whole_stream_command(ConfigSet),
|
||||||
whole_stream_command(ConfigSetInto),
|
whole_stream_command(ConfigSetInto),
|
||||||
whole_stream_command(ConfigClear),
|
whole_stream_command(ConfigClear),
|
||||||
whole_stream_command(ConfigLoad),
|
|
||||||
whole_stream_command(ConfigRemove),
|
whole_stream_command(ConfigRemove),
|
||||||
whole_stream_command(ConfigPath),
|
whole_stream_command(ConfigPath),
|
||||||
whole_stream_command(Help),
|
whole_stream_command(Help),
|
||||||
|
@ -1,29 +1,21 @@
|
|||||||
use nu_test_support::fs::Stub::FileWithContentToBeTrimmed;
|
use nu_test_support::pipeline as input;
|
||||||
use nu_test_support::playground::Playground;
|
use nu_test_support::playground::{says, Playground};
|
||||||
use nu_test_support::{nu, pipeline};
|
|
||||||
|
use hamcrest2::assert_that;
|
||||||
|
use hamcrest2::prelude::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn adds_a_row_to_the_end() {
|
fn adds_a_row_to_the_end() {
|
||||||
Playground::setup("append_test_1", |dirs, sandbox| {
|
Playground::setup("append_test_1", |_, nu| {
|
||||||
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
assert_that!(
|
||||||
"los_tres_caballeros.txt",
|
nu.pipeline(&input(
|
||||||
r#"
|
r#"
|
||||||
Andrés N. Robalino
|
echo [ "Andrés N. Robalino", "Jonathan Turner", "Yehuda Katz" ]
|
||||||
Jonathan Turner
|
|
||||||
Yehuda Katz
|
|
||||||
"#,
|
|
||||||
)]);
|
|
||||||
|
|
||||||
let actual = nu!(
|
|
||||||
cwd: dirs.test(), pipeline(
|
|
||||||
r#"
|
|
||||||
open los_tres_caballeros.txt
|
|
||||||
| lines
|
|
||||||
| append "pollo loco"
|
| append "pollo loco"
|
||||||
| nth 3
|
| nth 3
|
||||||
"#
|
"#
|
||||||
));
|
)),
|
||||||
|
says().to_stdout("pollo loco")
|
||||||
assert_eq!(actual.out, "pollo loco");
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1 +0,0 @@
|
|||||||
|
|
@ -1 +0,0 @@
|
|||||||
|
|
@ -1 +0,0 @@
|
|||||||
|
|
@ -1,5 +1,5 @@
|
|||||||
use nu_test_support::fs::Stub::EmptyFile;
|
use nu_test_support::fs::Stub::EmptyFile;
|
||||||
use nu_test_support::playground::{Dirs, Playground};
|
use nu_test_support::playground::Playground;
|
||||||
use nu_test_support::{nu, pipeline};
|
use nu_test_support::{nu, pipeline};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -269,61 +269,57 @@ fn lists_files_including_starting_with_dot() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn list_all_columns() {
|
fn list_all_columns() {
|
||||||
Playground::setup(
|
Playground::setup("ls_test_all_columns", |dirs, sandbox| {
|
||||||
"ls_test_all_columns",
|
sandbox.with_files(vec![
|
||||||
|dirs: Dirs, sandbox: &mut Playground| {
|
EmptyFile("Leonardo.yaml"),
|
||||||
sandbox.with_files(vec![
|
EmptyFile("Raphael.json"),
|
||||||
EmptyFile("Leonardo.yaml"),
|
EmptyFile("Donatello.xml"),
|
||||||
EmptyFile("Raphael.json"),
|
EmptyFile("Michelangelo.txt"),
|
||||||
EmptyFile("Donatello.xml"),
|
]);
|
||||||
EmptyFile("Michelangelo.txt"),
|
// Normal Operation
|
||||||
]);
|
let actual = nu!(
|
||||||
// Normal Operation
|
cwd: dirs.test(),
|
||||||
let actual = nu!(
|
"ls | get | to md"
|
||||||
cwd: dirs.test(),
|
);
|
||||||
"ls | get | to md"
|
let expected = ["name", "type", "size", "modified"].join("");
|
||||||
);
|
assert_eq!(actual.out, expected, "column names are incorrect for ls");
|
||||||
let expected = ["name", "type", "size", "modified"].join("");
|
// Long
|
||||||
assert_eq!(actual.out, expected, "column names are incorrect for ls");
|
let actual = nu!(
|
||||||
// Long
|
cwd: dirs.test(),
|
||||||
let actual = nu!(
|
"ls -l | get | to md"
|
||||||
cwd: dirs.test(),
|
);
|
||||||
"ls -l | get | to md"
|
let expected = {
|
||||||
);
|
#[cfg(unix)]
|
||||||
let expected = {
|
{
|
||||||
#[cfg(unix)]
|
[
|
||||||
{
|
"name",
|
||||||
[
|
"type",
|
||||||
"name",
|
"target",
|
||||||
"type",
|
"num_links",
|
||||||
"target",
|
"inode",
|
||||||
"num_links",
|
"readonly",
|
||||||
"inode",
|
"mode",
|
||||||
"readonly",
|
"uid",
|
||||||
"mode",
|
"group",
|
||||||
"uid",
|
"size",
|
||||||
"group",
|
"created",
|
||||||
"size",
|
"accessed",
|
||||||
"created",
|
"modified",
|
||||||
"accessed",
|
]
|
||||||
"modified",
|
.join("")
|
||||||
]
|
}
|
||||||
.join("")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
[
|
[
|
||||||
"name", "type", "target", "readonly", "size", "created", "accessed",
|
"name", "type", "target", "readonly", "size", "created", "accessed", "modified",
|
||||||
"modified",
|
]
|
||||||
]
|
.join("")
|
||||||
.join("")
|
}
|
||||||
}
|
};
|
||||||
};
|
assert_eq!(
|
||||||
assert_eq!(
|
actual.out, expected,
|
||||||
actual.out, expected,
|
"column names are incorrect for ls long"
|
||||||
"column names are incorrect for ls long"
|
);
|
||||||
);
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
mod append;
|
mod append;
|
||||||
mod autoenv;
|
|
||||||
mod autoenv_trust;
|
|
||||||
mod autoenv_untrust;
|
|
||||||
mod cal;
|
mod cal;
|
||||||
mod cd;
|
mod cd;
|
||||||
mod compact;
|
mod compact;
|
||||||
|
@ -47,11 +47,13 @@ impl Conf for NuConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl NuConfig {
|
impl NuConfig {
|
||||||
pub fn with(config_file: Option<std::path::PathBuf>) -> NuConfig {
|
pub fn with(config_file: Option<std::ffi::OsString>) -> NuConfig {
|
||||||
match &config_file {
|
match &config_file {
|
||||||
None => NuConfig::new(),
|
None => NuConfig::new(),
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
let vars = if let Ok(variables) = read(Tag::unknown(), &config_file) {
|
let source_file = config_file.map(std::path::PathBuf::from);
|
||||||
|
|
||||||
|
let vars = if let Ok(variables) = read(Tag::unknown(), &source_file) {
|
||||||
variables
|
variables
|
||||||
} else {
|
} else {
|
||||||
IndexMap::default()
|
IndexMap::default()
|
||||||
@ -59,7 +61,7 @@ impl NuConfig {
|
|||||||
|
|
||||||
NuConfig {
|
NuConfig {
|
||||||
vars,
|
vars,
|
||||||
modified_at: NuConfig::get_last_modified(&config_file),
|
modified_at: NuConfig::get_last_modified(&source_file),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,11 +36,11 @@ impl Conf for FakeConfig {
|
|||||||
|
|
||||||
impl FakeConfig {
|
impl FakeConfig {
|
||||||
pub fn new(config_file: &Path) -> FakeConfig {
|
pub fn new(config_file: &Path) -> FakeConfig {
|
||||||
let config_file = Some(PathBuf::from(config_file));
|
let config_file = config_file.to_path_buf();
|
||||||
|
|
||||||
FakeConfig {
|
FakeConfig {
|
||||||
config: NuConfig::with(config_file.clone()),
|
config: NuConfig::with(Some(config_file.clone().into_os_string())),
|
||||||
source_file: config_file,
|
source_file: Some(config_file),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,6 +61,6 @@ impl FakeConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn reload(&mut self) {
|
pub fn reload(&mut self) {
|
||||||
self.config = NuConfig::with(self.source_file.clone());
|
self.config = NuConfig::with(self.source_file.clone().map(|x| x.into_os_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,7 @@ users = "0.11.0"
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-test-support = { version = "0.28.0", path = "../nu-test-support" }
|
nu-test-support = { version = "0.28.0", path = "../nu-test-support" }
|
||||||
|
hamcrest2 = "0.3.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
rustyline-support = []
|
rustyline-support = []
|
||||||
|
@ -232,7 +232,7 @@ fn evaluate_literal(literal: &hir::Literal, span: Span) -> Value {
|
|||||||
|
|
||||||
fn evaluate_reference(name: &str, ctx: &EvaluationContext, tag: Tag) -> Result<Value, ShellError> {
|
fn evaluate_reference(name: &str, ctx: &EvaluationContext, tag: Tag) -> Result<Value, ShellError> {
|
||||||
match name {
|
match name {
|
||||||
"$nu" => crate::evaluate::variables::nu(&ctx.scope.get_env_vars(), tag),
|
"$nu" => crate::evaluate::variables::nu(&ctx.scope, tag),
|
||||||
|
|
||||||
"$true" => Ok(Value {
|
"$true" => Ok(Value {
|
||||||
value: UntaggedValue::boolean(true),
|
value: UntaggedValue::boolean(true),
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
|
use crate::evaluate::scope::Scope;
|
||||||
use crate::history_path::history_path;
|
use crate::history_path::history_path;
|
||||||
use indexmap::IndexMap;
|
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{TaggedDictBuilder, UntaggedValue, Value};
|
use nu_protocol::{Primitive, TaggedDictBuilder, UntaggedValue, Value};
|
||||||
use nu_source::Tag;
|
use nu_source::Tag;
|
||||||
|
|
||||||
pub fn nu(env: &IndexMap<String, String>, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
pub fn nu(scope: &Scope, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||||
|
let env = &scope.get_env_vars();
|
||||||
let tag = tag.into();
|
let tag = tag.into();
|
||||||
|
|
||||||
let mut nu_dict = TaggedDictBuilder::new(&tag);
|
let mut nu_dict = TaggedDictBuilder::new(&tag);
|
||||||
@ -17,7 +18,15 @@ pub fn nu(env: &IndexMap<String, String>, tag: impl Into<Tag>) -> Result<Value,
|
|||||||
}
|
}
|
||||||
nu_dict.insert_value("env", dict.into_value());
|
nu_dict.insert_value("env", dict.into_value());
|
||||||
|
|
||||||
let config = nu_data::config::read(&tag, &None)?;
|
let config_file = match scope.get_var("config-path") {
|
||||||
|
Some(Value {
|
||||||
|
value: UntaggedValue::Primitive(Primitive::FilePath(path)),
|
||||||
|
..
|
||||||
|
}) => Some(path),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let config = nu_data::config::read(&tag, &config_file)?;
|
||||||
nu_dict.insert_value("config", UntaggedValue::row(config).into_value(&tag));
|
nu_dict.insert_value("config", UntaggedValue::row(config).into_value(&tag));
|
||||||
|
|
||||||
let mut table = vec![];
|
let mut table = vec![];
|
||||||
@ -40,7 +49,12 @@ pub fn nu(env: &IndexMap<String, String>, tag: impl Into<Tag>) -> Result<Value,
|
|||||||
let temp = std::env::temp_dir();
|
let temp = std::env::temp_dir();
|
||||||
nu_dict.insert_value("temp-dir", UntaggedValue::filepath(temp).into_value(&tag));
|
nu_dict.insert_value("temp-dir", UntaggedValue::filepath(temp).into_value(&tag));
|
||||||
|
|
||||||
let config = nu_data::config::default_path()?;
|
let config = if let Some(path) = config_file {
|
||||||
|
path
|
||||||
|
} else {
|
||||||
|
nu_data::config::default_path()?
|
||||||
|
};
|
||||||
|
|
||||||
nu_dict.insert_value(
|
nu_dict.insert_value(
|
||||||
"config-path",
|
"config-path",
|
||||||
UntaggedValue::filepath(config).into_value(&tag),
|
UntaggedValue::filepath(config).into_value(&tag),
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
mod invocation;
|
mod invocation;
|
||||||
mod operator;
|
mod operator;
|
||||||
|
mod variables;
|
||||||
|
34
crates/nu-engine/tests/evaluate/variables.rs
Normal file
34
crates/nu-engine/tests/evaluate/variables.rs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
use nu_test_support::fs::Stub::FileWithContent;
|
||||||
|
use nu_test_support::fs::{AbsolutePath, DisplayPath};
|
||||||
|
use nu_test_support::playground::{says, Playground};
|
||||||
|
|
||||||
|
use hamcrest2::assert_that;
|
||||||
|
use hamcrest2::prelude::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn config_path_variable_present() {
|
||||||
|
Playground::setup("nu_variable_test_1", |_, nu| {
|
||||||
|
assert_that!(
|
||||||
|
nu.pipeline("echo $nu.config-path"),
|
||||||
|
says().to_stdout(nu.get_config())
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn custom_config_path_variable_present() {
|
||||||
|
Playground::setup("nu_variable_test_2", |dirs, nu| {
|
||||||
|
let file = AbsolutePath::new(dirs.test().join("config.toml"));
|
||||||
|
|
||||||
|
nu.with_config(&file);
|
||||||
|
nu.with_files(vec![FileWithContent(
|
||||||
|
"config.toml",
|
||||||
|
"skip_welcome_message = true",
|
||||||
|
)]);
|
||||||
|
|
||||||
|
assert_that!(
|
||||||
|
nu.pipeline("echo $nu.config-path"),
|
||||||
|
says().to_stdout(&file.display_path())
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
0
crates/nu-protocol/src/value/unit.rs
Normal file
0
crates/nu-protocol/src/value/unit.rs
Normal file
@ -23,5 +23,4 @@ glob = "0.3.0"
|
|||||||
indexmap = { version = "1.6.1", features = ["serde-1"] }
|
indexmap = { version = "1.6.1", features = ["serde-1"] }
|
||||||
num-bigint = { version = "0.3.1", features = ["serde"] }
|
num-bigint = { version = "0.3.1", features = ["serde"] }
|
||||||
tempfile = "3.2.0"
|
tempfile = "3.2.0"
|
||||||
|
hamcrest2 = "0.3.0"
|
||||||
[build-dependencies]
|
|
@ -1,176 +1,12 @@
|
|||||||
use crate::fs;
|
mod director;
|
||||||
use crate::fs::Stub;
|
pub mod matchers;
|
||||||
|
pub mod nu_process;
|
||||||
|
mod play;
|
||||||
|
|
||||||
use getset::Getters;
|
#[cfg(test)]
|
||||||
use glob::glob;
|
mod tests;
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use tempfile::{tempdir, TempDir};
|
|
||||||
|
|
||||||
pub struct Playground {
|
pub use director::Director;
|
||||||
root: TempDir,
|
pub use matchers::says;
|
||||||
tests: String,
|
pub use nu_process::{Executable, NuProcess, NuResult, Outcome};
|
||||||
cwd: PathBuf,
|
pub use play::{Dirs, Playground};
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Getters)]
|
|
||||||
#[get = "pub"]
|
|
||||||
pub struct Dirs {
|
|
||||||
pub root: PathBuf,
|
|
||||||
pub test: PathBuf,
|
|
||||||
pub fixtures: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Dirs {
|
|
||||||
pub fn formats(&self) -> PathBuf {
|
|
||||||
self.fixtures.join("formats")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Playground {
|
|
||||||
pub fn root(&self) -> &Path {
|
|
||||||
self.root.path()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn back_to_playground(&mut self) -> &mut Self {
|
|
||||||
self.cwd = PathBuf::from(self.root()).join(self.tests.clone());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setup(topic: &str, block: impl FnOnce(Dirs, &mut Playground)) {
|
|
||||||
let root = tempdir().expect("Couldn't create a tempdir");
|
|
||||||
let nuplay_dir = root.path().join(topic);
|
|
||||||
|
|
||||||
if PathBuf::from(&nuplay_dir).exists() {
|
|
||||||
std::fs::remove_dir_all(PathBuf::from(&nuplay_dir)).expect("can not remove directory");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::fs::create_dir(PathBuf::from(&nuplay_dir)).expect("can not create directory");
|
|
||||||
|
|
||||||
let mut playground = Playground {
|
|
||||||
root,
|
|
||||||
tests: topic.to_string(),
|
|
||||||
cwd: nuplay_dir,
|
|
||||||
};
|
|
||||||
|
|
||||||
let playground_root = playground.root.path();
|
|
||||||
|
|
||||||
let fixtures = fs::fixtures();
|
|
||||||
let fixtures = dunce::canonicalize(fixtures.clone()).unwrap_or_else(|e| {
|
|
||||||
panic!(
|
|
||||||
"Couldn't canonicalize fixtures path {}: {:?}",
|
|
||||||
fixtures.display(),
|
|
||||||
e
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
let test = dunce::canonicalize(playground_root.join(topic)).unwrap_or_else(|e| {
|
|
||||||
panic!(
|
|
||||||
"Couldn't canonicalize test path {}: {:?}",
|
|
||||||
playground_root.join(topic).display(),
|
|
||||||
e
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
let root = dunce::canonicalize(playground_root).unwrap_or_else(|e| {
|
|
||||||
panic!(
|
|
||||||
"Couldn't canonicalize tests root path {}: {:?}",
|
|
||||||
playground_root.display(),
|
|
||||||
e
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
let dirs = Dirs {
|
|
||||||
root,
|
|
||||||
test,
|
|
||||||
fixtures,
|
|
||||||
};
|
|
||||||
|
|
||||||
block(dirs, &mut playground);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn mkdir(&mut self, directory: &str) -> &mut Self {
|
|
||||||
self.cwd.push(directory);
|
|
||||||
std::fs::create_dir_all(&self.cwd).expect("can not create directory");
|
|
||||||
self.back_to_playground();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
pub fn symlink(&mut self, from: impl AsRef<Path>, to: impl AsRef<Path>) -> &mut Self {
|
|
||||||
let from = self.cwd.join(from);
|
|
||||||
let to = self.cwd.join(to);
|
|
||||||
|
|
||||||
let create_symlink = {
|
|
||||||
#[cfg(unix)]
|
|
||||||
{
|
|
||||||
std::os::unix::fs::symlink
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
{
|
|
||||||
if from.is_file() {
|
|
||||||
std::os::windows::fs::symlink_file
|
|
||||||
} else if from.is_dir() {
|
|
||||||
std::os::windows::fs::symlink_dir
|
|
||||||
} else {
|
|
||||||
panic!("symlink from must be a file or dir")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
create_symlink(from, to).expect("can not create symlink");
|
|
||||||
self.back_to_playground();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_files(&mut self, files: Vec<Stub>) -> &mut Self {
|
|
||||||
let endl = fs::line_ending();
|
|
||||||
|
|
||||||
files
|
|
||||||
.iter()
|
|
||||||
.map(|f| {
|
|
||||||
let mut path = PathBuf::from(&self.cwd);
|
|
||||||
|
|
||||||
let (file_name, contents) = match *f {
|
|
||||||
Stub::EmptyFile(name) => (name, "fake data".to_string()),
|
|
||||||
Stub::FileWithContent(name, content) => (name, content.to_string()),
|
|
||||||
Stub::FileWithContentToBeTrimmed(name, content) => (
|
|
||||||
name,
|
|
||||||
content
|
|
||||||
.lines()
|
|
||||||
.skip(1)
|
|
||||||
.map(|line| line.trim())
|
|
||||||
.collect::<Vec<&str>>()
|
|
||||||
.join(&endl),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
path.push(file_name);
|
|
||||||
|
|
||||||
std::fs::write(path, contents.as_bytes()).expect("can not create file");
|
|
||||||
})
|
|
||||||
.for_each(drop);
|
|
||||||
self.back_to_playground();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn within(&mut self, directory: &str) -> &mut Self {
|
|
||||||
self.cwd.push(directory);
|
|
||||||
std::fs::create_dir(&self.cwd).expect("can not create directory");
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn glob_vec(pattern: &str) -> Vec<PathBuf> {
|
|
||||||
let glob = glob(pattern);
|
|
||||||
|
|
||||||
glob.expect("invalid pattern")
|
|
||||||
.map(|path| {
|
|
||||||
if let Ok(path) = path {
|
|
||||||
path
|
|
||||||
} else {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
141
crates/nu-test-support/src/playground/director.rs
Normal file
141
crates/nu-test-support/src/playground/director.rs
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
use super::nu_process::*;
|
||||||
|
use std::ffi::OsString;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct Director {
|
||||||
|
pub cwd: Option<OsString>,
|
||||||
|
pub config: Option<OsString>,
|
||||||
|
pub pipeline: Option<String>,
|
||||||
|
pub executable: Option<NuProcess>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Director {
|
||||||
|
pub fn cococo(&self, arg: &str) -> Self {
|
||||||
|
let mut process = NuProcess::default();
|
||||||
|
process.args(&["--testbin", "cococo", arg]);
|
||||||
|
Director {
|
||||||
|
config: self.config.clone(),
|
||||||
|
executable: Some(process),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pipeline(&self, commands: &str) -> Self {
|
||||||
|
let mut director = Director {
|
||||||
|
pipeline: if commands.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(format!(
|
||||||
|
"
|
||||||
|
{}
|
||||||
|
exit",
|
||||||
|
commands
|
||||||
|
))
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut process = NuProcess::default();
|
||||||
|
|
||||||
|
if let Some(working_directory) = &self.cwd {
|
||||||
|
process.cwd(working_directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
process.arg("--skip-plugins");
|
||||||
|
if let Some(config_file) = self.config.as_ref() {
|
||||||
|
process.args(&[
|
||||||
|
"--config-file",
|
||||||
|
config_file.to_str().expect("failed to convert."),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
director.executable = Some(process);
|
||||||
|
director
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn executable(&self) -> Option<&NuProcess> {
|
||||||
|
if let Some(binary) = &self.executable {
|
||||||
|
Some(binary)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Executable for Director {
|
||||||
|
fn execute(&self) -> NuResult {
|
||||||
|
use std::io::Write;
|
||||||
|
use std::process::Stdio;
|
||||||
|
|
||||||
|
match self.executable() {
|
||||||
|
Some(binary) => {
|
||||||
|
let mut process = match binary
|
||||||
|
.construct()
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
{
|
||||||
|
Ok(child) => child,
|
||||||
|
Err(why) => panic!("Can't run test {}", why.to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(pipeline) = &self.pipeline {
|
||||||
|
process
|
||||||
|
.stdin
|
||||||
|
.as_mut()
|
||||||
|
.expect("couldn't open stdin")
|
||||||
|
.write_all(pipeline.as_bytes())
|
||||||
|
.expect("couldn't write to stdin");
|
||||||
|
}
|
||||||
|
|
||||||
|
process
|
||||||
|
.wait_with_output()
|
||||||
|
.map_err(|_| {
|
||||||
|
let reason = format!(
|
||||||
|
"could not execute process {} ({})",
|
||||||
|
binary, "No execution took place"
|
||||||
|
);
|
||||||
|
|
||||||
|
NuError {
|
||||||
|
desc: reason,
|
||||||
|
exit: None,
|
||||||
|
output: None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.and_then(|process| {
|
||||||
|
let out =
|
||||||
|
Outcome::new(&read_std(&process.stdout), &read_std(&process.stderr));
|
||||||
|
|
||||||
|
match process.status.success() {
|
||||||
|
true => Ok(out),
|
||||||
|
false => Err(NuError {
|
||||||
|
desc: String::new(),
|
||||||
|
exit: Some(process.status),
|
||||||
|
output: Some(out),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
None => Err(NuError {
|
||||||
|
desc: String::from("err"),
|
||||||
|
exit: None,
|
||||||
|
output: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Director {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "director")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_std(std: &[u8]) -> Vec<u8> {
|
||||||
|
let out = String::from_utf8_lossy(std);
|
||||||
|
let out = out.lines().collect::<Vec<_>>().join("\n");
|
||||||
|
let out = out.replace("\r\n", "");
|
||||||
|
out.replace("\n", "").into_bytes()
|
||||||
|
}
|
106
crates/nu-test-support/src/playground/matchers.rs
Normal file
106
crates/nu-test-support/src/playground/matchers.rs
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
use hamcrest2::core::{MatchResult, Matcher};
|
||||||
|
use std::fmt;
|
||||||
|
use std::str;
|
||||||
|
|
||||||
|
use super::nu_process::Outcome;
|
||||||
|
use super::{Director, Executable};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Play {
|
||||||
|
stdout_expectation: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Play {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "play")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Play {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "play")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn says() -> Play {
|
||||||
|
Play {
|
||||||
|
stdout_expectation: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait CheckerMatchers {
|
||||||
|
fn output(&self, actual: &Outcome) -> MatchResult;
|
||||||
|
fn std(&self, actual: &[u8], expected: Option<&String>, description: &str) -> MatchResult;
|
||||||
|
fn stdout(&self, actual: &Outcome) -> MatchResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CheckerMatchers for Play {
|
||||||
|
fn output(&self, actual: &Outcome) -> MatchResult {
|
||||||
|
self.stdout(actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stdout(&self, actual: &Outcome) -> MatchResult {
|
||||||
|
self.std(&actual.out, self.stdout_expectation.as_ref(), "stdout")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn std(&self, actual: &[u8], expected: Option<&String>, description: &str) -> MatchResult {
|
||||||
|
let out = match expected {
|
||||||
|
Some(out) => out,
|
||||||
|
None => return Ok(()),
|
||||||
|
};
|
||||||
|
let actual = match str::from_utf8(actual) {
|
||||||
|
Err(..) => return Err(format!("{} was not utf8 encoded", description)),
|
||||||
|
Ok(actual) => actual,
|
||||||
|
};
|
||||||
|
|
||||||
|
if actual != *out {
|
||||||
|
return Err(format!(
|
||||||
|
"not equal:\n actual: {}\n expected: {}\n\n",
|
||||||
|
actual, out
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Matcher<Outcome> for Play {
|
||||||
|
fn matches(&self, output: Outcome) -> MatchResult {
|
||||||
|
self.output(&output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Matcher<Director> for Play {
|
||||||
|
fn matches(&self, mut director: Director) -> MatchResult {
|
||||||
|
self.matches(&mut director)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Matcher<&'a mut Director> for Play {
|
||||||
|
fn matches(&self, director: &'a mut Director) -> MatchResult {
|
||||||
|
if director.executable().is_none() {
|
||||||
|
return Err(format!("no such process {}", director));
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = director.execute();
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(out) => self.output(&out),
|
||||||
|
Err(err) => {
|
||||||
|
if let Some(out) = &err.output {
|
||||||
|
return self.output(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(format!("could not exec process {}: {:?}", director, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Play {
|
||||||
|
#[allow(clippy::clippy::wrong_self_convention)]
|
||||||
|
pub fn to_stdout(mut self, expected: &str) -> Self {
|
||||||
|
self.stdout_expectation = Some(expected.to_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
99
crates/nu-test-support/src/playground/nu_process.rs
Normal file
99
crates/nu-test-support/src/playground/nu_process.rs
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
use crate::fs::executable_path;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::ffi::{OsStr, OsString};
|
||||||
|
use std::fmt;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::process::{Command, ExitStatus};
|
||||||
|
|
||||||
|
pub trait Executable {
|
||||||
|
fn execute(&self) -> NuResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Outcome {
|
||||||
|
pub out: Vec<u8>,
|
||||||
|
pub err: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Outcome {
|
||||||
|
pub fn new(out: &[u8], err: &[u8]) -> Outcome {
|
||||||
|
Outcome {
|
||||||
|
out: out.to_vec(),
|
||||||
|
err: err.to_vec(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type NuResult = Result<Outcome, NuError>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct NuError {
|
||||||
|
pub desc: String,
|
||||||
|
pub exit: Option<ExitStatus>,
|
||||||
|
pub output: Option<Outcome>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct NuProcess {
|
||||||
|
pub arguments: Vec<OsString>,
|
||||||
|
pub environment_vars: HashMap<String, Option<OsString>>,
|
||||||
|
pub cwd: Option<OsString>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for NuProcess {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "`nu")?;
|
||||||
|
|
||||||
|
for arg in &self.arguments {
|
||||||
|
write!(f, " {}", arg.to_string_lossy())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(f, "`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for NuProcess {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
arguments: vec![],
|
||||||
|
environment_vars: HashMap::default(),
|
||||||
|
cwd: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NuProcess {
|
||||||
|
pub fn arg<T: AsRef<OsStr>>(&mut self, arg: T) -> &mut Self {
|
||||||
|
self.arguments.push(arg.as_ref().to_os_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn args<T: AsRef<OsStr>>(&mut self, arguments: &[T]) -> &mut NuProcess {
|
||||||
|
self.arguments
|
||||||
|
.extend(arguments.iter().map(|t| t.as_ref().to_os_string()));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cwd<T: AsRef<OsStr>>(&mut self, path: T) -> &mut NuProcess {
|
||||||
|
self.cwd = Some(path.as_ref().to_os_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_cwd(&self) -> Option<&Path> {
|
||||||
|
self.cwd.as_ref().map(Path::new)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn construct(&self) -> Command {
|
||||||
|
let mut command = Command::new(&executable_path());
|
||||||
|
|
||||||
|
if let Some(cwd) = self.get_cwd() {
|
||||||
|
command.current_dir(cwd);
|
||||||
|
}
|
||||||
|
|
||||||
|
for arg in &self.arguments {
|
||||||
|
command.arg(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
command
|
||||||
|
}
|
||||||
|
}
|
216
crates/nu-test-support/src/playground/play.rs
Normal file
216
crates/nu-test-support/src/playground/play.rs
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
use super::Director;
|
||||||
|
use crate::fs;
|
||||||
|
use crate::fs::Stub;
|
||||||
|
use getset::Getters;
|
||||||
|
use glob::glob;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::str;
|
||||||
|
use tempfile::{tempdir, TempDir};
|
||||||
|
|
||||||
|
pub struct Playground<'a> {
|
||||||
|
root: TempDir,
|
||||||
|
tests: String,
|
||||||
|
cwd: PathBuf,
|
||||||
|
config: PathBuf,
|
||||||
|
dirs: &'a Dirs,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Getters, Clone)]
|
||||||
|
#[get = "pub"]
|
||||||
|
pub struct Dirs {
|
||||||
|
pub root: PathBuf,
|
||||||
|
pub test: PathBuf,
|
||||||
|
pub fixtures: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dirs {
|
||||||
|
pub fn formats(&self) -> PathBuf {
|
||||||
|
self.fixtures.join("formats")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Playground<'a> {
|
||||||
|
pub fn root(&self) -> &Path {
|
||||||
|
self.root.path()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cwd(&self) -> &Path {
|
||||||
|
&self.cwd
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn back_to_playground(&mut self) -> &mut Self {
|
||||||
|
self.cwd = PathBuf::from(self.root()).join(self.tests.clone());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn play(&mut self) -> &mut Self {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setup(topic: &str, block: impl FnOnce(Dirs, &mut Playground)) {
|
||||||
|
let root = tempdir().expect("Couldn't create a tempdir");
|
||||||
|
let nuplay_dir = root.path().join(topic);
|
||||||
|
|
||||||
|
if PathBuf::from(&nuplay_dir).exists() {
|
||||||
|
std::fs::remove_dir_all(PathBuf::from(&nuplay_dir)).expect("can not remove directory");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::fs::create_dir(PathBuf::from(&nuplay_dir)).expect("can not create directory");
|
||||||
|
|
||||||
|
let fixtures = fs::fixtures();
|
||||||
|
let fixtures = dunce::canonicalize(fixtures.clone()).unwrap_or_else(|e| {
|
||||||
|
panic!(
|
||||||
|
"Couldn't canonicalize fixtures path {}: {:?}",
|
||||||
|
fixtures.display(),
|
||||||
|
e
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut playground = Playground {
|
||||||
|
root,
|
||||||
|
tests: topic.to_string(),
|
||||||
|
cwd: nuplay_dir,
|
||||||
|
config: fixtures.join("playground/config/default.toml"),
|
||||||
|
dirs: &Dirs::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let playground_root = playground.root.path();
|
||||||
|
|
||||||
|
let test = dunce::canonicalize(playground_root.join(topic)).unwrap_or_else(|e| {
|
||||||
|
panic!(
|
||||||
|
"Couldn't canonicalize test path {}: {:?}",
|
||||||
|
playground_root.join(topic).display(),
|
||||||
|
e
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let root = dunce::canonicalize(playground_root).unwrap_or_else(|e| {
|
||||||
|
panic!(
|
||||||
|
"Couldn't canonicalize tests root path {}: {:?}",
|
||||||
|
playground_root.display(),
|
||||||
|
e
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let dirs = Dirs {
|
||||||
|
root,
|
||||||
|
test,
|
||||||
|
fixtures,
|
||||||
|
};
|
||||||
|
|
||||||
|
playground.dirs = &dirs;
|
||||||
|
|
||||||
|
block(dirs.clone(), &mut playground);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_config(&mut self, source_file: impl AsRef<Path>) -> &mut Self {
|
||||||
|
self.config = source_file.as_ref().to_path_buf();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_config(&self) -> &str {
|
||||||
|
self.config.to_str().expect("could not convert path.")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(&mut self) -> Director {
|
||||||
|
Director {
|
||||||
|
cwd: Some(self.dirs.test().into()),
|
||||||
|
config: Some(self.config.clone().into()),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cococo(&mut self, arg: &str) -> Director {
|
||||||
|
self.build().cococo(arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pipeline(&mut self, commands: &str) -> Director {
|
||||||
|
self.build().pipeline(commands)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mkdir(&mut self, directory: &str) -> &mut Self {
|
||||||
|
self.cwd.push(directory);
|
||||||
|
std::fs::create_dir_all(&self.cwd).expect("can not create directory");
|
||||||
|
self.back_to_playground();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pub fn symlink(&mut self, from: impl AsRef<Path>, to: impl AsRef<Path>) -> &mut Self {
|
||||||
|
let from = self.cwd.join(from);
|
||||||
|
let to = self.cwd.join(to);
|
||||||
|
|
||||||
|
let create_symlink = {
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
std::os::unix::fs::symlink
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
if from.is_file() {
|
||||||
|
std::os::windows::fs::symlink_file
|
||||||
|
} else if from.is_dir() {
|
||||||
|
std::os::windows::fs::symlink_dir
|
||||||
|
} else {
|
||||||
|
panic!("symlink from must be a file or dir")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
create_symlink(from, to).expect("can not create symlink");
|
||||||
|
self.back_to_playground();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_files(&mut self, files: Vec<Stub>) -> &mut Self {
|
||||||
|
let endl = fs::line_ending();
|
||||||
|
|
||||||
|
files
|
||||||
|
.iter()
|
||||||
|
.map(|f| {
|
||||||
|
let mut path = PathBuf::from(&self.cwd);
|
||||||
|
|
||||||
|
let (file_name, contents) = match *f {
|
||||||
|
Stub::EmptyFile(name) => (name, "fake data".to_string()),
|
||||||
|
Stub::FileWithContent(name, content) => (name, content.to_string()),
|
||||||
|
Stub::FileWithContentToBeTrimmed(name, content) => (
|
||||||
|
name,
|
||||||
|
content
|
||||||
|
.lines()
|
||||||
|
.skip(1)
|
||||||
|
.map(|line| line.trim())
|
||||||
|
.collect::<Vec<&str>>()
|
||||||
|
.join(&endl),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
path.push(file_name);
|
||||||
|
|
||||||
|
std::fs::write(path, contents.as_bytes()).expect("can not create file");
|
||||||
|
})
|
||||||
|
.for_each(drop);
|
||||||
|
self.back_to_playground();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn within(&mut self, directory: &str) -> &mut Self {
|
||||||
|
self.cwd.push(directory);
|
||||||
|
std::fs::create_dir(&self.cwd).expect("can not create directory");
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn glob_vec(pattern: &str) -> Vec<PathBuf> {
|
||||||
|
let glob = glob(pattern);
|
||||||
|
|
||||||
|
glob.expect("invalid pattern")
|
||||||
|
.map(|path| {
|
||||||
|
if let Ok(path) = path {
|
||||||
|
path
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
47
crates/nu-test-support/src/playground/tests.rs
Normal file
47
crates/nu-test-support/src/playground/tests.rs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
use crate::playground::Playground;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use super::matchers::says;
|
||||||
|
use hamcrest2::assert_that;
|
||||||
|
use hamcrest2::prelude::*;
|
||||||
|
|
||||||
|
fn path(p: &Path) -> PathBuf {
|
||||||
|
dunce::canonicalize(p)
|
||||||
|
.unwrap_or_else(|e| panic!("Couldn't canonicalize path {}: {:?}", p.display(), e))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn asserts_standard_out_expectation_from_nu_executable() {
|
||||||
|
Playground::setup("topic", |_, nu| {
|
||||||
|
assert_that!(nu.cococo("andres"), says().to_stdout("andres"));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn asserts_standard_out_expectation_from_nu_executable_pipeline_fed() {
|
||||||
|
Playground::setup("topic", |_, nu| {
|
||||||
|
assert_that!(nu.pipeline("echo 'andres'"), says().to_stdout("andres"));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn current_working_directory_in_sandbox_directory_created() {
|
||||||
|
Playground::setup("topic", |dirs, nu| {
|
||||||
|
let original_cwd = dirs.test();
|
||||||
|
nu.within("some_directory_within");
|
||||||
|
|
||||||
|
assert_eq!(path(&nu.cwd()), original_cwd.join("some_directory_within"));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn current_working_directory_back_to_root_from_anywhere() {
|
||||||
|
Playground::setup("topic", |dirs, nu| {
|
||||||
|
let original_cwd = dirs.test();
|
||||||
|
|
||||||
|
nu.within("some_directory_within");
|
||||||
|
nu.back_to_playground();
|
||||||
|
|
||||||
|
assert_eq!(path(&nu.cwd()), *original_cwd);
|
||||||
|
})
|
||||||
|
}
|
44
src/main.rs
44
src/main.rs
@ -1,14 +1,21 @@
|
|||||||
use clap::{App, Arg};
|
use clap::{App, Arg};
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
use nu_cli::create_default_context;
|
use nu_cli::{create_default_context, NuScript, Options};
|
||||||
use nu_command::utils::test_bins as binaries;
|
use nu_command::utils::test_bins as binaries;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fs::File;
|
|
||||||
use std::io::prelude::*;
|
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut options = Options::new();
|
||||||
|
|
||||||
let matches = App::new("nushell")
|
let matches = App::new("nushell")
|
||||||
.version(clap::crate_version!())
|
.version(clap::crate_version!())
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("config-file")
|
||||||
|
.long("config-file")
|
||||||
|
.help("custom configuration source file")
|
||||||
|
.hidden(true)
|
||||||
|
.takes_value(true),
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("loglevel")
|
Arg::with_name("loglevel")
|
||||||
.short("l")
|
.short("l")
|
||||||
@ -84,6 +91,11 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
options.config = matches
|
||||||
|
.value_of("config-file")
|
||||||
|
.map(std::ffi::OsString::from);
|
||||||
|
options.stdin = matches.is_present("stdin");
|
||||||
|
|
||||||
let loglevel = match matches.value_of("loglevel") {
|
let loglevel = match matches.value_of("loglevel") {
|
||||||
None => LevelFilter::Warn,
|
None => LevelFilter::Warn,
|
||||||
Some("error") => LevelFilter::Error,
|
Some("error") => LevelFilter::Error,
|
||||||
@ -125,28 +137,20 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
match matches.values_of("commands") {
|
match matches.values_of("commands") {
|
||||||
None => {}
|
None => {}
|
||||||
Some(values) => {
|
Some(values) => {
|
||||||
let script_text: String = values
|
options.scripts = vec![NuScript::code(values)?];
|
||||||
.map(|x| x.to_string())
|
|
||||||
.collect::<Vec<String>>()
|
futures::executor::block_on(nu_cli::run_script_file(options))?;
|
||||||
.join("\n");
|
|
||||||
futures::executor::block_on(nu_cli::run_script_file(
|
|
||||||
script_text,
|
|
||||||
matches.is_present("stdin"),
|
|
||||||
))?;
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match matches.value_of("script") {
|
match matches.value_of("script") {
|
||||||
Some(script) => {
|
Some(filepath) => {
|
||||||
let mut file = File::open(script)?;
|
let filepath = std::ffi::OsString::from(filepath);
|
||||||
let mut buffer = String::new();
|
|
||||||
file.read_to_string(&mut buffer)?;
|
|
||||||
|
|
||||||
futures::executor::block_on(nu_cli::run_script_file(
|
options.scripts = vec![NuScript::source_file(filepath.as_os_str())?];
|
||||||
buffer,
|
|
||||||
matches.is_present("stdin"),
|
futures::executor::block_on(nu_cli::run_script_file(options))?;
|
||||||
))?;
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,7 +163,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
|
|
||||||
#[cfg(feature = "rustyline-support")]
|
#[cfg(feature = "rustyline-support")]
|
||||||
{
|
{
|
||||||
futures::executor::block_on(nu_cli::cli(context))?;
|
futures::executor::block_on(nu_cli::cli(context, options))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "rustyline-support"))]
|
#[cfg(not(feature = "rustyline-support"))]
|
||||||
|
2
tests/fixtures/nuplayground/.gitignore
vendored
2
tests/fixtures/nuplayground/.gitignore
vendored
@ -1,2 +0,0 @@
|
|||||||
*_test*
|
|
||||||
*.txt
|
|
1
tests/fixtures/playground/config/default.toml
vendored
Normal file
1
tests/fixtures/playground/config/default.toml
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
skip_welcome_message = true
|
142
tests/shell/environment/configuration.rs
Normal file
142
tests/shell/environment/configuration.rs
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
use nu_test_support::fs::{file_contents, Stub::FileWithContent};
|
||||||
|
use nu_test_support::fs::{AbsolutePath, DisplayPath};
|
||||||
|
use nu_test_support::pipeline as input;
|
||||||
|
use nu_test_support::playground::{says, Executable, Playground};
|
||||||
|
|
||||||
|
use hamcrest2::assert_that;
|
||||||
|
use hamcrest2::prelude::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn clears_the_configuration() {
|
||||||
|
Playground::setup("config_clear_test", |dirs, nu| {
|
||||||
|
let file = AbsolutePath::new(dirs.test().join("config.toml"));
|
||||||
|
|
||||||
|
nu.with_config(&file);
|
||||||
|
nu.with_files(vec![FileWithContent(
|
||||||
|
"config.toml",
|
||||||
|
r#"
|
||||||
|
skip_welcome_message = true
|
||||||
|
pivot_mode = "arepas"
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
assert!(nu.pipeline("config clear").execute().is_ok());
|
||||||
|
assert!(file_contents(&file).is_empty());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn retrieves_config_values() {
|
||||||
|
Playground::setup("config_get_test", |dirs, nu| {
|
||||||
|
let file = AbsolutePath::new(dirs.test().join("config.toml"));
|
||||||
|
|
||||||
|
nu.with_config(&file);
|
||||||
|
nu.with_files(vec![FileWithContent(
|
||||||
|
"config.toml",
|
||||||
|
r#"
|
||||||
|
skip_welcome_message = true
|
||||||
|
|
||||||
|
[arepa]
|
||||||
|
colors = ["yellow", "white"]
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
assert_that!(
|
||||||
|
nu.pipeline("config get arepa.colors.0"),
|
||||||
|
says().to_stdout("yellow")
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sets_a_config_value() {
|
||||||
|
Playground::setup("config_set_test", |dirs, nu| {
|
||||||
|
let file = AbsolutePath::new(dirs.test().join("config.toml"));
|
||||||
|
|
||||||
|
nu.with_config(&file);
|
||||||
|
nu.with_files(vec![FileWithContent(
|
||||||
|
"config.toml",
|
||||||
|
r#"
|
||||||
|
skip_welcome_message = true
|
||||||
|
|
||||||
|
[nu]
|
||||||
|
meal = "taco"
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
assert!(nu.pipeline("config set nu.meal 'arepa'").execute().is_ok());
|
||||||
|
|
||||||
|
assert_that!(nu.pipeline("config get nu.meal"), says().to_stdout("arepa"));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sets_config_values_into_one_property() {
|
||||||
|
Playground::setup("config_set_into_test", |dirs, nu| {
|
||||||
|
let file = AbsolutePath::new(dirs.test().join("config.toml"));
|
||||||
|
|
||||||
|
nu.with_config(&file);
|
||||||
|
nu.with_files(vec![FileWithContent(
|
||||||
|
"config.toml",
|
||||||
|
r#"
|
||||||
|
skip_welcome_message = true
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
assert!(nu
|
||||||
|
.pipeline(&input(
|
||||||
|
r#"
|
||||||
|
echo ["amarillo", "blanco"]
|
||||||
|
| config set_into arepa_colors
|
||||||
|
"#,
|
||||||
|
))
|
||||||
|
.execute()
|
||||||
|
.is_ok());
|
||||||
|
|
||||||
|
assert_that!(
|
||||||
|
nu.pipeline("config get arepa_colors.1"),
|
||||||
|
says().to_stdout("blanco")
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn config_path() {
|
||||||
|
Playground::setup("config_path_test", |dirs, nu| {
|
||||||
|
let file = AbsolutePath::new(dirs.test().join("config.toml"));
|
||||||
|
|
||||||
|
nu.with_config(&file);
|
||||||
|
nu.with_files(vec![FileWithContent(
|
||||||
|
"config.toml",
|
||||||
|
r#"
|
||||||
|
skip_welcome_message = true
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
assert_that!(
|
||||||
|
nu.pipeline("config path"),
|
||||||
|
says().to_stdout(&file.display_path())
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn removes_config_values() {
|
||||||
|
Playground::setup("config_remove_test", |dirs, nu| {
|
||||||
|
let file = AbsolutePath::new(dirs.test().join("config.toml"));
|
||||||
|
|
||||||
|
nu.with_config(&file);
|
||||||
|
nu.with_files(vec![FileWithContent(
|
||||||
|
"config.toml",
|
||||||
|
r#"
|
||||||
|
skip_welcome_message = true
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
assert!(nu
|
||||||
|
.pipeline("config remove skip_welcome_message")
|
||||||
|
.execute()
|
||||||
|
.is_ok());
|
||||||
|
assert!(file_contents(&file).is_empty());
|
||||||
|
})
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
mod configuration;
|
||||||
mod nu_env;
|
mod nu_env;
|
||||||
|
|
||||||
pub mod support {
|
pub mod support {
|
||||||
|
Loading…
Reference in New Issue
Block a user