Skip to content

Commit 47f69b0

Browse files
committed
feat(template): access profiles in tera context
1 parent 84025d8 commit 47f69b0

File tree

8 files changed

+84
-14
lines changed

8 files changed

+84
-14
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ config = "0.13.1"
1616
dirs = "^4"
1717
toml = "^0"
1818
serde = { version = "1.0.139", features = ["derive"] }
19+
serde_json = "1.0.89"
1920
anyhow = "1.0.58"
2021
tera = "1.16.0"
2122
colored = "2.0.0"

src/dots.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ impl Dot {
2222
&self,
2323
vars: &Variables,
2424
auto_ignored: Vec<PathBuf>,
25+
profiles: &[String],
2526
) -> Result<LinkResult> {
2627
let source = &self.source()?;
2728
let target = &self.build_copy_path();
@@ -47,7 +48,7 @@ impl Dot {
4748
vars.resolve_ref();
4849

4950
// Recursively copy dotfile to .dots directory
50-
self.traverse_and_copy(source, target, ignored_paths.as_slice(), &vars)
51+
self.traverse_and_copy(source, target, ignored_paths.as_slice(), &vars, profiles)
5152
}
5253

5354
fn load_local_vars(source: &Path) -> Variables {
@@ -74,6 +75,7 @@ impl Dot {
7475
target: &PathBuf,
7576
ignored: &[PathBuf],
7677
vars: &Variables,
78+
profiles: &[String],
7779
) -> Result<LinkResult> {
7880
if ignored.contains(source) {
7981
return Ok(LinkResult::Ignored);
@@ -82,7 +84,7 @@ impl Dot {
8284
// Single file : inject vars and write to .dots/
8385
if source.is_file() {
8486
fs::create_dir_all(target.parent().unwrap())?;
85-
match vars.to_dot(source) {
87+
match vars.to_dot(source, profiles) {
8688
Ok(content) if target.exists() => self.update(source, target, content),
8789
Ok(content) => self.create(source, target, content),
8890
Err(_) if target.exists() => {
@@ -107,6 +109,7 @@ impl Dot {
107109
&target.join(entry_name),
108110
ignored,
109111
vars,
112+
&[],
110113
);
111114

112115
match result {
@@ -356,6 +359,7 @@ mod tests {
356359
&PathBuf::from("dotfiles_with_multiple_nested_dir/.dots/dir"),
357360
&vec![],
358361
&Variables::default(),
362+
&[],
359363
)?;
360364

361365
run_cmd!(tree -a;)?;
@@ -389,6 +393,7 @@ mod tests {
389393
&PathBuf::from("dotfiles_non_utf8/.dots/ferris.png"),
390394
&vec![],
391395
&Variables::default(),
396+
&[],
392397
)?;
393398

394399
run_cmd!(tree -a;)?;
@@ -428,6 +433,7 @@ mod tests {
428433
PathBuf::from("source_dot/file.md"),
429434
],
430435
&Variables::default(),
436+
&[],
431437
)?;
432438

433439
// Assert
@@ -489,7 +495,7 @@ mod tests {
489495
vars: Dot::default_vars(),
490496
};
491497

492-
dot.install(&Variables::default(), vec![])?;
498+
dot.install(&Variables::default(), vec![], &[])?;
493499

494500
assert_that!(PathBuf::from(".dots")).exists();
495501
assert_that!(PathBuf::from(".dots/source_dot")).exists();
@@ -514,7 +520,7 @@ mod tests {
514520
vars.insert("name", "Tom Bombadil");
515521

516522
// Act
517-
dot.install(&vars, vec![])?;
523+
dot.install(&vars, vec![], &[])?;
518524
let dot = PathBuf::from(".dots/dotfiles/dot");
519525

520526
// Assert
@@ -551,7 +557,7 @@ mod tests {
551557
vars: PathBuf::from("my_vars.toml"),
552558
};
553559

554-
dot.install(&Variables::default(), vec![])?;
560+
dot.install(&Variables::default(), vec![], &[])?;
555561

556562
let content = fs::read_to_string(".dots/dir/template")?;
557563
assert_that!(content).is_equal_to(&"Hello Tom\n".to_string());
@@ -582,7 +588,7 @@ mod tests {
582588
};
583589

584590
// Arrange
585-
dot.install(&Variables::default(), vec![])?;
591+
dot.install(&Variables::default(), vec![], &[])?;
586592

587593
// Assert
588594
let content = fs::read_to_string(PathBuf::from(

src/lib.rs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,21 @@ pub(crate) const BOMBADIL_CONFIG: &str = "bombadil.toml";
4444
/// dotfile directory and how to install it.
4545
#[derive(Clone)]
4646
pub struct Bombadil {
47+
// path to self configuration, relative to $HOME
4748
path: PathBuf,
49+
// A list of dotfiles to link for this instance
4850
dots: HashMap<String, Dot>,
51+
// Variables for the tera template context
4952
vars: Variables,
53+
// Pre-hook commands, run before `bombadil-link`
5054
prehooks: Vec<Hook>,
55+
// Post-hook commands, run after `bombadil-link`
5156
posthooks: Vec<Hook>,
57+
// Available profiles
5258
profiles: HashMap<String, Profile>,
59+
// Profiles enabled for this isntance
60+
profile_enabled: Vec<String>,
61+
// A GPG user id, linking to user encryption/decryption key via gnupg
5362
gpg: Option<Gpg>,
5463
}
5564

@@ -148,7 +157,11 @@ impl Bombadil {
148157
// Render current settings and create symlinks
149158
fs::create_dir_all(dot_copy_dir)?;
150159
for (key, dot) in self.dots.iter() {
151-
match dot.install(&self.vars, self.get_auto_ignored_files(key)) {
160+
match dot.install(
161+
&self.vars,
162+
self.get_auto_ignored_files(key),
163+
self.profile_enabled.as_slice(),
164+
) {
152165
Err(err) => {
153166
eprintln!("{}", err);
154167
continue;
@@ -361,6 +374,8 @@ impl Bombadil {
361374
return Ok(());
362375
}
363376

377+
self.profile_enabled = profile_keys.iter().map(ToString::to_string).collect();
378+
364379
let mut profiles: Vec<Profile> = profile_keys
365380
.iter()
366381
// unwrap here is safe cause allowed profile keys are checked by clap
@@ -528,6 +543,7 @@ impl Bombadil {
528543
posthooks,
529544
profiles,
530545
gpg,
546+
profile_enabled: vec![],
531547
})
532548
}
533549

@@ -822,4 +838,21 @@ mod tests {
822838

823839
Ok(())
824840
}
841+
842+
#[sealed_test(files = ["tests/dotfiles_with_profile_context"], before = setup("dotfiles_with_profile_context"))]
843+
fn should_have_profile_context() -> Result<()> {
844+
// Arrange
845+
let mut bombadil = Bombadil::from_settings(NoGpg)?;
846+
bombadil.enable_profiles(vec!["fancy"])?;
847+
848+
// Act
849+
bombadil.install()?;
850+
let target = fs::read_link(".config/template.css")?;
851+
let content = fs::read_to_string(target)?;
852+
853+
// Assert
854+
assert_that!(content).is_equal_to(".class {color: #de1f1f}\n".to_string());
855+
856+
Ok(())
857+
}
825858
}

src/templating.rs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ impl Variables {
6363

6464
/// Read file in the given path and return its content
6565
/// with variable replaced by their values.
66-
pub(crate) fn to_dot(&self, path: &Path) -> Result<String> {
66+
pub(crate) fn to_dot(&self, path: &Path, profiles: &[String]) -> Result<String> {
6767
// Read file content
6868
let file = File::open(path)?;
6969
let mut buf_reader = BufReader::new(file);
@@ -76,10 +76,19 @@ impl Variables {
7676
context.insert(name, value);
7777
}
7878

79+
let profiles_context = serde_json::to_value(profiles)?;
80+
7981
self.secrets.iter().for_each(|(k, v)| {
80-
context.insert(k.to_owned(), v);
82+
if k == "profiles" {
83+
eprintln!("Cannot insert variable '{k}{v}'");
84+
eprintln!("'profiles' is a reserved variable name");
85+
} else {
86+
context.insert(k.to_owned(), v);
87+
}
8188
});
8289

90+
context.insert("profiles", &profiles_context);
91+
8392
let mut tera = Tera::default();
8493
let filename = path.as_os_str().to_str().expect("Non UTF8 filename");
8594

@@ -157,7 +166,7 @@ mod test {
157166
variables,
158167
secrets: Default::default(),
159168
}
160-
.to_dot(Path::new("tests/dotfiles_simple/template.css"))
169+
.to_dot(Path::new("tests/dotfiles_simple/template.css"), &[])
161170
.unwrap();
162171

163172
assert_eq!(
@@ -181,7 +190,7 @@ mod test {
181190
secrets.insert("pass".to_string(), "hunter2".to_string());
182191

183192
let dot_content = Variables { variables, secrets }
184-
.to_dot(Path::new("tests/dotfiles_with_secret/template"))
193+
.to_dot(Path::new("tests/dotfiles_with_secret/template"), &[])
185194
.unwrap();
186195

187196
assert_that!(dot_content).contains("color: red_value");
@@ -194,7 +203,7 @@ mod test {
194203
variables: HashMap::new(),
195204
secrets: Default::default(),
196205
}
197-
.to_dot(Path::new("tests/dotfiles_non_utf8/ferris.png"));
206+
.to_dot(Path::new("tests/dotfiles_non_utf8/ferris.png"), &[]);
198207

199208
assert_that!(content).is_err();
200209
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
dotfiles_dir = "dotfiles_with_profile_context"
2+
3+
[settings]
4+
vars = [ "vars.toml" ]
5+
6+
7+
8+
[settings.dots]
9+
css = { source = "template.css", target = ".config/template.css" }
10+
11+
[profiles.fancy]
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.class {
2+
{%- if profiles is containing("fancy") -%}
3+
color: {{red}}
4+
{%- else -%}
5+
color: {{black}}
6+
{%- endif -%}
7+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
red = "#de1f1f"
2+
black = "#ffffff"

0 commit comments

Comments
 (0)