#![cfg_attr(docsrs, feature(doc_cfg))]
#![warn(missing_docs)]
pub use carta_ast as ast;
pub use carta_ast::Document;
pub use carta_core::{
AnyReader, AnyWriter, BytesReader, BytesWriter, EpubOptions, Error, Extension, Extensions,
MathMethod, MediaBag, MediaItem, Output, Reader, ReaderOptions, Result, TocStyle, WrapMode,
Writer, WriterOptions, media, presets, walk,
};
use std::sync::Arc;
mod format_spec;
mod registry;
#[cfg(feature = "standalone")]
mod standalone;
pub use format_spec::parse_format_spec;
pub use registry::{
any_reader_for, any_writer_for, input_format_names, output_format_names, reader_for,
supported_input_formats, supported_output_formats, writer_for,
};
pub fn convert_text(
from: &str,
to: &str,
input: &str,
reader_options: &ReaderOptions,
writer_options: &WriterOptions,
) -> Result<String> {
match convert(from, to, input.as_bytes(), reader_options, writer_options)? {
Output::Text(text) => Ok(text),
Output::Bytes(_) => {
let (base, _) = parse_format_spec(to)?;
Err(Error::BinaryFormat(base))
}
}
}
pub fn convert(
from: &str,
to: &str,
input: &[u8],
reader_options: &ReaderOptions,
writer_options: &WriterOptions,
) -> Result<Output> {
let (document, media) = read_document(from, input, reader_options)?;
render_document(to, document, media, writer_options)
}
pub fn read_document(
from: &str,
input: &[u8],
reader_options: &ReaderOptions,
) -> Result<(Document, MediaBag)> {
let (from_base, from_ext) = format_spec::parse_reader_format_spec(from)?;
let reader = any_reader_for(&from_base)?;
let mut reader_options = reader_options.clone();
reader_options.extensions = from_ext.union(reader_options.extensions);
reader_options.greedy_paragraphs |= from_base.starts_with("markdown");
reader.read_media(input, &reader_options)
}
pub fn render_document(
to: &str,
document: Document,
media: MediaBag,
writer_options: &WriterOptions,
) -> Result<Output> {
let (to_base, to_ext) = parse_format_spec(to)?;
let writer = any_writer_for(&to_base)?;
let mut writer_options = writer_options.clone();
writer_options.extensions = to_ext.union(writer_options.extensions);
writer_options.media = Arc::new(media);
#[cfg(feature = "standalone")]
let document = {
let mut document = document;
standalone::merge_metadata(&mut document, &writer_options);
document
};
let writer = match writer {
AnyWriter::Text(writer) => writer,
AnyWriter::Bytes(writer) => {
return writer.write(&document, &writer_options).map(Output::Bytes);
}
};
#[cfg(feature = "standalone")]
let pristine_needed = writer_options.standalone || writer_options.template.is_some();
#[cfg(not(feature = "standalone"))]
let pristine_needed = false;
let mut document = document;
let body = if writer_options.number_sections && writer.numbers_sections_in_body() {
if pristine_needed {
let mut numbered = document.clone();
carta_core::sections::number_sections(&mut numbered.blocks);
writer.write(&numbered, &writer_options)?
} else {
carta_core::sections::number_sections(&mut document.blocks);
return writer.write(&document, &writer_options).map(Output::Text);
}
} else {
writer.write(&document, &writer_options)?
};
#[cfg(feature = "standalone")]
if (writer_options.standalone || writer_options.template.is_some())
&& let Some(wrapped) =
standalone::render(writer.as_ref(), &document, &body, &writer_options, &to_base)?
{
return Ok(Output::Text(wrapped));
}
Ok(Output::Text(body))
}
#[cfg(feature = "standalone")]
#[cfg_attr(docsrs, doc(cfg(feature = "standalone")))]
pub fn merge_metadata(document: &mut Document, writer_options: &WriterOptions) {
standalone::merge_metadata(document, writer_options);
}
#[cfg(feature = "metadata-file")]
#[cfg_attr(docsrs, doc(cfg(feature = "metadata-file")))]
pub fn parse_metadata_file(
content: &str,
json: bool,
) -> Result<std::collections::BTreeMap<String, ast::MetaValue>> {
if json {
carta_readers::metadata::parse_json(content)
} else {
carta_readers::metadata::parse_yaml(content)
}
}
pub fn format_extensions(format: Option<&str>) -> Result<Vec<(Extension, bool)>> {
let (base, extensions) = parse_format_spec(format.unwrap_or("markdown"))?;
if !registry::reader_recognizes(&base) && !registry::writer_recognizes(&base) {
return Err(Error::UnsupportedFormat(base));
}
let supported = format_spec::supported_extensions(&base);
let mut entries: Vec<(Extension, bool)> = Extension::ALL
.iter()
.filter(|&&extension| supported.is_none_or(|set| set.contains(extension)))
.map(|&extension| (extension, extensions.contains(extension)))
.collect();
entries.sort_by_key(|(extension, _)| extension.name());
Ok(entries)
}