1mod conf;
2mod config_trust;
3mod local_config;
4mod nuconfig;
5pub mod path;
6
7pub use conf::Conf;
8pub use config_trust::is_file_trusted;
9pub use config_trust::read_trusted;
10pub use config_trust::Trusted;
11pub use local_config::loadable_cfg_exists_in_dir;
12pub use local_config::LocalConfigDiff;
13pub use nuconfig::NuConfig;
14
15use indexmap::IndexMap;
16use log::trace;
17use nu_errors::{CoerceInto, ShellError};
18use nu_protocol::{
19 Dictionary, Primitive, ShellTypeName, TaggedDictBuilder, UnspannedPathMember, UntaggedValue,
20 Value,
21};
22use nu_source::{SpannedItem, Tag, TaggedItem};
23use std::env::var;
24use std::fs::{self, OpenOptions};
25use std::io;
26use std::path::{Path, PathBuf};
27
28pub fn convert_toml_value_to_nu_value(v: &toml::Value, tag: impl Into<Tag>) -> Value {
29 let tag = tag.into();
30
31 match v {
32 toml::Value::Boolean(b) => UntaggedValue::boolean(*b).into_value(tag),
33 toml::Value::Integer(n) => UntaggedValue::int(*n).into_value(tag),
34 toml::Value::Float(n) => UntaggedValue::decimal_from_float(*n, tag.span).into_value(tag),
35 toml::Value::String(s) => {
36 UntaggedValue::Primitive(Primitive::String(String::from(s))).into_value(tag)
37 }
38 toml::Value::Array(a) => UntaggedValue::Table(
39 a.iter()
40 .map(|x| convert_toml_value_to_nu_value(x, &tag))
41 .collect(),
42 )
43 .into_value(tag),
44 toml::Value::Datetime(dt) => {
45 UntaggedValue::Primitive(Primitive::String(dt.to_string())).into_value(tag)
46 }
47 toml::Value::Table(t) => {
48 let mut collected = TaggedDictBuilder::new(&tag);
49
50 for (k, v) in t {
51 collected.insert_value(k.clone(), convert_toml_value_to_nu_value(v, &tag));
52 }
53
54 collected.into_value()
55 }
56 }
57}
58
59fn collect_values(input: &[Value]) -> Result<Vec<toml::Value>, ShellError> {
60 let mut out = vec![];
61
62 for value in input {
63 out.push(helper(value)?);
64 }
65
66 Ok(out)
67}
68fn helper(v: &Value) -> Result<toml::Value, ShellError> {
71 use bigdecimal::ToPrimitive;
72
73 Ok(match &v.value {
74 UntaggedValue::Primitive(Primitive::Boolean(b)) => toml::Value::Boolean(*b),
75 UntaggedValue::Primitive(Primitive::Filesize(b)) => {
76 if let Some(value) = b.to_i64() {
77 toml::Value::Integer(value)
78 } else {
79 return Err(ShellError::labeled_error(
80 "Value too large to convert to toml value",
81 "value too large",
82 v.tag.span,
83 ));
84 }
85 }
86 UntaggedValue::Primitive(Primitive::Duration(i)) => toml::Value::String(i.to_string()),
87 UntaggedValue::Primitive(Primitive::Date(d)) => toml::Value::String(d.to_string()),
88 UntaggedValue::Primitive(Primitive::EndOfStream) => {
89 toml::Value::String("<End of Stream>".to_string())
90 }
91 UntaggedValue::Primitive(Primitive::BeginningOfStream) => {
92 toml::Value::String("<Beginning of Stream>".to_string())
93 }
94 UntaggedValue::Primitive(Primitive::Decimal(f)) => {
95 toml::Value::Float(f.tagged(&v.tag).coerce_into("converting to TOML float")?)
96 }
97 UntaggedValue::Primitive(Primitive::Int(i)) => toml::Value::Integer(*i),
98 UntaggedValue::Primitive(Primitive::BigInt(i)) => {
99 toml::Value::Integer(i.tagged(&v.tag).coerce_into("converting to TOML integer")?)
100 }
101 UntaggedValue::Primitive(Primitive::Nothing) => {
102 toml::Value::String("<Nothing>".to_string())
103 }
104 UntaggedValue::Primitive(Primitive::GlobPattern(s)) => toml::Value::String(s.clone()),
105 UntaggedValue::Primitive(Primitive::String(s)) => toml::Value::String(s.clone()),
106 UntaggedValue::Primitive(Primitive::FilePath(s)) => {
107 toml::Value::String(s.display().to_string())
108 }
109 UntaggedValue::Primitive(Primitive::ColumnPath(path)) => toml::Value::Array(
110 path.iter()
111 .map(|x| match &x.unspanned {
112 UnspannedPathMember::String(string) => Ok(toml::Value::String(string.clone())),
113 UnspannedPathMember::Int(int) => Ok(toml::Value::Integer(*int)),
114 })
115 .collect::<Result<Vec<toml::Value>, ShellError>>()?,
116 ),
117 UntaggedValue::Table(l) => toml::Value::Array(collect_values(l)?),
118 UntaggedValue::Error(e) => return Err(e.clone()),
119 UntaggedValue::Block(_) => toml::Value::String("<Block>".to_string()),
120 #[cfg(feature = "dataframe")]
121 UntaggedValue::DataFrame(_) | UntaggedValue::FrameStruct(_) => {
122 toml::Value::String("<DataFrame>".to_string())
123 }
124 UntaggedValue::Primitive(Primitive::Range(_)) => toml::Value::String("<Range>".to_string()),
125 UntaggedValue::Primitive(Primitive::Binary(b)) => {
126 toml::Value::Array(b.iter().map(|x| toml::Value::Integer(*x as i64)).collect())
127 }
128 UntaggedValue::Row(o) => {
129 let mut m = toml::map::Map::new();
130 for (k, v) in &o.entries {
131 m.insert(k.clone(), helper(v)?);
132 }
133 toml::Value::Table(m)
134 }
135 })
136}
137
138pub fn value_to_toml_value(v: &Value) -> Result<toml::Value, ShellError> {
141 match &v.value {
142 UntaggedValue::Row(o) => {
143 let mut m = toml::map::Map::new();
144 for (k, v) in &o.entries {
145 m.insert(k.clone(), helper(v)?);
146 }
147 Ok(toml::Value::Table(m))
148 }
149 UntaggedValue::Primitive(Primitive::String(s)) => {
150 toml::de::from_str(s).map_err(|_| {
152 ShellError::labeled_error(
153 format!("{:?} unable to de-serialize string to TOML", s),
154 "invalid TOML",
155 v.tag(),
156 )
157 })
158 }
159 _ => Err(ShellError::labeled_error(
160 format!("{:?} is not a valid top-level TOML", v.value),
161 "invalid TOML",
162 v.tag(),
163 )),
164 }
165}
166
167pub fn config_path() -> Result<PathBuf, ShellError> {
168 use directories_next::ProjectDirs;
169
170 let dir = ProjectDirs::from("org", "nushell", "nu")
171 .ok_or_else(|| ShellError::untagged_runtime_error("Couldn't find project directory"))?;
172 let path = var("NU_CONFIG_DIR").map_or(ProjectDirs::config_dir(&dir).to_owned(), |path| {
173 PathBuf::from(path)
174 });
175 std::fs::create_dir_all(&path).map_err(|err| {
176 ShellError::untagged_runtime_error(&format!("Couldn't create {} path:\n{}", "config", err))
177 })?;
178
179 Ok(path)
180}
181
182pub fn default_path() -> Result<PathBuf, ShellError> {
183 default_path_for(&None)
184}
185
186pub fn default_path_for(file: &Option<PathBuf>) -> Result<PathBuf, ShellError> {
187 let mut filename = config_path()?;
188 let file: &Path = file.as_deref().unwrap_or_else(|| "config.toml".as_ref());
189 filename.push(file);
190
191 Ok(filename)
192}
193
194pub fn user_data() -> Result<PathBuf, ShellError> {
195 use directories_next::ProjectDirs;
196
197 let dir = ProjectDirs::from("org", "nushell", "nu")
198 .ok_or_else(|| ShellError::untagged_runtime_error("Couldn't find project directory"))?;
199 let path = ProjectDirs::data_local_dir(&dir).to_owned();
200 std::fs::create_dir_all(&path).map_err(|err| {
201 ShellError::untagged_runtime_error(&format!(
202 "Couldn't create {} path:\n{}",
203 "user data", err
204 ))
205 })?;
206
207 Ok(path)
208}
209
210#[derive(Debug, Clone)]
211pub enum Status {
212 LastModified(std::time::SystemTime),
213 Unavailable,
214}
215
216impl Default for Status {
217 fn default() -> Self {
218 Status::Unavailable
219 }
220}
221
222pub fn last_modified(at: &Option<PathBuf>) -> Result<Status, Box<dyn std::error::Error>> {
223 let filename = default_path()?;
224
225 let filename = match at {
226 None => filename,
227 Some(ref file) => file.clone(),
228 };
229
230 if let Ok(time) = filename.metadata()?.modified() {
231 return Ok(Status::LastModified(time));
232 }
233
234 Ok(Status::Unavailable)
235}
236
237pub fn read(
238 tag: impl Into<Tag>,
239 at: &Option<PathBuf>,
240) -> Result<IndexMap<String, Value>, ShellError> {
241 let filename = default_path()?;
242
243 let filename = match at {
244 None => filename,
245 Some(ref file) => file.clone(),
246 };
247
248 if !filename.exists() && touch(&filename).is_err() {
249 return Ok(IndexMap::new());
252 }
253
254 trace!("config file = {}", filename.display());
255
256 let tag = tag.into();
257 let contents = fs::read_to_string(filename)
258 .map(|v| v.tagged(&tag))
259 .map_err(|err| {
260 ShellError::labeled_error(
261 &format!("Couldn't read config file:\n{}", err),
262 "file name",
263 &tag,
264 )
265 })?;
266
267 let parsed: toml::Value = toml::from_str(&contents).map_err(|err| {
268 ShellError::labeled_error(
269 &format!("Couldn't parse config file:\n{}", err),
270 "file name",
271 &tag,
272 )
273 })?;
274
275 let value = convert_toml_value_to_nu_value(&parsed, tag);
276 let tag = value.tag();
277 match value.value {
278 UntaggedValue::Row(Dictionary { entries }) => Ok(entries),
279 other => Err(ShellError::type_error(
280 "Dictionary",
281 other.type_name().spanned(tag.span),
282 )),
283 }
284}
285
286pub fn config(tag: impl Into<Tag>) -> Result<IndexMap<String, Value>, ShellError> {
287 read(tag, &None)
288}
289
290pub fn write(config: &IndexMap<String, Value>, at: &Option<PathBuf>) -> Result<(), ShellError> {
291 let filename = default_path()?;
292
293 let filename = match at {
294 None => filename,
295 Some(ref file) => file.clone(),
296 };
297
298 let contents = value_to_toml_value(
299 &UntaggedValue::Row(Dictionary::new(config.clone())).into_untagged_value(),
300 )?;
301
302 let contents = toml::to_string(&contents)?;
303
304 fs::write(&filename, &contents)?;
305
306 Ok(())
307}
308
309fn touch(path: &Path) -> io::Result<()> {
311 match OpenOptions::new().create(true).write(true).open(path) {
312 Ok(_) => Ok(()),
313 Err(e) => Err(e),
314 }
315}
316
317pub fn cfg_path_to_scope_tag(cfg_path: &Path) -> String {
318 cfg_path.to_string_lossy().to_string()
319}