mirror of
https://github.com/nushell/nushell
synced 2024-12-27 05:23:11 +00:00
Merge branch 'master' into build-warnings
# Conflicts: # src/commands/config.rs
This commit is contained in:
commit
ae74ba5bb0
9 changed files with 305 additions and 66 deletions
|
@ -116,6 +116,13 @@ workflows:
|
||||||
docker push quay.io/nushell/nu:devel
|
docker push quay.io/nushell/nu:devel
|
||||||
|
|
||||||
nightly:
|
nightly:
|
||||||
|
triggers:
|
||||||
|
- schedule:
|
||||||
|
cron: "0 0 * * *"
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- master
|
||||||
jobs:
|
jobs:
|
||||||
- docker/publish:
|
- docker/publish:
|
||||||
image: nushell/nu-base
|
image: nushell/nu-base
|
||||||
|
@ -123,9 +130,6 @@ workflows:
|
||||||
tag: nightly
|
tag: nightly
|
||||||
dockerfile: docker/Dockerfile.nu-base
|
dockerfile: docker/Dockerfile.nu-base
|
||||||
extra_build_args: --cache-from=quay.io/nushell/nu-base:nightly --build-arg RELEASE=true
|
extra_build_args: --cache-from=quay.io/nushell/nu-base:nightly --build-arg RELEASE=true
|
||||||
filters:
|
|
||||||
branches:
|
|
||||||
only: master
|
|
||||||
before_build:
|
before_build:
|
||||||
- run: docker pull quay.io/nushell/nu:nightly
|
- run: docker pull quay.io/nushell/nu:nightly
|
||||||
- run: docker pull quay.io/nushell/nu-base:nightly
|
- run: docker pull quay.io/nushell/nu-base:nightly
|
||||||
|
@ -139,11 +143,3 @@ workflows:
|
||||||
command: |
|
command: |
|
||||||
docker push quay.io/nushell/nu-base:nightly
|
docker push quay.io/nushell/nu-base:nightly
|
||||||
docker push quay.io/nushell/nu:nightly
|
docker push quay.io/nushell/nu:nightly
|
||||||
|
|
||||||
triggers:
|
|
||||||
- schedule:
|
|
||||||
cron: "0 22 * * *"
|
|
||||||
filters:
|
|
||||||
branches:
|
|
||||||
only:
|
|
||||||
- master
|
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::errors::ShellError;
|
|
||||||
use crate::data::{config, Value};
|
use crate::data::{config, Value};
|
||||||
|
use crate::errors::ShellError;
|
||||||
use crate::parser::hir::SyntaxType;
|
use crate::parser::hir::SyntaxType;
|
||||||
use crate::parser::registry::{self};
|
use crate::parser::registry::{self};
|
||||||
|
use crate::prelude::*;
|
||||||
use std::iter::FromIterator;
|
use std::iter::FromIterator;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
pub struct Config;
|
pub struct Config;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct ConfigArgs {
|
pub struct ConfigArgs {
|
||||||
|
load: Option<Tagged<PathBuf>>,
|
||||||
set: Option<(Tagged<String>, Tagged<Value>)>,
|
set: Option<(Tagged<String>, Tagged<Value>)>,
|
||||||
get: Option<Tagged<String>>,
|
get: Option<Tagged<String>>,
|
||||||
clear: Tagged<bool>,
|
clear: Tagged<bool>,
|
||||||
|
@ -25,6 +26,7 @@ impl WholeStreamCommand for Config {
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("config")
|
Signature::build("config")
|
||||||
|
.named("load", SyntaxType::Path)
|
||||||
.named("set", SyntaxType::Any)
|
.named("set", SyntaxType::Any)
|
||||||
.named("get", SyntaxType::Any)
|
.named("get", SyntaxType::Any)
|
||||||
.named("remove", SyntaxType::Any)
|
.named("remove", SyntaxType::Any)
|
||||||
|
@ -47,6 +49,7 @@ impl WholeStreamCommand for Config {
|
||||||
|
|
||||||
pub fn config(
|
pub fn config(
|
||||||
ConfigArgs {
|
ConfigArgs {
|
||||||
|
load,
|
||||||
set,
|
set,
|
||||||
get,
|
get,
|
||||||
clear,
|
clear,
|
||||||
|
@ -55,7 +58,15 @@ pub fn config(
|
||||||
}: ConfigArgs,
|
}: ConfigArgs,
|
||||||
RunnableContext { name, .. }: RunnableContext,
|
RunnableContext { name, .. }: RunnableContext,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let mut result = crate::data::config::config(name)?;
|
let name_span = name;
|
||||||
|
|
||||||
|
let configuration = if let Some(supplied) = load {
|
||||||
|
Some(supplied.item().clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut result = crate::data::config::read(name_span, &configuration)?;
|
||||||
|
|
||||||
if let Some(v) = get {
|
if let Some(v) = get {
|
||||||
let key = v.to_string();
|
let key = v.to_string();
|
||||||
|
@ -63,15 +74,27 @@ pub fn config(
|
||||||
.get(&key)
|
.get(&key)
|
||||||
.ok_or_else(|| ShellError::string(&format!("Missing key {} in config", key)))?;
|
.ok_or_else(|| ShellError::string(&format!("Missing key {} in config", key)))?;
|
||||||
|
|
||||||
return Ok(
|
let mut results = VecDeque::new();
|
||||||
stream![value.clone()].into(), // futures::stream::once(futures::future::ready(ReturnSuccess::Value(value.clone()))).into(),
|
|
||||||
);
|
match value {
|
||||||
|
Tagged {
|
||||||
|
item: Value::Table(list),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
for l in list {
|
||||||
|
results.push_back(ReturnSuccess::value(l.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
x => results.push_back(ReturnSuccess::value(x.clone())),
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(results.to_output_stream());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((key, value)) = set {
|
if let Some((key, value)) = set {
|
||||||
result.insert(key.to_string(), value.clone());
|
result.insert(key.to_string(), value.clone());
|
||||||
|
|
||||||
config::write_config(&result)?;
|
config::write(&result, &configuration)?;
|
||||||
|
|
||||||
return Ok(stream![Tagged::from_simple_spanned_item(
|
return Ok(stream![Tagged::from_simple_spanned_item(
|
||||||
Value::Row(result.into()),
|
Value::Row(result.into()),
|
||||||
|
@ -87,7 +110,7 @@ pub fn config(
|
||||||
{
|
{
|
||||||
result.clear();
|
result.clear();
|
||||||
|
|
||||||
config::write_config(&result)?;
|
config::write(&result, &configuration)?;
|
||||||
|
|
||||||
return Ok(stream![Tagged::from_simple_spanned_item(
|
return Ok(stream![Tagged::from_simple_spanned_item(
|
||||||
Value::Row(result.into()),
|
Value::Row(result.into()),
|
||||||
|
@ -101,7 +124,7 @@ pub fn config(
|
||||||
tag: Tag { span, .. },
|
tag: Tag { span, .. },
|
||||||
} = path
|
} = path
|
||||||
{
|
{
|
||||||
let path = config::config_path()?;
|
let path = config::default_path_for(&configuration)?;
|
||||||
|
|
||||||
return Ok(stream![Tagged::from_simple_spanned_item(
|
return Ok(stream![Tagged::from_simple_spanned_item(
|
||||||
Value::Primitive(Primitive::Path(path)),
|
Value::Primitive(Primitive::Path(path)),
|
||||||
|
@ -115,7 +138,7 @@ pub fn config(
|
||||||
|
|
||||||
if result.contains_key(&key) {
|
if result.contains_key(&key) {
|
||||||
result.swap_remove(&key);
|
result.swap_remove(&key);
|
||||||
config::write_config(&result)?;
|
config::write(&result, &configuration)?;
|
||||||
} else {
|
} else {
|
||||||
return Err(ShellError::string(&format!(
|
return Err(ShellError::string(&format!(
|
||||||
"{} does not exist in config",
|
"{} does not exist in config",
|
||||||
|
|
|
@ -558,6 +558,7 @@ impl Value {
|
||||||
Value::Primitive(Primitive::Decimal(x)) => Ok(format!("{}", x)),
|
Value::Primitive(Primitive::Decimal(x)) => Ok(format!("{}", x)),
|
||||||
Value::Primitive(Primitive::Int(x)) => Ok(format!("{}", x)),
|
Value::Primitive(Primitive::Int(x)) => Ok(format!("{}", x)),
|
||||||
Value::Primitive(Primitive::Bytes(x)) => Ok(format!("{}", x)),
|
Value::Primitive(Primitive::Bytes(x)) => Ok(format!("{}", x)),
|
||||||
|
Value::Primitive(Primitive::Path(x)) => Ok(format!("{}", x.display())),
|
||||||
// TODO: this should definitely be more general with better errors
|
// TODO: this should definitely be more general with better errors
|
||||||
other => Err(ShellError::string(format!(
|
other => Err(ShellError::string(format!(
|
||||||
"Expected string, got {:?}",
|
"Expected string, got {:?}",
|
||||||
|
|
|
@ -11,52 +11,60 @@ use std::fs::{self, OpenOptions};
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
const APP_INFO: AppInfo = AppInfo {
|
|
||||||
name: "nu",
|
|
||||||
author: "nu shell developers",
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
struct Config {
|
struct Config {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
extra: IndexMap<String, Tagged<Value>>,
|
extra: IndexMap<String, Tagged<Value>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn config_path() -> Result<PathBuf, ShellError> {
|
pub const APP_INFO: AppInfo = AppInfo {
|
||||||
let location = app_root(AppDataType::UserConfig, &APP_INFO)
|
name: "nu",
|
||||||
.map_err(|err| ShellError::string(&format!("Couldn't open config file:\n{}", err)))?;
|
author: "nu shell developers",
|
||||||
|
};
|
||||||
|
|
||||||
Ok(location.join("config.toml"))
|
pub fn config_path() -> Result<PathBuf, ShellError> {
|
||||||
|
let path = app_root(AppDataType::UserConfig, &APP_INFO)
|
||||||
|
.map_err(|err| ShellError::string(&format!("Couldn't open config path:\n{}", err)))?;
|
||||||
|
|
||||||
|
Ok(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn write_config(config: &IndexMap<String, Tagged<Value>>) -> Result<(), ShellError> {
|
pub fn default_path() -> Result<PathBuf, ShellError> {
|
||||||
let location = app_root(AppDataType::UserConfig, &APP_INFO)
|
default_path_for(&None)
|
||||||
.map_err(|err| ShellError::string(&format!("Couldn't open config file:\n{}", err)))?;
|
|
||||||
|
|
||||||
let filename = location.join("config.toml");
|
|
||||||
touch(&filename)?;
|
|
||||||
|
|
||||||
let contents =
|
|
||||||
value_to_toml_value(&Value::Row(Dictionary::new(config.clone())).tagged_unknown())?;
|
|
||||||
|
|
||||||
let contents = toml::to_string(&contents)?;
|
|
||||||
|
|
||||||
fs::write(&filename, &contents)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn config(span: impl Into<Span>) -> Result<IndexMap<String, Tagged<Value>>, ShellError> {
|
pub fn default_path_for(file: &Option<PathBuf>) -> Result<PathBuf, ShellError> {
|
||||||
let span = span.into();
|
let filename = &mut config_path()?;
|
||||||
|
let filename = match file {
|
||||||
|
None => {
|
||||||
|
filename.push("config.toml");
|
||||||
|
filename
|
||||||
|
}
|
||||||
|
Some(file) => {
|
||||||
|
filename.push(file);
|
||||||
|
filename
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let location = app_root(AppDataType::UserConfig, &APP_INFO)
|
Ok(filename.clone())
|
||||||
.map_err(|err| ShellError::string(&format!("Couldn't open config file:\n{}", err)))?;
|
}
|
||||||
|
|
||||||
|
pub fn read(
|
||||||
|
span: impl Into<Span>,
|
||||||
|
at: &Option<PathBuf>,
|
||||||
|
) -> Result<IndexMap<String, Tagged<Value>>, ShellError> {
|
||||||
|
let filename = default_path()?;
|
||||||
|
|
||||||
|
let filename = match at {
|
||||||
|
None => filename,
|
||||||
|
Some(ref file) => file.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
let filename = location.join("config.toml");
|
|
||||||
touch(&filename)?;
|
touch(&filename)?;
|
||||||
|
|
||||||
trace!("config file = {}", filename.display());
|
trace!("config file = {}", filename.display());
|
||||||
|
|
||||||
|
let span = span.into();
|
||||||
let contents = fs::read_to_string(filename)
|
let contents = fs::read_to_string(filename)
|
||||||
.map(|v| v.simple_spanned(span))
|
.map(|v| v.simple_spanned(span))
|
||||||
.map_err(|err| ShellError::string(&format!("Couldn't read config file:\n{}", err)))?;
|
.map_err(|err| ShellError::string(&format!("Couldn't read config file:\n{}", err)))?;
|
||||||
|
@ -75,6 +83,34 @@ pub(crate) fn config(span: impl Into<Span>) -> Result<IndexMap<String, Tagged<Va
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn config(span: impl Into<Span>) -> Result<IndexMap<String, Tagged<Value>>, ShellError> {
|
||||||
|
read(span, &None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(
|
||||||
|
config: &IndexMap<String, Tagged<Value>>,
|
||||||
|
at: &Option<PathBuf>,
|
||||||
|
) -> Result<(), ShellError> {
|
||||||
|
let filename = &mut default_path()?;
|
||||||
|
let filename = match at {
|
||||||
|
None => filename,
|
||||||
|
Some(file) => {
|
||||||
|
filename.pop();
|
||||||
|
filename.push(file);
|
||||||
|
filename
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let contents =
|
||||||
|
value_to_toml_value(&Value::Row(Dictionary::new(config.clone())).tagged_unknown())?;
|
||||||
|
|
||||||
|
let contents = toml::to_string(&contents)?;
|
||||||
|
|
||||||
|
fs::write(&filename, &contents)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
// A simple implementation of `% touch path` (ignores existing files)
|
// A simple implementation of `% touch path` (ignores existing files)
|
||||||
fn touch(path: &Path) -> io::Result<()> {
|
fn touch(path: &Path) -> io::Result<()> {
|
||||||
match OpenOptions::new().create(true).write(true).open(path) {
|
match OpenOptions::new().create(true).write(true).open(path) {
|
||||||
|
|
|
@ -16,6 +16,11 @@ pub struct TableView {
|
||||||
entries: Vec<Vec<(String, &'static str)>>,
|
entries: Vec<Vec<(String, &'static str)>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum TableMode {
|
||||||
|
Light,
|
||||||
|
Normal,
|
||||||
|
}
|
||||||
|
|
||||||
impl TableView {
|
impl TableView {
|
||||||
fn merge_descriptors(values: &[Tagged<Value>]) -> Vec<String> {
|
fn merge_descriptors(values: &[Tagged<Value>]) -> Vec<String> {
|
||||||
let mut ret = vec![];
|
let mut ret = vec![];
|
||||||
|
@ -198,15 +203,36 @@ impl RenderView for TableView {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut table = Table::new();
|
let mut table = Table::new();
|
||||||
table.set_format(
|
|
||||||
FormatBuilder::new()
|
let table_mode = crate::data::config::config(Span::unknown())?
|
||||||
.column_separator('│')
|
.get("table_mode")
|
||||||
.separator(LinePosition::Top, LineSeparator::new('━', '┯', ' ', ' '))
|
.map(|s| match s.as_string().unwrap().as_ref() {
|
||||||
.separator(LinePosition::Title, LineSeparator::new('─', '┼', ' ', ' '))
|
"light" => TableMode::Light,
|
||||||
.separator(LinePosition::Bottom, LineSeparator::new('━', '┷', ' ', ' '))
|
_ => TableMode::Normal,
|
||||||
.padding(1, 1)
|
})
|
||||||
.build(),
|
.unwrap_or(TableMode::Normal);
|
||||||
);
|
|
||||||
|
match table_mode {
|
||||||
|
TableMode::Light => {
|
||||||
|
table.set_format(
|
||||||
|
FormatBuilder::new()
|
||||||
|
.separator(LinePosition::Title, LineSeparator::new('─', '─', ' ', ' '))
|
||||||
|
.padding(1, 1)
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
table.set_format(
|
||||||
|
FormatBuilder::new()
|
||||||
|
.column_separator('│')
|
||||||
|
.separator(LinePosition::Top, LineSeparator::new('━', '┯', ' ', ' '))
|
||||||
|
.separator(LinePosition::Title, LineSeparator::new('─', '┼', ' ', ' '))
|
||||||
|
.separator(LinePosition::Bottom, LineSeparator::new('━', '┷', ' ', ' '))
|
||||||
|
.padding(1, 1)
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let header: Vec<Cell> = self
|
let header: Vec<Cell> = self
|
||||||
.headers
|
.headers
|
||||||
|
|
|
@ -28,6 +28,7 @@ pub use crate::parser::parse::token_tree_builder::TokenTreeBuilder;
|
||||||
pub use crate::plugin::{serve_plugin, Plugin};
|
pub use crate::plugin::{serve_plugin, Plugin};
|
||||||
pub use crate::utils::{AbsoluteFile, AbsolutePath, RelativePath};
|
pub use crate::utils::{AbsoluteFile, AbsolutePath, RelativePath};
|
||||||
pub use cli::cli;
|
pub use cli::cli;
|
||||||
|
pub use data::config::{APP_INFO, config_path};
|
||||||
pub use data::base::{Primitive, Value};
|
pub use data::base::{Primitive, Value};
|
||||||
pub use data::dict::{Dictionary, TaggedDictBuilder};
|
pub use data::dict::{Dictionary, TaggedDictBuilder};
|
||||||
pub use data::meta::{Span, Tag, Tagged, TaggedItem};
|
pub use data::meta::{Span, Tag, Tagged, TaggedItem};
|
||||||
|
|
|
@ -328,7 +328,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut ConfigDeserializer<'de> {
|
||||||
let type_name = std::any::type_name::<V::Value>();
|
let type_name = std::any::type_name::<V::Value>();
|
||||||
let tagged_val_name = std::any::type_name::<Tagged<Value>>();
|
let tagged_val_name = std::any::type_name::<Tagged<Value>>();
|
||||||
|
|
||||||
if name == tagged_val_name {
|
if type_name == tagged_val_name {
|
||||||
return visit::<Tagged<Value>, _>(value.val, name, fields, visitor);
|
return visit::<Tagged<Value>, _>(value.val, name, fields, visitor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -467,3 +467,27 @@ impl<'a, 'de: 'a> de::SeqAccess<'de> for StructDeserializer<'a, 'de> {
|
||||||
return Some(self.fields.len());
|
return Some(self.fields.len());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::any::type_name;
|
||||||
|
#[test]
|
||||||
|
fn check_type_name_properties() {
|
||||||
|
// This ensures that certain properties for the
|
||||||
|
// std::any::type_name function hold, that
|
||||||
|
// this code relies on. The type_name docs explicitly
|
||||||
|
// mention that the actual format of the output
|
||||||
|
// is unspecified and change is likely.
|
||||||
|
// This test makes sure that such change is detected
|
||||||
|
// by this test failing, and not things silently breaking.
|
||||||
|
// Specifically, we rely on this behaviour further above
|
||||||
|
// in the file to special case Tagged<Value> parsing.
|
||||||
|
let tuple = type_name::<()>();
|
||||||
|
let tagged_tuple = type_name::<Tagged<()>>();
|
||||||
|
let tagged_value = type_name::<Tagged<Value>>();
|
||||||
|
assert!(tuple != tagged_tuple);
|
||||||
|
assert!(tuple != tagged_value);
|
||||||
|
assert!(tagged_tuple != tagged_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
120
tests/command_config_test.rs
Normal file
120
tests/command_config_test.rs
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
mod helpers;
|
||||||
|
|
||||||
|
use helpers as h;
|
||||||
|
use helpers::{Playground, Stub::*};
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn has_default_configuration_file() {
|
||||||
|
let expected = "config.toml";
|
||||||
|
|
||||||
|
Playground::setup("config_test_1", |dirs, _| {
|
||||||
|
|
||||||
|
nu!(cwd: dirs.root(), "config");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
dirs.config_path().join(expected),
|
||||||
|
nu::config_path().unwrap().join(expected)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn shows_path_of_configuration_file() {
|
||||||
|
let expected = "config.toml";
|
||||||
|
|
||||||
|
Playground::setup("config_test_2", |dirs, _| {
|
||||||
|
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: dirs.test(),
|
||||||
|
"config --path | echo $it"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(PathBuf::from(actual), dirs.config_path().join(expected));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn use_different_configuration() {
|
||||||
|
Playground::setup("config_test_3", |dirs, sandbox| {
|
||||||
|
sandbox
|
||||||
|
.with_files(vec![FileWithContent(
|
||||||
|
"test_3.toml",
|
||||||
|
r#"
|
||||||
|
caballero_1 = "Andrés N. Robalino"
|
||||||
|
caballero_2 = "Jonathan Turner"
|
||||||
|
caballero_3 = "Yehuda katz"
|
||||||
|
"#
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: dirs.root(),
|
||||||
|
"config --get caballero_1 --load {}/test_3.toml | echo $it",
|
||||||
|
dirs.test()
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(actual, "Andrés N. Robalino");
|
||||||
|
});
|
||||||
|
|
||||||
|
h::delete_file_at(nu::config_path().unwrap().join("test_3.toml"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sets_configuration_value() {
|
||||||
|
Playground::setup("config_test_4", |dirs, sandbox| {
|
||||||
|
sandbox
|
||||||
|
.with_files(vec![FileWithContent(
|
||||||
|
"test_4.toml",
|
||||||
|
r#"
|
||||||
|
caballero_1 = "Andrés N. Robalino"
|
||||||
|
caballero_2 = "Jonathan Turner"
|
||||||
|
caballero_3 = "Yehuda katz"
|
||||||
|
"#
|
||||||
|
)]);
|
||||||
|
|
||||||
|
nu!(
|
||||||
|
cwd: dirs.test(),
|
||||||
|
"config --load test_4.toml --set [caballero_4 jonas]"
|
||||||
|
);
|
||||||
|
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: dirs.root(),
|
||||||
|
r#"open "{}/test_4.toml" | get caballero_4 | echo $it"#,
|
||||||
|
dirs.config_path()
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(actual, "jonas");
|
||||||
|
});
|
||||||
|
|
||||||
|
h::delete_file_at(nu::config_path().unwrap().join("test_4.toml"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn removes_configuration_value() {
|
||||||
|
Playground::setup("config_test_5", |dirs, sandbox| {
|
||||||
|
sandbox
|
||||||
|
.with_files(vec![FileWithContent(
|
||||||
|
"test_5.toml",
|
||||||
|
r#"
|
||||||
|
caballeros = [1, 1, 1]
|
||||||
|
podershell = [1, 1, 1]
|
||||||
|
"#
|
||||||
|
)]);
|
||||||
|
|
||||||
|
nu!(
|
||||||
|
cwd: dirs.test(),
|
||||||
|
"config --load test_5.toml --remove podershell"
|
||||||
|
);
|
||||||
|
|
||||||
|
let actual = nu_error!(
|
||||||
|
cwd: dirs.root(),
|
||||||
|
r#"open "{}/test_5.toml" | get podershell | echo $it"#,
|
||||||
|
dirs.config_path()
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(actual.contains("table missing column"));
|
||||||
|
});
|
||||||
|
|
||||||
|
h::delete_file_at(nu::config_path().unwrap().join("test_5.toml"));
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ use glob::glob;
|
||||||
pub use std::path::Path;
|
pub use std::path::Path;
|
||||||
pub use std::path::PathBuf;
|
pub use std::path::PathBuf;
|
||||||
|
|
||||||
|
use app_dirs::{get_app_root, AppDataType};
|
||||||
use getset::Getters;
|
use getset::Getters;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use tempfile::{tempdir, TempDir};
|
use tempfile::{tempdir, TempDir};
|
||||||
|
@ -177,6 +178,10 @@ impl Dirs {
|
||||||
pub fn formats(&self) -> PathBuf {
|
pub fn formats(&self) -> PathBuf {
|
||||||
PathBuf::from(self.fixtures.join("formats"))
|
PathBuf::from(self.fixtures.join("formats"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn config_path(&self) -> PathBuf {
|
||||||
|
get_app_root(AppDataType::UserConfig, &nu::APP_INFO).unwrap()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Playground {
|
impl Playground {
|
||||||
|
@ -227,11 +232,10 @@ impl Playground {
|
||||||
playground_root.join(topic).display()
|
playground_root.join(topic).display()
|
||||||
));
|
));
|
||||||
|
|
||||||
let root =
|
let root = dunce::canonicalize(playground_root).expect(&format!(
|
||||||
dunce::canonicalize(playground_root).expect(&format!(
|
"Couldn't canonicalize tests root path {}",
|
||||||
"Couldn't canonicalize tests root path {}",
|
playground_root.display()
|
||||||
playground_root.display()
|
));
|
||||||
));
|
|
||||||
|
|
||||||
let dirs = Dirs {
|
let dirs = Dirs {
|
||||||
root,
|
root,
|
||||||
|
@ -332,6 +336,14 @@ pub fn line_ending() -> String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn delete_file_at(full_path: impl AsRef<Path>) {
|
||||||
|
let full_path = full_path.as_ref();
|
||||||
|
|
||||||
|
if full_path.exists() {
|
||||||
|
std::fs::remove_file(full_path).expect("can not delete file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn create_file_at(full_path: impl AsRef<Path>) -> Result<(), std::io::Error> {
|
pub fn create_file_at(full_path: impl AsRef<Path>) -> Result<(), std::io::Error> {
|
||||||
let full_path = full_path.as_ref();
|
let full_path = full_path.as_ref();
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue