Skip to content

Commit 07be056

Browse files
committed
enhancement(cli): Adds optional file output to generator
Signed-off-by: Ian Henry <[email protected]>
1 parent 32162bb commit 07be056

File tree

1 file changed

+70
-12
lines changed

1 file changed

+70
-12
lines changed

src/generate.rs

Lines changed: 70 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@ use crate::config::{
55
use colored::*;
66
use indexmap::IndexMap;
77
use serde::Serialize;
8-
use std::collections::BTreeMap;
8+
use std::{
9+
collections::BTreeMap,
10+
fs::{create_dir_all, File},
11+
io::Write,
12+
path::PathBuf,
13+
};
914
use structopt::StructOpt;
1015
use toml::Value;
1116

@@ -46,6 +51,10 @@ pub struct Opts {
4651
/// is then up to you to restructure the `inputs` of each component to build
4752
/// the topology you need.
4853
expression: String,
54+
55+
/// Generate config as a file
56+
#[structopt(long, parse(from_os_str))]
57+
file: Option<PathBuf>,
4958
}
5059

5160
#[derive(Serialize)]
@@ -71,7 +80,11 @@ pub struct Config {
7180
pub sinks: Option<IndexMap<String, SinkOuter>>,
7281
}
7382

74-
fn generate_example(include_globals: bool, expression: &str) -> Result<String, Vec<String>> {
83+
fn generate_example(
84+
include_globals: bool,
85+
expression: &str,
86+
file: &Option<PathBuf>,
87+
) -> Result<String, Vec<String>> {
7588
let components: Vec<Vec<_>> = expression
7689
.split(|c| c == '|' || c == '/')
7790
.map(|s| {
@@ -309,6 +322,16 @@ fn generate_example(include_globals: bool, expression: &str) -> Result<String, V
309322
}
310323
}
311324

325+
if file.is_some() {
326+
match write_config(file.as_ref().unwrap(), &builder) {
327+
Ok(_) => println!(
328+
"Config file written to {:?}",
329+
&file.as_ref().unwrap().join("\n")
330+
),
331+
Err(e) => errs.push(format!("failed to write to file: {}", e)),
332+
};
333+
};
334+
312335
if !errs.is_empty() {
313336
Err(errs)
314337
} else {
@@ -317,7 +340,7 @@ fn generate_example(include_globals: bool, expression: &str) -> Result<String, V
317340
}
318341

319342
pub fn cmd(opts: &Opts) -> exitcode::ExitCode {
320-
match generate_example(!opts.fragment, &opts.expression) {
343+
match generate_example(!opts.fragment, &opts.expression, &opts.file) {
321344
Ok(s) => {
322345
println!("{}", s);
323346
exitcode::OK
@@ -329,33 +352,51 @@ pub fn cmd(opts: &Opts) -> exitcode::ExitCode {
329352
}
330353
}
331354

355+
fn write_config(filepath: &PathBuf, body: &str) -> Result<usize, crate::Error> {
356+
if filepath.exists() {
357+
// If the file exists, we don't want to overwrite, that's just rude.
358+
Err(format!("{:?} already exists", &filepath).into())
359+
} else {
360+
if let Some(directory) = filepath.parent() {
361+
create_dir_all(directory)?;
362+
}
363+
File::create(filepath)
364+
.and_then(|mut file| file.write(body.as_bytes()))
365+
.map_err(Into::into)
366+
}
367+
}
368+
332369
#[cfg(test)]
333370
mod tests {
334371
use super::*;
372+
use std::fs;
373+
use std::path::PathBuf;
374+
375+
use tempfile::tempdir;
335376

336377
#[test]
337378
fn generate_all() {
338379
let mut errors = Vec::new();
339380

340381
for name in SourceDescription::types() {
341382
let param = format!("{}//", name);
342-
let cfg = generate_example(true, &param).unwrap();
383+
let cfg = generate_example(true, &param, &None).unwrap();
343384
if let Err(error) = toml::from_str::<crate::config::ConfigBuilder>(&cfg) {
344385
errors.push((param, error));
345386
}
346387
}
347388

348389
for name in TransformDescription::types() {
349390
let param = format!("/{}/", name);
350-
let cfg = generate_example(true, &param).unwrap();
391+
let cfg = generate_example(true, &param, &None).unwrap();
351392
if let Err(error) = toml::from_str::<crate::config::ConfigBuilder>(&cfg) {
352393
errors.push((param, error));
353394
}
354395
}
355396

356397
for name in SinkDescription::types() {
357398
let param = format!("//{}", name);
358-
let cfg = generate_example(true, &param).unwrap();
399+
let cfg = generate_example(true, &param, &None).unwrap();
359400
if let Err(error) = toml::from_str::<crate::config::ConfigBuilder>(&cfg) {
360401
errors.push((param, error));
361402
}
@@ -367,11 +408,28 @@ mod tests {
367408
assert!(errors.is_empty());
368409
}
369410

411+
#[test]
412+
fn generate_configfile() {
413+
let tempdir = tempdir().expect("Unable to create tempdir for config");
414+
let filepath = tempdir.path().join("./config.example.toml");
415+
let cfg = generate_example(true, "stdin/json_parser/console", &Some(filepath.clone()));
416+
let filecontents = fs::read_to_string(
417+
fs::canonicalize(&filepath).expect("Could not return canonicalized filepath"),
418+
)
419+
.expect("Could not read config file");
420+
cleanup_configfile(&filepath);
421+
assert_eq!(cfg.unwrap(), filecontents)
422+
}
423+
424+
fn cleanup_configfile(filepath: &PathBuf) {
425+
fs::remove_file(filepath).expect("Could not cleanup config file!");
426+
}
427+
370428
#[cfg(all(feature = "transforms-json_parser", feature = "sinks-console"))]
371429
#[test]
372430
fn generate_basic() {
373431
assert_eq!(
374-
generate_example(true, "stdin/json_parser/console"),
432+
generate_example(true, "stdin/json_parser/console", &None),
375433
Ok(r#"data_dir = "/var/lib/vector/"
376434
377435
[sources.source0]
@@ -402,7 +460,7 @@ when_full = "block"
402460
);
403461

404462
assert_eq!(
405-
generate_example(true, "stdin|json_parser|console"),
463+
generate_example(true, "stdin|json_parser|console", &None),
406464
Ok(r#"data_dir = "/var/lib/vector/"
407465
408466
[sources.source0]
@@ -433,7 +491,7 @@ when_full = "block"
433491
);
434492

435493
assert_eq!(
436-
generate_example(true, "stdin//console"),
494+
generate_example(true, "stdin//console", &None),
437495
Ok(r#"data_dir = "/var/lib/vector/"
438496
439497
[sources.source0]
@@ -458,7 +516,7 @@ when_full = "block"
458516
);
459517

460518
assert_eq!(
461-
generate_example(true, "//console"),
519+
generate_example(true, "//console", &None),
462520
Ok(r#"data_dir = "/var/lib/vector/"
463521
464522
[sinks.sink0]
@@ -479,7 +537,7 @@ when_full = "block"
479537
);
480538

481539
assert_eq!(
482-
generate_example(true, "/add_fields,json_parser,remove_fields"),
540+
generate_example(true, "/add_fields,json_parser,remove_fields", &None),
483541
Ok(r#"data_dir = "/var/lib/vector/"
484542
485543
[transforms.transform0]
@@ -504,7 +562,7 @@ type = "remove_fields"
504562
);
505563

506564
assert_eq!(
507-
generate_example(false, "/add_fields,json_parser,remove_fields"),
565+
generate_example(false, "/add_fields,json_parser,remove_fields", &None),
508566
Ok(r#"
509567
[transforms.transform0]
510568
inputs = []

0 commit comments

Comments
 (0)