From 9cd8325b67b7c5d2aafa28fc8d813aa10149ad57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fre=CC=81de=CC=81ric=20Bour?= Date: Sat, 30 Mar 2024 14:07:08 +0900 Subject: [PATCH 1/2] IoProvider: expose paths of cached files This is used to implement the "bundle serve" command that benefits from sharing path to cached files. --- crates/bundles/src/cache.rs | 4 ++++ crates/io_base/src/lib.rs | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/crates/bundles/src/cache.rs b/crates/bundles/src/cache.rs index 5d43813fb..9693d2090 100644 --- a/crates/bundles/src/cache.rs +++ b/crates/bundles/src/cache.rs @@ -658,6 +658,10 @@ impl IoProvider for CachingBundle { InputOrigin::Other, )) } + + fn input_path(&mut self, name: &str, status: &mut dyn StatusBackend) -> OpenResult { + self.ensure_file_availability(name, status) + } } impl Bundle for CachingBundle { diff --git a/crates/io_base/src/lib.rs b/crates/io_base/src/lib.rs index bc2cafa31..c1690f37c 100644 --- a/crates/io_base/src/lib.rs +++ b/crates/io_base/src/lib.rs @@ -485,6 +485,15 @@ pub trait IoProvider: AsIoProviderMut { } } + /// Return the path of an input file if it happens to be stored on the file system. + /// + /// A minimal implementation can always return [`OpenResult::NotAvailable`]. + /// If a file is on the file system, it is preferred by the "bundle serve" command to return a + /// local path rather than dumping the contents. + fn input_path(&mut self, _name: &str, _status: &mut dyn StatusBackend) -> OpenResult { + OpenResult::NotAvailable + } + /// Open the "primary" input file, which in the context of TeX is the main /// input that it's given. When the build is being done using the /// filesystem and the input is a file on the filesystem, this function From 000e2774aa613f0eb51d4b09e2d309b392ea7932 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fre=CC=81de=CC=81ric=20Bour?= Date: Fri, 24 May 2024 07:18:12 +0900 Subject: [PATCH 2/2] Add "bundle serve" command --- src/bin/tectonic/v2cli/commands/bundle.rs | 74 +++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/src/bin/tectonic/v2cli/commands/bundle.rs b/src/bin/tectonic/v2cli/commands/bundle.rs index f5b1900f8..bf1dce686 100644 --- a/src/bin/tectonic/v2cli/commands/bundle.rs +++ b/src/bin/tectonic/v2cli/commands/bundle.rs @@ -1,8 +1,10 @@ use clap::{Parser, Subcommand}; +use std::io::Write; use tectonic::{ config::PersistentConfig, docmodel::{DocumentExt, DocumentSetupOptions}, errors::Result, + io::{InputFeatures, OpenResult}, tt_note, }; use tectonic_bundles::Bundle; @@ -60,6 +62,10 @@ enum BundleCommands { #[command(name = "search")] /// Filter the list of filenames contained in the bundle Search(BundleSearchCommand), + + #[command(name = "serve")] + /// Dump the contents of files requested on standard input + Serve(BundleServeCommand), } impl TectonicCommand for BundleCommand { @@ -67,6 +73,7 @@ impl TectonicCommand for BundleCommand { match &self.command { BundleCommands::Cat(c) => c.customize(cc), BundleCommands::Search(c) => c.customize(cc), + BundleCommands::Serve(c) => c.customize(cc), } } @@ -74,6 +81,7 @@ impl TectonicCommand for BundleCommand { match self.command { BundleCommands::Cat(c) => c.execute(config, status), BundleCommands::Search(c) => c.execute(config, status), + BundleCommands::Serve(c) => c.execute(config, status), } } } @@ -138,3 +146,69 @@ impl BundleSearchCommand { Ok(0) } } + +#[derive(Debug, Eq, PartialEq, Parser)] +struct BundleServeCommand { + /// Use only resource files cached locally + #[arg(short = 'C', long)] + only_cached: bool, +} + +impl BundleServeCommand { + fn customize(&self, cc: &mut CommandCustomizations) { + cc.always_stderr = true; + } + + fn execute(self, config: PersistentConfig, status: &mut dyn StatusBackend) -> Result { + let mut bundle = get_a_bundle(config, self.only_cached, status)?; + let mut filename = String::new(); + let stdin = std::io::stdin(); + let mut stdout = std::io::stdout(); + loop { + stdout.flush()?; + filename.clear(); + match stdin.read_line(&mut filename) { + Err(error) => { + eprintln!("error: {error}"); + return Ok(1); + } + Ok(0) => return Ok(0), + Ok(_) => { + let name = filename.trim_end(); + let error = match bundle.input_path(name, status) { + OpenResult::Err(e) => e, + OpenResult::Ok(path) => { + let mut path = path.as_os_str().as_encoded_bytes(); + let size = path.len() as u64; + stdout.write_all(&[b'P'])?; + stdout.write_all(&size.to_le_bytes())?; + let copied = std::io::copy(&mut path, &mut stdout)?; + assert!(size == copied); + continue; + } + OpenResult::NotAvailable => { + match bundle.input_open_name(name, status).must_exist() { + Ok(mut t) => { + let size = t.get_size()? as u64; + stdout.write_all(&[b'C'])?; + stdout.write_all(&size.to_le_bytes())?; + let copied = std::io::copy(&mut t, &mut stdout)?; + assert!(size == copied); + continue; + } + Err(e) => e, + } + } + }; + let text = error.to_string(); + let bytes = text.as_bytes(); + let size = bytes.len() as u64; + stdout.write_all(&[b'E'])?; + stdout.write_all(&size.to_le_bytes())?; + stdout.write_all(&bytes)?; + } + }; + stdout.flush().unwrap(); + } + } +}