use crate::ast::{Document, Span};
use crate::attributes::Attributes;
use crate::context::{OutputTarget, SafeMode};
use crate::diagnostic::{Diagnostic, DiagnosticCursor, DiagnosticLocation, Severity};
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BlockContext {
Paragraph,
Listing,
Literal,
Open,
Example,
Sidebar,
Quote,
Pass,
}
impl BlockContext {
pub fn as_str(self) -> &'static str {
match self {
BlockContext::Paragraph => "paragraph",
BlockContext::Listing => "listing",
BlockContext::Literal => "literal",
BlockContext::Open => "open",
BlockContext::Example => "example",
BlockContext::Sidebar => "sidebar",
BlockContext::Quote => "quote",
BlockContext::Pass => "pass",
}
}
pub(crate) fn of_tip(tip: &str) -> Option<Self> {
Some(match tip {
"----" => BlockContext::Listing,
"...." => BlockContext::Literal,
"--" => BlockContext::Open,
"====" => BlockContext::Example,
"****" => BlockContext::Sidebar,
"____" => BlockContext::Quote,
"++++" => BlockContext::Pass,
_ => return None,
})
}
pub(crate) fn builtin_style(self, style: &str) -> bool {
const ADMONITIONS: [&str; 5] = ["NOTE", "TIP", "IMPORTANT", "WARNING", "CAUTION"];
style == self.as_str()
|| match self {
BlockContext::Listing => matches!(style, "literal" | "source"),
BlockContext::Literal => matches!(style, "listing" | "source"),
BlockContext::Open => {
matches!(
style,
"comment"
| "example"
| "literal"
| "listing"
| "pass"
| "quote"
| "sidebar"
| "source"
| "verse"
| "admonition"
| "abstract"
| "partintro"
) || ADMONITIONS.contains(&style)
}
BlockContext::Example => style == "admonition" || ADMONITIONS.contains(&style),
BlockContext::Quote => style == "verse",
BlockContext::Pass => matches!(style, "stem" | "latexmath" | "asciimath"),
BlockContext::Sidebar | BlockContext::Paragraph => false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ContentModel {
#[default]
Simple,
Raw,
Verbatim,
Skip,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum MacroContent {
#[default]
Attributes,
Text,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AttrKey {
Positional(usize),
Named(String),
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ExtensionAttributes {
entries: Vec<(AttrKey, Option<String>)>,
}
impl ExtensionAttributes {
pub fn new() -> Self {
Self::default()
}
pub fn get(&self, name: &str) -> Option<&str> {
self.entries.iter().find_map(|(key, value)| match key {
AttrKey::Named(key) if key == name => value.as_deref(),
_ => None,
})
}
pub fn positional(&self, index: usize) -> Option<&str> {
self.entries.iter().find_map(|(key, value)| match key {
AttrKey::Positional(key) if *key == index => value.as_deref(),
_ => None,
})
}
pub fn contains(&self, name: &str) -> bool {
self.get(name).is_some()
}
pub fn set(&mut self, name: impl Into<String>, value: impl Into<String>) {
self.set_key(AttrKey::Named(name.into()), value.into());
}
pub fn set_key(&mut self, key: AttrKey, value: String) {
self.set_value(key, Some(value));
}
pub fn set_value(&mut self, key: AttrKey, value: Option<String>) {
match self.entries.iter_mut().find(|(k, _)| *k == key) {
Some((_, slot)) => *slot = value,
None => self.entries.push((key, value)),
}
}
pub fn remove(&mut self, name: &str) -> Option<String> {
let index = self
.entries
.iter()
.position(|(key, _)| matches!(key, AttrKey::Named(key) if key == name))?;
self.entries.remove(index).1
}
pub fn has_option(&self, name: &str) -> bool {
self.contains(&format!("{name}-option"))
}
pub fn iter(&self) -> impl Iterator<Item = (&AttrKey, Option<&str>)> {
self.entries
.iter()
.map(|(key, value)| (key, value.as_deref()))
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
#[derive(Debug, Clone)]
pub struct BlockRequest {
pub name: String,
pub context: BlockContext,
pub lines: Vec<String>,
pub attributes: ExtensionAttributes,
pub span: Span,
}
#[derive(Debug, Clone)]
pub struct BlockMacroRequest {
pub name: String,
pub target: String,
pub attributes: ExtensionAttributes,
pub span: Span,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum InlineMacroFormat {
#[default]
Long,
Short,
}
#[derive(Debug, Clone)]
pub struct InlineMacroRequest {
pub name: String,
pub target: String,
pub attributes: ExtensionAttributes,
pub span: Span,
}
pub struct ProcessContext<'a> {
attributes: &'a Attributes,
safe: SafeMode,
base_dir: Option<&'a str>,
output: Option<&'a OutputTarget>,
span: Span,
cursor: Option<DiagnosticCursor>,
diagnostics: std::cell::RefCell<Vec<Diagnostic>>,
markup: Option<std::sync::Arc<dyn Markup>>,
host: std::cell::RefCell<Option<&'a mut dyn SubstitutionHost>>,
}
pub trait Markup: Send + Sync + std::fmt::Debug {
fn markup(&self, parts: &[crate::AttributePart], attributes: &Attributes) -> String;
}
pub(crate) trait SubstitutionHost {
fn substitute(
&mut self,
text: &str,
stages: &[crate::pass_subs::Stage],
span: Span,
) -> Vec<crate::AttributePart>;
}
impl<'a> ProcessContext<'a> {
pub(crate) fn new(
attributes: &'a Attributes,
safe: SafeMode,
base_dir: Option<&'a str>,
output: Option<&'a OutputTarget>,
span: Span,
) -> Self {
Self {
attributes,
safe,
base_dir,
output,
span,
cursor: None,
diagnostics: Default::default(),
markup: None,
host: Default::default(),
}
}
pub(crate) fn with_markup(mut self, markup: Option<std::sync::Arc<dyn Markup>>) -> Self {
self.markup = markup;
self
}
pub(crate) fn with_host(self, host: &'a mut dyn SubstitutionHost) -> Self {
*self.host.borrow_mut() = Some(host);
self
}
pub fn apply_subs(&self, lines: &[String], subs: &str, subject: &str) -> Vec<String> {
let mut warnings = Vec::new();
let stages = crate::pass_subs::resolve_block(
subs,
crate::pass_subs::Defaults::None,
subject,
self.span,
&mut warnings,
);
self.diagnostics.borrow_mut().extend(warnings);
let text = lines.join("\n");
if stages.is_empty() || text.is_empty() {
return lines.to_vec();
}
let parts = match self.host.borrow_mut().as_mut() {
Some(host) => host.substitute(&text, &stages, self.span),
None => {
let mut diagnostics = Vec::new();
let parts =
crate::docinfo::substitute(&text, &stages, self.attributes, &mut diagnostics);
self.diagnostics.borrow_mut().extend(diagnostics);
parts
}
};
let text = match &self.markup {
Some(markup) => markup.markup(&parts, self.attributes),
None => crate::attributes::parts_text(&parts),
};
text.split('\n').map(str::to_owned).collect()
}
pub(crate) fn with_cursor(mut self, cursor: Option<DiagnosticCursor>) -> Self {
self.cursor = cursor;
self
}
pub fn attribute(&self, name: &str) -> Option<&str> {
self.attributes.get(name)
}
pub fn attributes(&self) -> &Attributes {
self.attributes
}
pub fn safe(&self) -> SafeMode {
self.safe
}
pub fn base_dir(&self) -> Option<&str> {
self.base_dir
}
pub fn output(&self) -> Option<&OutputTarget> {
self.output
}
pub fn span(&self) -> Span {
self.span
}
pub fn error(&mut self, message: impl Into<String>) {
self.report(Severity::Error, message.into());
}
pub fn warning(&mut self, message: impl Into<String>) {
self.report(Severity::Warning, message.into());
}
pub fn warning_unlocated(&mut self, message: impl Into<String>) {
self.report(Severity::Warning, message.into());
if let Some(last) = self.diagnostics.get_mut().last_mut() {
last.report.location = crate::diagnostic::DiagnosticLocation::Unlocated;
}
}
fn report(&mut self, severity: Severity, message: String) {
self.diagnostics.get_mut().push(Diagnostic {
omitted_span: None,
report: crate::DiagnosticReport {
location: self
.cursor
.map_or(DiagnosticLocation::Source, DiagnosticLocation::Cursor),
..Default::default()
},
span: self.span,
severity,
message,
});
}
pub(crate) fn into_diagnostics(self) -> Vec<Diagnostic> {
self.diagnostics.into_inner()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NewBlockKind {
Image { target: String },
Listing { source: String },
Literal { source: String },
Pass { source: String },
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NewBlock {
pub kind: NewBlockKind,
pub attributes: ExtensionAttributes,
pub discarded_captions: Vec<String>,
}
impl NewBlock {
pub fn image(target: impl Into<String>, attributes: ExtensionAttributes) -> Self {
Self {
kind: NewBlockKind::Image {
target: target.into(),
},
attributes,
discarded_captions: Vec::new(),
}
}
pub fn listing(source: impl Into<String>, attributes: ExtensionAttributes) -> Self {
Self {
kind: NewBlockKind::Listing {
source: source.into(),
},
attributes,
discarded_captions: Vec::new(),
}
}
pub fn literal(source: impl Into<String>, attributes: ExtensionAttributes) -> Self {
Self {
kind: NewBlockKind::Literal {
source: source.into(),
},
attributes,
discarded_captions: Vec::new(),
}
}
pub fn pass(source: impl Into<String>, attributes: ExtensionAttributes) -> Self {
Self {
kind: NewBlockKind::Pass {
source: source.into(),
},
attributes,
discarded_captions: Vec::new(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NewInlineKind {
Image { target: String },
Quoted {
format: crate::AttributeFormat,
text: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NewInline {
pub kind: NewInlineKind,
pub attributes: ExtensionAttributes,
}
impl NewInline {
pub fn image(target: impl Into<String>, attributes: ExtensionAttributes) -> Self {
Self {
kind: NewInlineKind::Image {
target: target.into(),
},
attributes,
}
}
pub fn quoted(
format: crate::AttributeFormat,
text: impl Into<String>,
attributes: ExtensionAttributes,
) -> Self {
Self {
kind: NewInlineKind::Quoted {
format,
text: text.into(),
},
attributes,
}
}
}
pub trait BlockProcessor: Send + Sync {
fn name(&self) -> &str;
fn contexts(&self) -> &[BlockContext];
fn positional_attributes(&self) -> &[&str] {
&[]
}
fn default_attributes(&self) -> &[(&str, &str)] {
&[]
}
fn content_model(&self) -> ContentModel {
ContentModel::Simple
}
fn process(&self, request: BlockRequest, context: &mut ProcessContext<'_>) -> Option<NewBlock>;
}
pub trait BlockMacroProcessor: Send + Sync {
fn name(&self) -> &str;
fn positional_attributes(&self) -> &[&str] {
&[]
}
fn default_attributes(&self) -> &[(&str, &str)] {
&[]
}
fn content(&self) -> MacroContent {
MacroContent::Attributes
}
fn process(
&self,
request: BlockMacroRequest,
context: &mut ProcessContext<'_>,
) -> Option<NewBlock>;
}
pub trait InlineMacroProcessor: Send + Sync {
fn name(&self) -> &str;
fn format(&self) -> InlineMacroFormat {
InlineMacroFormat::Long
}
fn positional_attributes(&self) -> &[&str] {
&[]
}
fn default_attributes(&self) -> &[(&str, &str)] {
&[]
}
fn content(&self) -> MacroContent {
MacroContent::Attributes
}
fn process(
&self,
request: InlineMacroRequest,
context: &mut ProcessContext<'_>,
) -> Option<NewInline>;
}
pub trait TreeProcessor: Send + Sync {
fn name(&self) -> &str;
fn process(&self, document: &mut Document, context: &mut ProcessContext<'_>);
}
#[derive(Default, Clone)]
pub struct Registry {
blocks: Vec<Arc<dyn BlockProcessor>>,
block_macros: Vec<Arc<dyn BlockMacroProcessor>>,
inline_macros: Vec<Arc<dyn InlineMacroProcessor>>,
tree_processors: Vec<Arc<dyn TreeProcessor>>,
}
impl std::fmt::Debug for Registry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Registry")
.field(
"blocks",
&self.blocks.iter().map(|p| p.name()).collect::<Vec<_>>(),
)
.field(
"block_macros",
&self
.block_macros
.iter()
.map(|p| p.name())
.collect::<Vec<_>>(),
)
.field(
"inline_macros",
&self
.inline_macros
.iter()
.map(|p| p.name())
.collect::<Vec<_>>(),
)
.field(
"tree_processors",
&self
.tree_processors
.iter()
.map(|p| p.name())
.collect::<Vec<_>>(),
)
.finish()
}
}
impl Registry {
pub fn new() -> Self {
Self::default()
}
pub fn register_block(&mut self, processor: impl BlockProcessor + 'static) -> &mut Self {
self.blocks.push(Arc::new(processor));
self
}
pub fn register_block_macro(
&mut self,
processor: impl BlockMacroProcessor + 'static,
) -> &mut Self {
self.block_macros.push(Arc::new(processor));
self
}
pub fn register_inline_macro(
&mut self,
processor: impl InlineMacroProcessor + 'static,
) -> &mut Self {
let processor: Arc<dyn InlineMacroProcessor> = Arc::new(processor);
match self
.inline_macros
.iter_mut()
.find(|p| p.name() == processor.name())
{
Some(slot) => *slot = processor,
None => self.inline_macros.push(processor),
}
self
}
pub fn register_tree_processor(
&mut self,
processor: impl TreeProcessor + 'static,
) -> &mut Self {
self.tree_processors.push(Arc::new(processor));
self
}
pub fn is_empty(&self) -> bool {
self.blocks.is_empty()
&& self.block_macros.is_empty()
&& self.inline_macros.is_empty()
&& self.tree_processors.is_empty()
}
pub fn block(&self, name: &str, context: BlockContext) -> Option<&Arc<dyn BlockProcessor>> {
self.blocks
.iter()
.rev()
.find(|p| p.name() == name && p.contexts().contains(&context))
}
pub fn block_macro(&self, name: &str) -> Option<&Arc<dyn BlockMacroProcessor>> {
self.block_macros.iter().rev().find(|p| p.name() == name)
}
pub fn inline_macros(&self) -> impl Iterator<Item = &Arc<dyn InlineMacroProcessor>> {
self.inline_macros.iter()
}
pub fn tree_processors(&self) -> impl Iterator<Item = &Arc<dyn TreeProcessor>> {
self.tree_processors.iter()
}
}