use clap::Parser;
use std::path::{Path, PathBuf};
use tectonic_bridge_core::{SecuritySettings, SecurityStance};
use tectonic::{
config::{maybe_return_test_bundle, PersistentConfig},
driver::{OutputFormat, PassSetting, ProcessingSession, ProcessingSessionBuilder},
errmsg,
errors::{ErrorKind, Result},
status::StatusBackend,
tt_error, tt_note,
unstable_opts::{UnstableArg, UnstableOptions},
};
use tectonic_bundles::detect_bundle;
#[derive(Debug, Parser)]
pub struct CompileOptions {
#[arg(value_hint = clap::ValueHint::FilePath)]
input: String,
#[arg(long, short, name = "path", default_value = "latex")]
format: String,
#[arg(long, short)]
bundle: Option<String>,
#[arg(short = 'C', long)]
only_cached: bool,
#[arg(long, name = "format", default_value = "pdf")]
outfmt: OutputFormat,
#[arg(long, name = "dest_path")]
makefile_rules: Option<PathBuf>,
#[arg(long, default_value = "default")]
pass: PassSetting,
#[arg(name = "count", long = "reruns", short = 'r')]
reruns: Option<usize>,
#[arg(short, long)]
keep_intermediates: bool,
#[arg(long)]
keep_logs: bool,
#[arg(long)]
synctex: bool,
#[arg(long, name = "hide_path")]
hide: Option<Vec<PathBuf>>,
#[arg(long = "print", short)]
print_stdout: bool,
#[arg(name = "outdir", short, long)]
outdir: Option<PathBuf>,
#[arg(long)]
untrusted: bool,
#[arg(name = "option", short = 'Z')]
unstable: Vec<UnstableArg>,
}
impl CompileOptions {
pub fn execute(self, config: PersistentConfig, status: &mut dyn StatusBackend) -> Result<i32> {
let unstable = UnstableOptions::from_unstable_args(self.unstable.into_iter());
let stance = if self.untrusted {
SecurityStance::DisableInsecures
} else {
SecurityStance::MaybeAllowInsecures
};
let mut sess_builder =
ProcessingSessionBuilder::new_with_security(SecuritySettings::new(stance));
let format_path = self.format;
let deterministic_mode = unstable.deterministic_mode;
sess_builder
.unstables(unstable)
.format_name(&format_path)
.keep_logs(self.keep_logs)
.keep_intermediates(self.keep_intermediates)
.format_cache_path(config.format_cache_path()?)
.synctex(self.synctex)
.output_format(self.outfmt)
.pass(self.pass);
if let Some(s) = self.reruns {
sess_builder.reruns(s);
}
if let Some(p) = self.makefile_rules {
sess_builder.makefile_output_path(p);
}
let input_path = self.input;
if input_path == "-" {
sess_builder.tex_input_name("texput.tex");
sess_builder.output_dir(Path::new(""));
tt_note!(
status,
"reading from standard input; outputs will appear under the base name \"texput\""
);
} else {
let input_path = Path::new(&input_path);
sess_builder.primary_input_path(input_path);
if let Some(fname) = input_path.file_name() {
sess_builder.tex_input_name(&fname.to_string_lossy());
} else {
return Err(errmsg!(
"can't figure out a basename for input path \"{}\"",
input_path.to_string_lossy()
));
};
if let Some(par) = input_path.parent() {
sess_builder.output_dir(par);
} else {
return Err(errmsg!(
"can't figure out a parent directory for input path \"{}\"",
input_path.to_string_lossy()
));
}
}
if let Some(output_dir) = self.outdir {
if !output_dir.is_dir() {
return Err(errmsg!(
"output directory \"{}\" does not exist",
output_dir.display()
));
}
sess_builder.output_dir(output_dir);
}
sess_builder.print_stdout(self.print_stdout);
if let Some(items) = self.hide {
for v in items {
sess_builder.hide(v);
}
}
if self.only_cached {
tt_note!(status, "using only cached resource files");
}
if let Some(bundle) = self.bundle {
if let Ok(bundle) = maybe_return_test_bundle(Some(bundle.clone())) {
sess_builder.bundle(bundle);
} else if let Some(bundle) = detect_bundle(bundle.clone(), self.only_cached, None)? {
sess_builder.bundle(bundle);
} else {
return Err(errmsg!("`{bundle}` doesn't specify a valid bundle."));
}
} else if let Ok(bundle) = maybe_return_test_bundle(None) {
sess_builder.bundle(bundle);
} else {
sess_builder.bundle(config.default_bundle(self.only_cached)?);
}
sess_builder.build_date_from_env(deterministic_mode);
run_and_report(sess_builder, status).map(|_| 0)
}
}
pub(crate) fn run_and_report(
sess_builder: ProcessingSessionBuilder,
status: &mut dyn StatusBackend,
) -> Result<ProcessingSession> {
let mut sess = sess_builder.create(status)?;
let result = sess.run(status);
if let Err(e) = &result {
if let ErrorKind::EngineError(engine) = e.kind() {
let output = sess.get_stdout_content();
if output.is_empty() {
tt_error!(
status,
"something bad happened inside {}, but no output was logged",
engine
);
} else {
tt_error!(
status,
"something bad happened inside {}; its output follows:\n",
engine
);
status.dump_error_logs(&output);
}
}
}
result.map(|_| sess)
}