Skip to content

Commit 3127671

Browse files
committed
feat(templates)!: rework template system
Variable are now declared in a free form toml file then converted to json tera context. tera context contain some default value like the currently active profiles. More default context could be added in the future, like 'os' 'wm' ect.
1 parent 008fd3b commit 3127671

File tree

13 files changed

+570
-360
lines changed

13 files changed

+570
-360
lines changed

Cargo.lock

Lines changed: 173 additions & 65 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ dirs = "5"
1717
toml = "0.7"
1818
serde = { version = "1", features = ["derive"] }
1919
serde_json = "1"
20+
serde_json_merge = "0.0.4"
21+
colored_json = "3.0.1"
2022
anyhow = "1"
2123
tera = "1"
2224
colored = "2.0.0"

src/bin/bombadil.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ enum Cli {
7777
value: String,
7878
#[clap(value_parser = profiles(), num_args(0..))]
7979
profiles: Vec<String>,
80+
#[clap(short, long, default_value = "true")]
81+
colored: bool,
8082
},
8183
/// Generate shell completions
8284
/// Generate shell completions
@@ -173,7 +175,11 @@ async fn main() -> Result<()> {
173175
.and_then(|bombadil| bombadil.add_secret(&key, &value, &var_file))
174176
.unwrap_or_else(|err| fatal!("{}", err));
175177
}
176-
Cli::Get { value, profiles } => {
178+
Cli::Get {
179+
value,
180+
profiles,
181+
colored,
182+
} => {
177183
let metadata_type = match value.as_str() {
178184
"dots" => MetadataType::Dots,
179185
"prehooks" => MetadataType::PreHooks,
@@ -196,7 +202,7 @@ async fn main() -> Result<()> {
196202
.unwrap_or_else(|err| fatal!("{}", err));
197203

198204
bombadil
199-
.print_metadata(metadata_type, &mut io::stdout())
205+
.print_metadata(metadata_type, &mut io::stdout(), colored)
200206
.expect("Failed to write metadata to stdout");
201207
}
202208
Cli::GenerateCompletions { shell } => {

src/dots.rs

Lines changed: 83 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,31 @@ use crate::settings::dots::{Dot, DotOverride};
44
use crate::templating::Variables;
55
use anyhow::Result;
66
use colored::*;
7+
use std::error::Error;
78
use std::fs;
89
use std::fs::{File, OpenOptions};
910
use std::io::Write;
1011
use std::path::{Path, PathBuf};
12+
use tera::ErrorKind;
1113

1214
#[derive(PartialEq, Eq, Debug)]
1315
pub enum LinkResult {
14-
Updated,
15-
Created,
16-
Ignored,
17-
Unchanged,
16+
Updated {
17+
source: PathBuf,
18+
copy: PathBuf,
19+
target: PathBuf,
20+
},
21+
Created {
22+
source: PathBuf,
23+
copy: PathBuf,
24+
target: PathBuf,
25+
},
26+
Ignored {
27+
source: PathBuf,
28+
},
29+
Unchanged {
30+
target: PathBuf,
31+
},
1832
}
1933

2034
impl Dot {
@@ -44,15 +58,12 @@ impl Dot {
4458
vars.extend(local_vars);
4559
}
4660

47-
// Resolve % reference
48-
vars.resolve_ref();
49-
5061
// Recursively copy dotfile to .dots directory
5162
self.traverse_and_copy(source, target, ignored_paths.as_slice(), &vars, profiles)
5263
}
5364

5465
fn load_local_vars(source: &Path) -> Variables {
55-
Variables::from_toml(source).unwrap_or_else(|err| {
66+
Variables::from_path(source).unwrap_or_else(|err| {
5667
eprintln!("{}", err.to_string().yellow());
5768
Variables::default()
5869
})
@@ -77,23 +88,38 @@ impl Dot {
7788
profiles: &[String],
7889
) -> Result<LinkResult> {
7990
if ignored.contains(source) {
80-
return Ok(LinkResult::Ignored);
91+
return Ok(LinkResult::Ignored {
92+
source: source.clone(),
93+
});
8194
}
8295

8396
// Single file : inject vars and write to .dots/
8497
if source.is_file() {
8598
fs::create_dir_all(target.parent().unwrap())?;
99+
86100
match vars.to_dot(source, profiles) {
87101
Ok(content) if target.exists() => self.update(source, target, content),
88102
Ok(content) => self.create(source, target, content),
89-
Err(_) if target.exists() => {
90-
// Fixme: we probabbly want to remove anyhow here
91-
// And handle specific error case (i.e: ignore utf8 error)
103+
Err(e) if target.exists() => {
104+
match e.kind {
105+
ErrorKind::Utf8Conversion { .. } => {}
106+
ErrorKind::Io(_) => {}
107+
ErrorKind::Msg(message) => println!("\t{}", message.to_string().red()),
108+
_ => {
109+
if let Some(source) = e.source() {
110+
println!("\t{}", source);
111+
}
112+
}
113+
}
92114
self.update_raw(source, target)
93115
}
94116
Err(_) => {
95-
fs::copy(source, target)?;
96-
Ok(LinkResult::Created)
117+
fs::copy(&source, &target)?;
118+
Ok(LinkResult::Created {
119+
source: source.clone(),
120+
target: target.clone(),
121+
copy: self.copy_path()?,
122+
})
97123
}
98124
}
99125
} else {
@@ -117,12 +143,28 @@ impl Dot {
117143
}
118144
}
119145

120-
if link_results.contains(&LinkResult::Updated) {
121-
Ok(LinkResult::Updated)
122-
} else if link_results.contains(&LinkResult::Created) {
123-
Ok(LinkResult::Created)
146+
if link_results
147+
.iter()
148+
.any(|res| matches!(res, LinkResult::Updated { .. }))
149+
{
150+
Ok(LinkResult::Updated {
151+
copy: self.copy_path()?,
152+
source: self.source()?,
153+
target: self.target()?,
154+
})
155+
} else if link_results
156+
.iter()
157+
.any(|res| matches!(res, LinkResult::Created { .. }))
158+
{
159+
Ok(LinkResult::Created {
160+
copy: self.copy_path()?,
161+
source: self.source()?,
162+
target: self.target()?,
163+
})
124164
} else {
125-
Ok(LinkResult::Unchanged)
165+
Ok(LinkResult::Unchanged {
166+
target: self.target()?,
167+
})
126168
}
127169
}
128170
}
@@ -132,20 +174,30 @@ impl Dot {
132174
let mut dot_copy = File::create(target)?;
133175
dot_copy.write_all(content.as_bytes())?;
134176
dot_copy.set_permissions(permissions)?;
135-
Ok(LinkResult::Created)
177+
Ok(LinkResult::Created {
178+
target: self.target()?,
179+
source: self.source()?,
180+
copy: self.copy_path()?,
181+
})
136182
}
137183

138184
fn update(&self, source: &PathBuf, target: &PathBuf, content: String) -> Result<LinkResult> {
139185
let target_content = fs::read_to_string(target)?;
140186
if target_content == content {
141-
Ok(LinkResult::Unchanged)
187+
Ok(LinkResult::Unchanged {
188+
target: self.target()?,
189+
})
142190
} else {
143191
let permissions = fs::metadata(source)?.permissions();
144192
let mut dot_copy = OpenOptions::new().write(true).truncate(true).open(target)?;
145193
dot_copy.write_all(content.as_bytes())?;
146194
dot_copy.set_permissions(permissions)?;
147195
dot_copy.sync_data()?;
148-
Ok(LinkResult::Updated)
196+
Ok(LinkResult::Updated {
197+
target: self.target()?,
198+
source: self.source()?,
199+
copy: self.copy_path()?,
200+
})
149201
}
150202
}
151203

@@ -154,15 +206,21 @@ impl Dot {
154206
let content = fs::read(source)?;
155207

156208
if target_content == content {
157-
Ok(LinkResult::Unchanged)
209+
Ok(LinkResult::Unchanged {
210+
target: self.target()?,
211+
})
158212
} else {
159213
let permissions = fs::metadata(source)?.permissions();
160214
let mut dot_copy = OpenOptions::new().write(true).truncate(true).open(target)?;
161215

162216
dot_copy.write_all(&content)?;
163217
dot_copy.set_permissions(permissions)?;
164218
dot_copy.sync_data()?;
165-
Ok(LinkResult::Updated)
219+
Ok(LinkResult::Updated {
220+
target: self.target()?,
221+
source: self.source()?,
222+
copy: self.copy_path()?,
223+
})
166224
}
167225
}
168226
}
@@ -515,8 +573,7 @@ mod tests {
515573
vars: Dot::default_vars(),
516574
};
517575

518-
let mut vars = Variables::default();
519-
vars.insert("name", "Tom Bombadil");
576+
let vars: Variables = toml::from_str(r#"name = "Tom Bombadil""#)?;
520577

521578
// Act
522579
dot.install(&vars, vec![], &[])?;

src/gpg.rs

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use std::process::{Command, Stdio};
66

77
const PGP_HEADER: &str = "-----BEGIN PGP MESSAGE-----\n\n";
88
const PGP_FOOTER: &str = "\n-----END PGP MESSAGE-----";
9-
pub(crate) const GPG_PREFIX: &str = "gpg:";
109

1110
#[derive(Clone)]
1211
pub struct Gpg {
@@ -26,15 +25,12 @@ impl Gpg {
2625
value: &str,
2726
var_file: &S,
2827
) -> Result<()> {
29-
let mut vars = Variables::from_toml(var_file.as_ref())?;
28+
let mut vars = Variables::from_path(var_file.as_ref())?;
3029
let encrypted = self.encrypt(value)?;
3130
let encrypted = encrypted.replace(PGP_HEADER, "");
3231
let encrypted = encrypted.replace(PGP_FOOTER, "");
33-
34-
let encrypted = format!("gpg:{}", encrypted);
35-
vars.insert(key, &encrypted);
36-
37-
let toml = toml::to_string(&vars.variables)?;
32+
vars.push_secret(key, &encrypted);
33+
let toml = toml::to_string(&vars)?;
3834
std::fs::write(var_file, toml)?;
3935
println!("Added {} : {}", key, value);
4036

@@ -172,9 +168,9 @@ mod test {
172168

173169
let result = std::fs::read_to_string("vars.toml")?;
174170
let toml: Value = toml::from_str(&result)?;
175-
let value = toml.get("key");
171+
let value = toml.get("secrets").unwrap().get("key").unwrap().as_str();
172+
176173
assert_that!(value).is_some();
177-
assert_that!(value.unwrap().as_str().unwrap().starts_with("gpg:"));
178174
Ok(())
179175
}
180176

@@ -186,10 +182,13 @@ mod test {
186182

187183
let result = std::fs::read_to_string("vars.toml")?;
188184
let toml: Value = toml::from_str(&result)?;
189-
let value = toml.get("key");
190-
let value = value.unwrap().as_str().unwrap();
191-
let value = value.strip_prefix("gpg:").unwrap();
192-
185+
let value = toml
186+
.get("secrets")
187+
.unwrap()
188+
.get("key")
189+
.unwrap()
190+
.as_str()
191+
.unwrap();
193192
let decrypted = gpg.decrypt_secret(value)?;
194193

195194
assert_eq!(decrypted, "value");

0 commit comments

Comments
 (0)