References for images
This commit is contained in:
parent
c2cfd3f72c
commit
e5ba622e0d
9 changed files with 332 additions and 8 deletions
|
@ -1,6 +1,7 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use crate::compiler::compiler::Compiler;
|
||||
use crate::elements::reference::Reference;
|
||||
use crate::parser::source::Token;
|
||||
use downcast_rs::impl_downcast;
|
||||
use downcast_rs::Downcast;
|
||||
|
@ -68,6 +69,9 @@ pub trait ReferenceableElement: Element {
|
|||
|
||||
/// Key for refcounting
|
||||
fn refcount_key(&self) -> &'static str;
|
||||
|
||||
/// Creates the reference element
|
||||
fn compile_reference(&self, compiler: &Compiler, document: &dyn Document, reference: &Reference, refid: usize) -> Result<String, String>;
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for dyn ReferenceableElement {
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
pub fn validate_refname(name: &str) -> Result<&str, String> {
|
||||
use super::document::Document;
|
||||
|
||||
pub fn validate_refname<'a>(
|
||||
document: &dyn Document,
|
||||
name: &'a str,
|
||||
check_duplicate: bool,
|
||||
) -> Result<&'a str, String> {
|
||||
let trimmed = name.trim_start().trim_end();
|
||||
if trimmed.is_empty() {
|
||||
return Err("Refname cannot be empty".to_string());
|
||||
}
|
||||
|
||||
for c in trimmed.chars() {
|
||||
if c.is_ascii_punctuation() {
|
||||
if c.is_ascii_punctuation() && !(c == '.' || c == '_') {
|
||||
return Err(format!(
|
||||
"Refname `{trimmed}` cannot contain punctuation codepoint: `{c}`"
|
||||
));
|
||||
|
@ -24,5 +30,43 @@ pub fn validate_refname(name: &str) -> Result<&str, String> {
|
|||
}
|
||||
}
|
||||
|
||||
Ok(trimmed)
|
||||
if check_duplicate && document.get_reference(trimmed).is_some() {
|
||||
Err(format!("Refname `{trimmed}` is already in use!"))
|
||||
} else {
|
||||
Ok(trimmed)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::*;
|
||||
use crate::parser::langparser::LangParser;
|
||||
use crate::parser::parser::Parser;
|
||||
use crate::parser::source::SourceFile;
|
||||
|
||||
#[test]
|
||||
fn validate_refname_tests() {
|
||||
let source = Rc::new(SourceFile::with_content(
|
||||
"".to_string(),
|
||||
"#{ref} Section".to_string(),
|
||||
None,
|
||||
));
|
||||
let parser = LangParser::default();
|
||||
let doc = parser.parse(source, None);
|
||||
|
||||
assert_eq!(validate_refname(&*doc, " abc ", true), Ok("abc"));
|
||||
assert_eq!(
|
||||
validate_refname(&*doc, " Some_reference ", true),
|
||||
Ok("Some_reference")
|
||||
);
|
||||
assert!(validate_refname(&*doc, "", true).is_err());
|
||||
assert!(validate_refname(&*doc, "\n", true).is_err());
|
||||
assert!(validate_refname(&*doc, "'", true).is_err());
|
||||
assert!(validate_refname(&*doc, "]", true).is_err());
|
||||
|
||||
// Duplicate
|
||||
assert!(validate_refname(&*doc, "ref", true).is_err());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ use crate::document::element::ElemKind;
|
|||
use crate::document::element::Element;
|
||||
use crate::document::element::ReferenceableElement;
|
||||
use crate::document::references::validate_refname;
|
||||
use crate::parser::parser::Parser;
|
||||
use crate::parser::parser::ReportColors;
|
||||
use crate::parser::rule::RegexRule;
|
||||
use crate::parser::source::Source;
|
||||
|
@ -34,6 +35,7 @@ use crate::parser::util::PropertyMapError;
|
|||
use crate::parser::util::PropertyParser;
|
||||
|
||||
use super::paragraph::Paragraph;
|
||||
use super::reference::Reference;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MediaType {
|
||||
|
@ -186,6 +188,32 @@ impl ReferenceableElement for Medium {
|
|||
fn reference_name(&self) -> Option<&String> { Some(&self.reference) }
|
||||
|
||||
fn refcount_key(&self) -> &'static str { "medium" }
|
||||
|
||||
fn compile_reference(
|
||||
&self,
|
||||
compiler: &Compiler,
|
||||
_document: &dyn Document,
|
||||
reference: &Reference,
|
||||
refid: usize,
|
||||
) -> Result<String, String> {
|
||||
match compiler.target() {
|
||||
Target::HTML => {
|
||||
let caption = reference
|
||||
.caption()
|
||||
.map_or(format!("({refid})"), |cap| cap.clone());
|
||||
|
||||
// TODO Handle other kind of media
|
||||
match self.media_type {
|
||||
MediaType::IMAGE => Ok(format!(
|
||||
r#"<a class="medium-ref">{caption}<img src="{}"></a>"#,
|
||||
self.uri
|
||||
)),
|
||||
_ => todo!(""),
|
||||
}
|
||||
}
|
||||
_ => todo!(""),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MediaRule {
|
||||
|
@ -296,7 +324,7 @@ impl RegexRule for MediaRule {
|
|||
fn on_regex_match<'a>(
|
||||
&self,
|
||||
_: usize,
|
||||
parser: &dyn crate::parser::parser::Parser,
|
||||
parser: &dyn Parser,
|
||||
document: &'a (dyn Document<'a> + 'a),
|
||||
token: Token,
|
||||
matches: Captures,
|
||||
|
@ -305,7 +333,7 @@ impl RegexRule for MediaRule {
|
|||
|
||||
let refname = match (
|
||||
matches.get(1).unwrap(),
|
||||
validate_refname(matches.get(1).unwrap().as_str()),
|
||||
validate_refname(document, matches.get(1).unwrap().as_str(), true),
|
||||
) {
|
||||
(_, Ok(refname)) => refname.to_string(),
|
||||
(m, Err(err)) => {
|
||||
|
|
|
@ -14,3 +14,4 @@ pub mod tex;
|
|||
pub mod graphviz;
|
||||
pub mod raw;
|
||||
pub mod media;
|
||||
pub mod reference;
|
||||
|
|
211
src/elements/reference.rs
Normal file
211
src/elements/reference.rs
Normal file
|
@ -0,0 +1,211 @@
|
|||
use std::collections::HashMap;
|
||||
use std::ops::Range;
|
||||
use std::rc::Rc;
|
||||
|
||||
use ariadne::Fmt;
|
||||
use ariadne::Label;
|
||||
use ariadne::Report;
|
||||
use ariadne::ReportKind;
|
||||
use regex::Captures;
|
||||
use regex::Match;
|
||||
use regex::Regex;
|
||||
|
||||
use crate::compiler::compiler::Compiler;
|
||||
use crate::compiler::compiler::Target;
|
||||
use crate::document::document::Document;
|
||||
use crate::document::element::ElemKind;
|
||||
use crate::document::element::Element;
|
||||
use crate::document::references::validate_refname;
|
||||
use crate::parser::parser::Parser;
|
||||
use crate::parser::parser::ReportColors;
|
||||
use crate::parser::rule::RegexRule;
|
||||
use crate::parser::source::Source;
|
||||
use crate::parser::source::Token;
|
||||
use crate::parser::util;
|
||||
use crate::parser::util::Property;
|
||||
use crate::parser::util::PropertyMap;
|
||||
use crate::parser::util::PropertyParser;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Reference {
|
||||
pub(self) location: Token,
|
||||
pub(self) refname: String,
|
||||
pub(self) caption: Option<String>,
|
||||
}
|
||||
|
||||
impl Reference {
|
||||
pub fn caption(&self) -> Option<&String> { self.caption.as_ref() }
|
||||
}
|
||||
|
||||
impl Element for Reference {
|
||||
fn location(&self) -> &Token { &self.location }
|
||||
|
||||
fn kind(&self) -> ElemKind { ElemKind::Inline }
|
||||
|
||||
fn element_name(&self) -> &'static str { "Reference" }
|
||||
|
||||
fn to_string(&self) -> String { format!("{self:#?}") }
|
||||
|
||||
fn compile(&self, compiler: &Compiler, document: &dyn Document) -> Result<String, String> {
|
||||
match compiler.target() {
|
||||
Target::HTML => {
|
||||
let elemref = document.get_reference(self.refname.as_str()).unwrap();
|
||||
let elem = document.get_from_reference(&elemref).unwrap();
|
||||
|
||||
elem.compile_reference(
|
||||
compiler,
|
||||
document,
|
||||
self,
|
||||
compiler.reference_id(document, elemref),
|
||||
)
|
||||
}
|
||||
_ => todo!(""),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ReferenceRule {
|
||||
re: [Regex; 1],
|
||||
properties: PropertyParser,
|
||||
}
|
||||
|
||||
impl ReferenceRule {
|
||||
pub fn new() -> Self {
|
||||
let mut props = HashMap::new();
|
||||
props.insert(
|
||||
"caption".to_string(),
|
||||
Property::new(
|
||||
false,
|
||||
"Override the display of the reference".to_string(),
|
||||
None,
|
||||
),
|
||||
);
|
||||
Self {
|
||||
re: [Regex::new(r"§\{(.*)\}(\[((?:\\.|[^\\\\])*?)\])?").unwrap()],
|
||||
properties: PropertyParser::new(props),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_properties(
|
||||
&self,
|
||||
colors: &ReportColors,
|
||||
token: &Token,
|
||||
m: &Option<Match>,
|
||||
) -> Result<PropertyMap, Report<'_, (Rc<dyn Source>, Range<usize>)>> {
|
||||
match m {
|
||||
None => match self.properties.default() {
|
||||
Ok(properties) => Ok(properties),
|
||||
Err(e) => Err(
|
||||
Report::build(ReportKind::Error, token.source(), token.start())
|
||||
.with_message("Invalid Media Properties")
|
||||
.with_label(
|
||||
Label::new((token.source().clone(), token.range.clone()))
|
||||
.with_message(format!("Media is missing required property: {e}"))
|
||||
.with_color(colors.error),
|
||||
)
|
||||
.finish(),
|
||||
),
|
||||
},
|
||||
Some(props) => {
|
||||
let processed =
|
||||
util::process_escaped('\\', "]", props.as_str().trim_start().trim_end());
|
||||
match self.properties.parse(processed.as_str()) {
|
||||
Err(e) => Err(
|
||||
Report::build(ReportKind::Error, token.source(), props.start())
|
||||
.with_message("Invalid Media Properties")
|
||||
.with_label(
|
||||
Label::new((token.source().clone(), props.range()))
|
||||
.with_message(e)
|
||||
.with_color(colors.error),
|
||||
)
|
||||
.finish(),
|
||||
),
|
||||
Ok(properties) => Ok(properties),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RegexRule for ReferenceRule {
|
||||
fn name(&self) -> &'static str { "Reference" }
|
||||
|
||||
fn regexes(&self) -> &[regex::Regex] { &self.re }
|
||||
|
||||
fn on_regex_match<'a>(
|
||||
&self,
|
||||
_: usize,
|
||||
parser: &dyn Parser,
|
||||
document: &'a (dyn Document<'a> + 'a),
|
||||
token: Token,
|
||||
matches: Captures,
|
||||
) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>> {
|
||||
let mut reports = vec![];
|
||||
|
||||
let refname = match (
|
||||
matches.get(1).unwrap(),
|
||||
validate_refname(document, matches.get(1).unwrap().as_str(), false),
|
||||
) {
|
||||
(m, Ok(refname)) => {
|
||||
if document.get_reference(refname).is_none() {
|
||||
reports.push(
|
||||
Report::build(ReportKind::Error, token.source(), m.start())
|
||||
.with_message("Uknown Reference Refname")
|
||||
.with_label(
|
||||
Label::new((token.source().clone(), m.range())).with_message(
|
||||
format!(
|
||||
"Could not find element with reference: `{}`",
|
||||
refname.fg(parser.colors().info)
|
||||
),
|
||||
),
|
||||
)
|
||||
.finish(),
|
||||
);
|
||||
return reports;
|
||||
}
|
||||
refname.to_string()
|
||||
}
|
||||
(m, Err(err)) => {
|
||||
reports.push(
|
||||
Report::build(ReportKind::Error, token.source(), m.start())
|
||||
.with_message("Invalid Reference Refname")
|
||||
.with_label(
|
||||
Label::new((token.source().clone(), m.range())).with_message(err),
|
||||
)
|
||||
.finish(),
|
||||
);
|
||||
return reports;
|
||||
}
|
||||
};
|
||||
// Properties
|
||||
let properties = match self.parse_properties(parser.colors(), &token, &matches.get(3)) {
|
||||
Ok(pm) => pm,
|
||||
Err(report) => {
|
||||
reports.push(report);
|
||||
return reports;
|
||||
}
|
||||
};
|
||||
|
||||
let caption = properties
|
||||
.get("caption", |_, value| -> Result<String, ()> {
|
||||
Ok(value.clone())
|
||||
})
|
||||
.ok()
|
||||
.and_then(|(_, s)| Some(s));
|
||||
|
||||
parser.push(
|
||||
document,
|
||||
Box::new(Reference {
|
||||
location: token,
|
||||
refname,
|
||||
caption,
|
||||
}),
|
||||
);
|
||||
|
||||
reports
|
||||
}
|
||||
|
||||
fn lua_bindings<'lua>(&self, _lua: &'lua mlua::Lua) -> Vec<(String, mlua::Function<'lua>)> {
|
||||
vec![]
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@ use super::tex::TexRule;
|
|||
use super::text::TextRule;
|
||||
use super::variable::VariableRule;
|
||||
use super::variable::VariableSubstitutionRule;
|
||||
use super::reference::ReferenceRule;
|
||||
|
||||
pub fn register<P: Parser>(parser: &mut P) {
|
||||
parser.add_rule(Box::new(CommentRule::new()), None).unwrap();
|
||||
|
@ -35,4 +36,5 @@ pub fn register<P: Parser>(parser: &mut P) {
|
|||
parser.add_rule(Box::new(SectionRule::new()), None).unwrap();
|
||||
parser.add_rule(Box::new(LinkRule::new()), None).unwrap();
|
||||
parser.add_rule(Box::new(TextRule::default()), None).unwrap();
|
||||
parser.add_rule(Box::new(ReferenceRule::new()), None).unwrap();
|
||||
}
|
||||
|
|
|
@ -52,6 +52,16 @@ impl ReferenceableElement for Section {
|
|||
fn reference_name(&self) -> Option<&String> { self.reference.as_ref() }
|
||||
|
||||
fn refcount_key(&self) -> &'static str { "section" }
|
||||
|
||||
fn compile_reference(
|
||||
&self,
|
||||
compiler: &Compiler,
|
||||
document: &dyn Document,
|
||||
reference: &super::reference::Reference,
|
||||
refid: usize,
|
||||
) -> Result<String, String> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SectionRule {
|
||||
|
|
|
@ -85,8 +85,7 @@ fn main() {
|
|||
println!("-- END AST DEBUGGING --");
|
||||
}
|
||||
|
||||
if debug_opts.contains(&"ref".to_string())
|
||||
{
|
||||
if debug_opts.contains(&"ref".to_string()) {
|
||||
println!("-- BEGIN REFERENCES DEBUGGING --");
|
||||
let sc = doc.scope().borrow();
|
||||
sc.referenceable.iter().for_each(|(name, reference)| {
|
||||
|
|
27
style.css
27
style.css
|
@ -97,7 +97,7 @@ div.medium p.medium-refname {
|
|||
text-align: center;
|
||||
|
||||
font-weight: bold;
|
||||
color: #9424af;
|
||||
color: #d367c1;
|
||||
}
|
||||
|
||||
div.medium p {
|
||||
|
@ -108,3 +108,28 @@ div.medium p {
|
|||
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
a.medium-ref {
|
||||
display: inline;
|
||||
|
||||
font-weight: bold;
|
||||
color: #d367c1;
|
||||
}
|
||||
|
||||
a.medium-ref:hover {
|
||||
background: #334;
|
||||
}
|
||||
|
||||
a.medium-ref img {
|
||||
display: none;
|
||||
margin: 1.3em 0 0 0;
|
||||
}
|
||||
|
||||
a:hover.medium-ref img {
|
||||
max-width: 50%;
|
||||
margin: auto;
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
|
||||
box-shadow: 0px 0px 6px 2px rgba(0,0,0,0.75);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue