Skip to content

Commit c2aad8c

Browse files
authored
Add new output file option and test like hell (#63)
1 parent bb3bcc1 commit c2aad8c

4 files changed

Lines changed: 1194 additions & 14 deletions

File tree

src/commands/transform.rs

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use crate::{
1818
};
1919
use anyhow::{Result, anyhow};
2020
use arrow::datatypes::SchemaRef;
21+
use camino::Utf8Path;
2122
use glob::glob;
2223
use owo_colors::OwoColorize;
2324
use tabled::{builder::Builder, settings::Style};
@@ -31,6 +32,7 @@ pub async fn run(args: TransformCommand) -> Result<()> {
3132
by,
3233
exclude_columns,
3334
list_outputs,
35+
list_outputs_file,
3436
create_dirs,
3537
overwrite,
3638
query,
@@ -249,15 +251,19 @@ pub async fn run(args: TransformCommand) -> Result<()> {
249251
let files = pipeline.execute().await?;
250252

251253
if let Some(format) = list_outputs_format {
252-
print_output_files(&files, format)?;
254+
print_output_files(&files, format, list_outputs_file.as_deref())?;
253255
}
254256

255257
Ok(())
256258
}
257259

258-
fn print_output_files(files: &[OutputFileInfo], format: ListOutputsFormat) -> Result<()> {
259-
match format {
260-
ListOutputsFormat::None => {}
260+
fn print_output_files(
261+
files: &[OutputFileInfo],
262+
format: ListOutputsFormat,
263+
output_path: Option<&Utf8Path>,
264+
) -> Result<()> {
265+
let output = match format {
266+
ListOutputsFormat::None => return Ok(()),
261267
ListOutputsFormat::Text => {
262268
if files.is_empty() {
263269
return Ok(());
@@ -277,8 +283,14 @@ fn print_output_files(files: &[OutputFileInfo], format: ListOutputsFormat) -> Re
277283
header.push("Path".to_string());
278284
header.push("Row Count".to_string());
279285

280-
let colored_header: Vec<String> = header.iter().map(|h| h.bold().to_string()).collect();
281-
builder.push_record(colored_header);
286+
if output_path.is_none() {
287+
// writing to stdout, so use colors
288+
let colored_header: Vec<String> =
289+
header.iter().map(|h| h.bold().to_string()).collect();
290+
builder.push_record(colored_header);
291+
} else {
292+
builder.push_record(header);
293+
}
282294

283295
// sort by path for consistent output
284296
let mut sorted_files = files.to_vec();
@@ -288,21 +300,38 @@ fn print_output_files(files: &[OutputFileInfo], format: ListOutputsFormat) -> Re
288300
let mut row: Vec<String> = file
289301
.partition_values
290302
.iter()
291-
.map(|pv| format_json_value(&pv.value).green().to_string())
303+
.map(|pv| {
304+
let val = format_json_value(&pv.value);
305+
if output_path.is_none() {
306+
// again, writing to stdout, so use colors
307+
val.green().to_string()
308+
} else {
309+
val
310+
}
311+
})
292312
.collect();
293313
row.push(file.path.clone());
294-
row.push(file.row_count.to_string().cyan().to_string());
314+
let row_count = file.row_count.to_string();
315+
if output_path.is_none() {
316+
// once again, writing to stdout, so use colors
317+
row.push(row_count.cyan().to_string());
318+
} else {
319+
row.push(row_count);
320+
}
295321
builder.push_record(row);
296322
}
297323

298-
let table = builder.build().with(Style::rounded()).to_string();
299-
println!("{}", table);
300-
}
301-
ListOutputsFormat::Json => {
302-
let json = serde_json::to_string_pretty(files)?;
303-
println!("{}", json);
324+
builder.build().with(Style::rounded()).to_string()
304325
}
326+
ListOutputsFormat::Json => serde_json::to_string_pretty(files)?,
327+
};
328+
329+
if let Some(path) = output_path {
330+
std::fs::write(path, output)?;
331+
} else {
332+
println!("{}", output);
305333
}
334+
306335
Ok(())
307336
}
308337

src/io_strategies/partitioner.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,69 @@ mod tests {
341341
assert_eq!(results[0].1.num_rows(), 2); // 2024-01
342342
assert_eq!(results[1].1.num_rows(), 1); // 2024-02
343343
assert_eq!(results[2].1.num_rows(), 2); // 2025-01
344+
345+
// verify BOTH partition columns are present and have correct values
346+
// partition 0: (2024, 1)
347+
assert!(
348+
results[0].0.contains_key("year"),
349+
"partition 0 missing 'year' key"
350+
);
351+
assert!(
352+
results[0].0.contains_key("month"),
353+
"partition 0 missing 'month' key"
354+
);
355+
let year0 = results[0]
356+
.0
357+
.get("year")
358+
.unwrap()
359+
.as_any()
360+
.downcast_ref::<Int32Array>()
361+
.unwrap();
362+
let month0 = results[0]
363+
.0
364+
.get("month")
365+
.unwrap()
366+
.as_any()
367+
.downcast_ref::<Int32Array>()
368+
.unwrap();
369+
assert_eq!(year0.value(0), 2024);
370+
assert_eq!(month0.value(0), 1);
371+
372+
// partition 1: (2024, 2)
373+
let year1 = results[1]
374+
.0
375+
.get("year")
376+
.unwrap()
377+
.as_any()
378+
.downcast_ref::<Int32Array>()
379+
.unwrap();
380+
let month1 = results[1]
381+
.0
382+
.get("month")
383+
.unwrap()
384+
.as_any()
385+
.downcast_ref::<Int32Array>()
386+
.unwrap();
387+
assert_eq!(year1.value(0), 2024);
388+
assert_eq!(month1.value(0), 2);
389+
390+
// partition 2: (2025, 1)
391+
let year2 = results[2]
392+
.0
393+
.get("year")
394+
.unwrap()
395+
.as_any()
396+
.downcast_ref::<Int32Array>()
397+
.unwrap();
398+
let month2 = results[2]
399+
.0
400+
.get("month")
401+
.unwrap()
402+
.as_any()
403+
.downcast_ref::<Int32Array>()
404+
.unwrap();
405+
assert_eq!(year2.value(0), 2025);
406+
assert_eq!(month2.value(0), 1);
344407
}
345408

346409
#[tokio::test]

src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -795,6 +795,10 @@ pub struct TransformCommand {
795795
#[arg(long, short = 'l', value_enum, requires = "to_many")]
796796
pub list_outputs: Option<ListOutputsFormat>,
797797

798+
/// Write output file listing to a file instead of stdout.
799+
#[arg(long, requires = "list_outputs")]
800+
pub list_outputs_file: Option<Utf8PathBuf>,
801+
798802
/// Create directories as needed.
799803
#[arg(long, default_value_t = true)]
800804
pub create_dirs: bool,

0 commit comments

Comments
 (0)