Skip to content

Commit

Permalink
Merge pull request #2356 from dathere/template-globals-json-option
Browse files Browse the repository at this point in the history
`template`: add `--globals-json` option
  • Loading branch information
jqnatividad authored Dec 16, 2024
2 parents 04cc119 + 895efee commit 119f414
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 3 deletions.
36 changes: 33 additions & 3 deletions src/cmd/template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ template arguments:
template options:
--template <str> MiniJinja template string to use (alternative to --template-file)
-t, --template-file <file> MiniJinja template file to use
-j, --globals-json <file> A JSON file containing global variables to make available in templates.
The JSON properties can be accessed in templates using the "globals"
namespace (e.g. {{globals.school_name}}, {{globals.year}}).
This allows sharing common values across all template renders.
--outfilename <str> MiniJinja template string to use to create the filename of the output
files to write to <outdir>. If set to just QSV_ROWNO, the filestem
is set to the current rowno of the record, padded with leading
Expand Down Expand Up @@ -116,6 +120,7 @@ Common options:
use std::{
fs,
io::{BufWriter, Write},
path::PathBuf,
sync::{
atomic::{AtomicBool, AtomicU16, Ordering},
OnceLock, RwLock,
Expand All @@ -131,7 +136,7 @@ use rayon::{
prelude::IntoParallelRefIterator,
};
use serde::Deserialize;
use simd_json::BorrowedValue;
use simd_json::{json, BorrowedValue};

use crate::{
config::{Config, Delimiter, DEFAULT_WTR_BUFFER_CAPACITY},
Expand All @@ -148,6 +153,7 @@ struct Args {
arg_outdir: Option<String>,
flag_template: Option<String>,
flag_template_file: Option<String>,
flag_globals_json: Option<PathBuf>,
flag_output: Option<String>,
flag_outfilename: String,
flag_outsubdir_size: u16,
Expand Down Expand Up @@ -218,6 +224,23 @@ pub fn run(argv: &[&str]) -> CliResult<()> {
Ordering::Relaxed,
);

// setup globals JSON context if specified
let mut globals_flag = false;
let globals_ctx = if let Some(globals_json) = args.flag_globals_json {
globals_flag = true;
match std::fs::read(globals_json) {
Ok(mut bytes) => match simd_json::from_slice(&mut bytes) {
Ok(json) => json,
Err(e) => return fail_clierror!("Failed to parse globals JSON file: {e}"),
},
Err(e) => return fail_clierror!("Failed to read globals JSON file: {e}"),
}
} else {
json!("")
};

let globals_ctx_borrowed: simd_json::BorrowedValue = globals_ctx.into();

// Set up minijinja environment
let mut env = Environment::new();

Expand Down Expand Up @@ -255,7 +278,7 @@ pub fn run(argv: &[&str]) -> CliResult<()> {
}
let width = rowcount.to_string().len();

// read headers
// read headers - the headers are used as MiniJinja variables in the template
let headers = if args.flag_no_headers {
csv::StringRecord::new()
} else {
Expand Down Expand Up @@ -427,6 +450,13 @@ pub fn run(argv: &[&str]) -> CliResult<()> {
let curr_record = record;

let mut context = simd_json::borrowed::Object::default();
// Add globals context to the record context
if globals_flag {
context.insert(
std::borrow::Cow::Borrowed("globals"),
globals_ctx_borrowed.clone(),
);
}
context.reserve(headers_len);
let mut row_number = 0_u64;

Expand All @@ -452,7 +482,7 @@ pub fn run(argv: &[&str]) -> CliResult<()> {
}
}
} else {
// Use header names
// Use header names as template variables
for (header, field) in headers.iter().zip(curr_record.iter()) {
context.insert(
std::borrow::Cow::Borrowed(header),
Expand Down
75 changes: 75 additions & 0 deletions tests/test_template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1113,3 +1113,78 @@ fn template_round_banker_filter() {
);
assert_eq!(got, expected);
}

#[test]
fn template_globals_json() {
let wrk = Workdir::new("template_globals");
wrk.create(
"data.csv",
vec![
svec!["name", "score"],
svec!["Alice", "85"],
svec!["Bob", "92"],
],
);

wrk.create_from_string(
"globals.json",
r#"{
"passing_score": 90,
"school_name": "Test Academy",
"year": 2023
}"#,
);

let mut cmd = wrk.command("template");
cmd.arg("--template")
.arg(concat!(
"School: {{globals.school_name}}\n",
"Year: {{globals.year}}\n",
"Student: {{name}}\n",
"Score: {{score}}\n",
"Status: {% if score|int >= globals.passing_score %}PASS{% else %}FAIL{% endif \
%}\n\n\n"
))
.arg("--globals-json")
.arg("globals.json")
.arg("data.csv");

let got: String = wrk.stdout(&mut cmd);
let expected = concat!(
"School: Test Academy\n",
"Year: 2023\n",
"Student: Alice\n",
"Score: 85\n",
"Status: FAIL\n\n",
"School: Test Academy\n",
"Year: 2023\n",
"Student: Bob\n",
"Score: 92\n",
"Status: PASS"
);
assert_eq!(got, expected);
}

#[test]
fn template_globals_json_invalid() {
let wrk = Workdir::new("template_globals_invalid");
wrk.create("data.csv", vec![svec!["name"], svec!["test"]]);

wrk.create_from_string(
"invalid.json",
r#"{
"bad_json": "missing_comma"
"another_field": true
}"#,
);

let mut cmd = wrk.command("template");
cmd.arg("--template")
.arg("{{name}}\n")
.arg("--globals-json")
.arg("invalid.json")
.arg("data.csv");

let got: String = wrk.output_stderr(&mut cmd);
assert!(got.contains("Failed to parse globals JSON file"));
}

0 comments on commit 119f414

Please sign in to comment.