use std::fs;
use std::path::Path;
use carta::{DocxOptions, EpubOptions, Result};
use super::Cli;
#[cfg(feature = "highlight")]
use carta::Error;
#[cfg(feature = "highlight")]
use std::io::{self, Write};
#[cfg(feature = "highlight")]
use std::path::PathBuf;
#[cfg(feature = "highlight")]
use std::sync::Arc;
pub(super) fn epub_options(cli: &Cli) -> Result<EpubOptions> {
let mut epub = EpubOptions::default();
for path in &cli.css {
epub.stylesheets.push(fs::read_to_string(path)?);
}
if let Some(path) = &cli.epub_cover_image {
epub.cover_image = Some((base_name(path), fs::read(path)?));
}
for path in &cli.epub_embed_font {
epub.fonts.push((base_name(path), fs::read(path)?));
}
if let Some(path) = &cli.epub_metadata {
epub.metadata_xml = Some(fs::read_to_string(path)?);
}
epub.subdirectory.clone_from(&cli.epub_subdirectory);
epub.split_level = cli.split_level;
epub.source_date_epoch = source_date_epoch();
epub.locale = std::env::var("LANG").ok();
Ok(epub)
}
pub(super) fn docx_options(cli: &Cli) -> Result<DocxOptions> {
let mut docx = DocxOptions::default();
if let Some(path) = &cli.reference_doc {
docx.reference_doc = Some(fs::read(path)?);
}
docx.source_date_epoch = source_date_epoch();
docx.locale = std::env::var("LANG").ok();
Ok(docx)
}
fn base_name(path: &Path) -> String {
path.file_name()
.and_then(|name| name.to_str())
.unwrap_or_default()
.to_owned()
}
fn source_date_epoch() -> Option<i64> {
std::env::var("SOURCE_DATE_EPOCH")
.ok()
.and_then(|value| value.trim().parse::<i64>().ok())
}
#[cfg(feature = "highlight")]
const DEFAULT_HIGHLIGHT_STYLE: &str = "pygments";
#[allow(clippy::arc_with_non_send_sync)]
#[cfg(feature = "highlight")]
pub(super) fn highlight_options(
cli: &Cli,
data_dir: Option<&Path>,
) -> Result<carta::HighlightOptions> {
let mode = cli.syntax_highlighting.as_deref();
if cli.no_highlight || mode == Some("none") {
return Ok(carta::HighlightOptions::default());
}
if mode == Some("idiomatic") {
return Ok(carta::HighlightOptions {
idiomatic: true,
..carta::HighlightOptions::default()
});
}
let style = cli.highlight_style.as_deref().or(match mode {
Some("default") | None => None,
named => named,
});
let theme = resolve_theme(style.unwrap_or(DEFAULT_HIGHLIGHT_STYLE))?;
let mut highlighter = carta::Highlighter::new();
for path in &cli.syntax_definition {
let xml = fs::read_to_string(path)?;
add_syntax_definition(&mut highlighter, path, &xml)
.map_err(|error| Error::Highlight(format!("{}: {error}", path.display())))?;
}
for directory in syntax_directories(data_dir) {
load_syntax_directory(&mut highlighter, &directory);
}
Ok(carta::HighlightOptions {
highlighter: Some(Arc::new(highlighter)),
theme: Some(theme),
idiomatic: false,
})
}
#[cfg(feature = "highlight")]
fn add_syntax_definition(
highlighter: &mut carta::Highlighter,
path: &Path,
xml: &str,
) -> std::result::Result<String, String> {
let registry = highlighter.registry_mut();
match path.file_stem().and_then(|stem| stem.to_str()) {
Some(stem) => registry.add_definition_with_stem(xml, stem),
None => registry.add_definition(xml),
}
.map_err(|error| error.to_string())
}
#[cfg(feature = "highlight")]
fn syntax_directories(data_dir: Option<&Path>) -> Vec<PathBuf> {
if let Some(configured) = std::env::var_os("CARTA_SYNTAX_DIR") {
if configured.is_empty() {
return Vec::new();
}
return vec![PathBuf::from(configured)];
}
let mut directories = Vec::new();
if let Some(in_data_dir) = data_dir.map(|dir| dir.join("syntax"))
&& in_data_dir.is_dir()
{
directories.push(in_data_dir);
}
if let Some(beside_executable) = std::env::current_exe()
.ok()
.and_then(|exe| Some(exe.parent()?.join("syntax")))
&& beside_executable.is_dir()
{
directories.push(beside_executable);
}
directories
}
#[cfg(feature = "highlight")]
fn load_syntax_directory(highlighter: &mut carta::Highlighter, directory: &Path) {
if let Err(error) = highlighter.registry_mut().add_directory(directory) {
eprintln!(
"carta: warning: cannot read syntax directory {}: {error}",
directory.display()
);
}
}
#[cfg(feature = "highlight")]
fn resolve_theme(spec: &str) -> Result<carta::Theme> {
if let Some(result) = carta::builtin_style(spec) {
return result.map_err(|error| Error::Highlight(format!("style '{spec}': {error}")));
}
let bytes = fs::read(spec)?;
carta::Theme::from_json(&bytes).map_err(|error| Error::Highlight(format!("{spec}: {error}")))
}
#[cfg(feature = "highlight")]
pub(super) fn print_highlight_style(spec: &str) -> Result<()> {
let json = resolve_theme(spec)?
.to_json()
.map_err(|error| Error::Highlight(error.to_string()))?;
let mut out = io::stdout().lock();
writeln!(out, "{json}")?;
Ok(())
}