Skip to content

Commit 1b0174d

Browse files
authored
Allow templating with stdin/stdout (#24)
1 parent 3b80486 commit 1b0174d

File tree

4 files changed

+57
-19
lines changed

4 files changed

+57
-19
lines changed

Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ test: $(EXECFILE)
2828
rm -rf $(TEST_DEST_DIR)
2929
mkdir $(TEST_DEST_DIR)
3030
./$(EXECFILE) $(TEST_RULES) $(TEST_DOTFILES) $(TEST_DEST_DIR) $(TEST_IGNORE_ARG); \
31+
cat $(TEST_DOTFILES)/template | ./$(EXECFILE) $(TEST_RULES) > $(TEST_DEST_DIR)/stdout; \
3132
diff $(DIFF_FLAGS) $(TEST_DEST_DIR) $(TEST_EXPECTED_DIR)
3233
if [ ! -x "$(TEST_DEST_DIR)/binary_file" ]; then \
3334
@echo "Expected binary_file to be executable."; \

README.md

+9-3
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
A small, portable Rust program intended for templating dotfiles across multiple systems.
33

44
## Purpose
5-
Storing dotfiles in git repositories allows them to be shared across multiple computers, but this becomes problematic once systems require slightly different configurations. Laptops require battery indicators and WiFi utilities, HiDPI displays use larger fonts... `dot-templater` intends to solve these problems by making it simple to change values or enable/disable chunks of configuration in any file.
5+
Storing dotfiles in git repositories allows them to be shared across multiple computers, but this becomes problematic once systems require slightly different configurations. Laptops require battery indicators and WiFi utilities, HiDPI displays use larger fonts... `dot-templater` intends to solve these problems by making it simple to change values or enable/disable chunks of configuration in any file, or content from stdin.
66

77
## Features
8-
* Make string substitutions in files according to configured key/value pairs.
8+
* Make string substitutions in files/content according to configured key/value pairs.
99
* Use output from arbitrary shell commands in templated dotfiles (e.g. for passwords with GNU Pass).
10-
* Toggle chunks of files per feature flags.
10+
* Toggle chunks of files/content per feature flags.
1111
* Copy binary files without templating.
1212
* Preserve file permissions.
1313
* Perform a dry-run to compare expected output against existing files.
@@ -30,6 +30,12 @@ dot-templater CONFIG SRC_DIR DEST_DIR
3030

3131
Copies files from `SRC_DIR` to `DEST_DIR` according to rules in `CONFIG`.
3232

33+
```
34+
dot-templater CONFIG
35+
```
36+
37+
Templates content from stdin to stdout according to rules in `CONFIG`.
38+
3339
```
3440
dot-templater --diff CONFIG SRC_DIR DEST_DIR
3541
```

src/lib.rs

+34-9
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,14 @@ impl Config {
8484
}
8585
}
8686

87-
fn template_file(&self, source: &Path) -> Result<String, Box<dyn Error>> {
88-
let source = BufReader::new(File::open(source)?);
87+
fn template_lines<T: BufRead>(
88+
&self,
89+
lines: std::io::Lines<T>,
90+
) -> Result<String, Box<dyn Error>> {
8991
let mut feature_stack = Vec::new();
9092
let mut templated_output = String::new();
9193

92-
for line in source.lines() {
94+
for line in lines {
9395
let mut line = line?;
9496
match Config::get_feature(&line) {
9597
Some(feature) => match feature_stack.last() {
@@ -115,6 +117,12 @@ impl Config {
115117
Ok(templated_output)
116118
}
117119

120+
fn template_file(&self, source: &Path) -> Result<String, Box<dyn Error>> {
121+
let source = BufReader::new(File::open(source)?);
122+
123+
Ok(self.template_lines(source.lines())?)
124+
}
125+
118126
fn get_feature(line: &str) -> Option<&str> {
119127
let re = Regex::new("^\\s*### .*$").expect("fixed regex always valid");
120128
if re.is_match(line) {
@@ -141,25 +149,33 @@ pub enum ConfigValue {
141149

142150
pub struct Arguments<'a> {
143151
pub rules: &'a str,
144-
pub source: &'a str,
145-
pub dest: &'a str,
146152
pub diff: Mode,
153+
pub source: Option<&'a str>,
154+
pub dest: Option<&'a str>,
147155
pub ignore: Vec<&'a str>,
148156
}
149157

150158
impl<'a> Arguments<'a> {
151159
pub fn new(args: &'a clap::ArgMatches) -> Self {
152160
let rules = args.value_of("CONFIG").expect("CONFIG is required");
153-
let mut source = args.value_of("SRC_DIR").expect("SRC_DIR is required");
154-
let mut dest = args.value_of("DEST_DIR").expect("DEST_DIR is required");
161+
let mut source = args.value_of("SRC_DIR");
162+
let mut dest = args.value_of("DEST_DIR");
155163
let diff = if args.is_present("diff") {
156164
Mode::Diff
157165
} else {
158166
Mode::Template
159167
};
160168

161-
source = Self::trim_trailing_slash(&source);
162-
dest = Self::trim_trailing_slash(&dest);
169+
source = match &source {
170+
None => None,
171+
Some(s) => Some(Self::trim_trailing_slash(&s)),
172+
};
173+
174+
dest = match &dest {
175+
None => None,
176+
Some(s) => Some(Self::trim_trailing_slash(&s)),
177+
};
178+
163179
let ignore = match args.values_of("ignore") {
164180
Some(value) => value.collect(),
165181
None => vec![],
@@ -237,6 +253,15 @@ fn diff_files(config: &Config, source: &Path, dest: &Path) -> Result<(), Box<dyn
237253
Ok(())
238254
}
239255

256+
pub fn template_lines(
257+
config: &Config,
258+
lines: std::io::Lines<std::io::StdinLock>,
259+
) -> Result<(), Box<dyn Error>> {
260+
print!("{}", config.template_lines(lines)?);
261+
262+
Ok(())
263+
}
264+
240265
pub fn template(
241266
config: &Config,
242267
source_dir: &str,

src/main.rs

+13-7
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use dot_templater::Config;
99
use std::fs::File;
1010
use std::io::BufRead;
1111
use std::io::BufReader;
12+
use std::io::{self};
1213
use std::process;
1314

1415
fn main() {
@@ -24,13 +25,11 @@ fn main() {
2425
.arg(
2526
Arg::with_name("SRC_DIR")
2627
.help("Path to directory containing template files")
27-
.required(true)
2828
.index(2),
2929
)
3030
.arg(
3131
Arg::with_name("DEST_DIR")
3232
.help("Path to generate output files in")
33-
.required(true)
3433
.index(3),
3534
)
3635
.arg(
@@ -74,9 +73,16 @@ fn main() {
7473
process::exit(1);
7574
});
7675

77-
dot_templater::template(&config, &args.source, &args.dest, args.diff, args.ignore)
78-
.unwrap_or_else(|err| {
79-
eprintln!("Error while performing templating: {}", err);
80-
process::exit(1);
81-
});
76+
if args.source.is_some() && args.dest.is_some() {
77+
let source = &args.source.unwrap();
78+
let dest = &args.dest.unwrap();
79+
80+
dot_templater::template(&config, &source, &dest, args.diff, args.ignore)
81+
} else {
82+
dot_templater::template_lines(&config, io::stdin().lock().lines())
83+
}
84+
.unwrap_or_else(|err| {
85+
eprintln!("Error while performing templating: {}", err);
86+
process::exit(1);
87+
});
8288
}

0 commit comments

Comments
 (0)