univert 0.4.0

Universal file converter (library and CLI)
Documentation
use std::path::PathBuf;

use tempfile::Builder;

use crate::{Error, Job, Options, Result, conversion_map};

/// Runs either a direct converter or a chain of intermediate conversions.
pub fn run(
  from: &str,
  to: &str,
  entry: conversion_map::Entry,
  job: &Job,
) -> Result<()> {
  if let Some(func) = entry.direct {
    return func(job);
  }

  // Multi-step: input -> intermediate_1 -> ... -> intermediate_n -> output
  let mut current_input: PathBuf = job.input.to_path_buf();
  let mut current_from: String = from.to_string();

  // Track temp files so they live until the chain finishes.
  let mut temps: Vec<tempfile::NamedTempFile> = Vec::new();

  let hops: Vec<&str> = entry
    .intermediates
    .iter()
    .copied()
    .chain(std::iter::once(to))
    .collect();

  let steps = hops.len();

  // Options for hops: never re-split / never re-force on intermediates.
  let hop_opts = Options {
    force: true,
    split: false,
  };

  for (i, next_ext) in hops.iter().enumerate() {
    let is_last = i + 1 == steps;

    let hop_entry = conversion_map::lookup(&current_from, next_ext).ok_or(
      Error::Unsupported {
        from: current_from.clone(),
        to: (*next_ext).to_string(),
      },
    )?;

    let next_output: PathBuf = if is_last {
      job.output.to_path_buf()
    } else {
      let tmp = Builder::new()
        .prefix("univert-")
        .suffix(&format!(".{next_ext}"))
        .tempfile()?;
      let path = tmp.path().to_path_buf();
      temps.push(tmp);
      path
    };

    let hop_job = Job {
      input: &current_input,
      output: &next_output,
      options: if is_last { job.options } else { &hop_opts },
    };

    // Direct-only in hops for now; nested chains would require recursion.
    let func = hop_entry.direct.ok_or_else(|| Error::Unsupported {
      from: current_from.clone(),
      to: (*next_ext).to_string(),
    })?;

    func(&hop_job)?;

    current_input = next_output;
    current_from = (*next_ext).to_string();
  }

  Ok(())
}