2019-08-02 21:15:07 +02:00
|
|
|
use crate::commands::from_toml::convert_toml_value_to_nu_value;
|
|
|
|
use crate::commands::to_toml::value_to_toml_value;
|
2019-09-05 18:23:42 +02:00
|
|
|
use crate::data::{Dictionary, Value};
|
2019-06-01 07:50:16 +02:00
|
|
|
use crate::errors::ShellError;
|
|
|
|
use crate::prelude::*;
|
|
|
|
use app_dirs::*;
|
|
|
|
use indexmap::IndexMap;
|
|
|
|
use log::trace;
|
2019-08-02 21:15:07 +02:00
|
|
|
use serde::{Deserialize, Serialize};
|
2019-06-01 07:50:16 +02:00
|
|
|
use std::fs::{self, OpenOptions};
|
|
|
|
use std::io;
|
2019-08-02 21:15:07 +02:00
|
|
|
use std::path::{Path, PathBuf};
|
2019-06-01 07:50:16 +02:00
|
|
|
|
|
|
|
#[derive(Deserialize, Serialize)]
|
|
|
|
struct Config {
|
|
|
|
#[serde(flatten)]
|
2019-08-01 03:58:42 +02:00
|
|
|
extra: IndexMap<String, Tagged<Value>>,
|
2019-08-02 21:15:07 +02:00
|
|
|
}
|
|
|
|
|
2019-09-10 12:08:01 +02:00
|
|
|
pub const APP_INFO: AppInfo = AppInfo {
|
|
|
|
name: "nu",
|
|
|
|
author: "nu shell developers",
|
|
|
|
};
|
2019-06-01 07:50:16 +02:00
|
|
|
|
2019-09-10 12:08:01 +02:00
|
|
|
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)))?;
|
2019-08-02 21:15:07 +02:00
|
|
|
|
2019-09-10 12:08:01 +02:00
|
|
|
Ok(path)
|
|
|
|
}
|
2019-06-01 07:50:16 +02:00
|
|
|
|
2019-09-10 12:08:01 +02:00
|
|
|
pub fn default_path() -> Result<PathBuf, ShellError> {
|
|
|
|
default_path_for(&None)
|
|
|
|
}
|
2019-06-01 07:50:16 +02:00
|
|
|
|
2019-09-10 12:08:01 +02:00
|
|
|
pub fn default_path_for(file: &Option<PathBuf>) -> Result<PathBuf, ShellError> {
|
|
|
|
let filename = &mut config_path()?;
|
|
|
|
let filename = match file {
|
|
|
|
None => {
|
|
|
|
filename.push("config.toml");
|
|
|
|
filename
|
|
|
|
}
|
|
|
|
Some(file) => {
|
|
|
|
filename.push(file);
|
|
|
|
filename
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(filename.clone())
|
2019-06-01 07:50:16 +02:00
|
|
|
}
|
|
|
|
|
2019-09-10 12:08:01 +02:00
|
|
|
pub fn read(
|
|
|
|
span: impl Into<Span>,
|
|
|
|
at: &Option<PathBuf>,
|
|
|
|
) -> Result<IndexMap<String, Tagged<Value>>, ShellError> {
|
|
|
|
let filename = default_path()?;
|
2019-07-08 18:44:53 +02:00
|
|
|
|
2019-09-10 12:08:01 +02:00
|
|
|
let filename = match at {
|
|
|
|
None => filename,
|
|
|
|
Some(ref file) => file.clone(),
|
|
|
|
};
|
2019-06-01 07:50:16 +02:00
|
|
|
|
|
|
|
touch(&filename)?;
|
|
|
|
|
|
|
|
trace!("config file = {}", filename.display());
|
|
|
|
|
2019-09-10 12:08:01 +02:00
|
|
|
let span = span.into();
|
2019-06-01 07:50:16 +02:00
|
|
|
let contents = fs::read_to_string(filename)
|
2019-08-05 10:54:29 +02:00
|
|
|
.map(|v| v.simple_spanned(span))
|
2019-06-01 07:50:16 +02:00
|
|
|
.map_err(|err| ShellError::string(&format!("Couldn't read config file:\n{}", err)))?;
|
|
|
|
|
2019-08-02 21:15:07 +02:00
|
|
|
let parsed: toml::Value = toml::from_str(&contents)
|
2019-06-01 07:50:16 +02:00
|
|
|
.map_err(|err| ShellError::string(&format!("Couldn't parse config file:\n{}", err)))?;
|
|
|
|
|
2019-08-09 06:51:21 +02:00
|
|
|
let value = convert_toml_value_to_nu_value(&parsed, Tag::unknown_origin(span));
|
|
|
|
let tag = value.tag();
|
2019-08-02 21:15:07 +02:00
|
|
|
match value.item {
|
2019-09-05 18:23:42 +02:00
|
|
|
Value::Row(Dictionary { entries }) => Ok(entries),
|
2019-08-02 21:15:07 +02:00
|
|
|
other => Err(ShellError::type_error(
|
|
|
|
"Dictionary",
|
2019-08-09 06:51:21 +02:00
|
|
|
other.type_name().tagged(tag),
|
2019-08-02 21:15:07 +02:00
|
|
|
)),
|
|
|
|
}
|
2019-06-01 07:50:16 +02:00
|
|
|
}
|
|
|
|
|
2019-09-10 12:08:01 +02:00
|
|
|
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(())
|
|
|
|
}
|
|
|
|
|
2019-06-01 07:50:16 +02:00
|
|
|
// A simple implementation of `% touch path` (ignores existing files)
|
|
|
|
fn touch(path: &Path) -> io::Result<()> {
|
|
|
|
match OpenOptions::new().create(true).write(true).open(path) {
|
|
|
|
Ok(_) => Ok(()),
|
|
|
|
Err(e) => Err(e),
|
|
|
|
}
|
|
|
|
}
|