use crate::commands::from_toml::convert_toml_value_to_nu_value; use crate::commands::to_toml::value_to_toml_value; use crate::data::{Dictionary, Value}; use crate::errors::ShellError; use crate::prelude::*; use app_dirs::*; use indexmap::IndexMap; use log::trace; use serde::{Deserialize, Serialize}; use std::fs::{self, OpenOptions}; use std::io; use std::path::{Path, PathBuf}; #[derive(Deserialize, Serialize)] struct Config { #[serde(flatten)] extra: IndexMap>, } pub const APP_INFO: AppInfo = AppInfo { name: "nu", author: "nu shell developers", }; pub fn config_path() -> Result { let path = app_root(AppDataType::UserConfig, &APP_INFO) .map_err(|err| ShellError::string(&format!("Couldn't open config path:\n{}", err)))?; Ok(path) } pub fn default_path() -> Result { default_path_for(&None) } pub fn default_path_for(file: &Option) -> Result { let filename = &mut config_path()?; let filename = match file { None => { filename.push("config.toml"); filename } Some(file) => { filename.push(file); filename } }; Ok(filename.clone()) } pub fn read( tag: impl Into, at: &Option, ) -> Result>, ShellError> { let filename = default_path()?; let filename = match at { None => filename, Some(ref file) => file.clone(), }; touch(&filename)?; trace!("config file = {}", filename.display()); let tag = tag.into(); let contents = fs::read_to_string(filename) .map(|v| v.tagged(tag)) .map_err(|err| ShellError::string(&format!("Couldn't read config file:\n{}", err)))?; let parsed: toml::Value = toml::from_str(&contents) .map_err(|err| ShellError::string(&format!("Couldn't parse config file:\n{}", err)))?; let value = convert_toml_value_to_nu_value(&parsed, tag); let tag = value.tag(); match value.item { Value::Row(Dictionary { entries }) => Ok(entries), other => Err(ShellError::type_error( "Dictionary", other.type_name().tagged(tag), )), } } pub(crate) fn config(tag: impl Into) -> Result>, ShellError> { read(tag, &None) } pub fn write( config: &IndexMap>, at: &Option, ) -> 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) fn touch(path: &Path) -> io::Result<()> { match OpenOptions::new().create(true).write(true).open(path) { Ok(_) => Ok(()), Err(e) => Err(e), } }