diff options
| author | Philipp A | 2023-12-28 15:43:24 +0100 |
|---|---|---|
| committer | GitHub | 2023-12-28 15:43:24 +0100 |
| commit | 0f4f1a420cbcf263a9118ec9b288c95f1b59ade8 (patch) | |
| tree | 030abd05e8894e4e9c6775794f890180cf6c5e91 | |
| parent | 38b1c488601cfc2479f02df555b135ef01aa5618 (diff) | |
| parent | 774dd4798aedc40b38c6480e9c47f34c482f12d0 (diff) | |
| download | rust-rst-0f4f1a420cbcf263a9118ec9b288c95f1b59ade8.tar.bz2 | |
Merge branch 'main' into allow-rst-to-read-from-stdin
37 files changed, 3158 insertions, 2467 deletions
diff --git a/.editorconfig b/.editorconfig index c730fb1..73554fa 100644 --- a/.editorconfig +++ b/.editorconfig @@ -2,12 +2,12 @@ root = true [*] -indent_style = tab +indent_style = space indent_size = 4 end_of_line = lf charset = utf-8 -trim_trailing_whitespace = false +trim_trailing_whitespace = true insert_final_newline = true -[*.pest] -indent_style = space +[*.{yml,yaml}] +indent_size = 2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..25b0ac9 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,20 @@ +name: Create release PR or publish GitHub release and Rust crate + +permissions: + pull-requests: write + contents: write + +on: + push: + branches: [main] + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + - uses: MarcoIeni/release-plz-action@v0.5 + env: + GITHUB_TOKEN: ${{ secrets.PAT_GITHUB }} + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..6e35283 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,55 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + CARGO_TERM_COLOR: always + +jobs: + build_and_test: + name: Build, check, and test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy + - uses: taiki-e/install-action@v2 + with: + tool: cargo-hack,just + - uses: Swatinem/rust-cache@v2 + - run: just build + - run: just check + - run: just fmt --check + - run: just test + doc: + name: Build and publish docs + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/configure-pages@v3 + - uses: dtolnay/rust-toolchain@nightly + - uses: taiki-e/install-action@v2 + with: + tool: just + - uses: Swatinem/rust-cache@v2 + - run: just doc + - uses: actions/upload-pages-artifact@v3 + with: + path: target/doc + - uses: actions/deploy-pages@v4 + if: github.ref_name == 'main' + id: deployment diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..1c4f592 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,20 @@ +ci: + skip: + - cargo-fmt + - cargo-clippy + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + - id: detect-private-key + - id: check-merge-conflict + - repo: https://github.com/kykosic/pre-commit-rust + rev: '0.4.0' + hooks: + - id: cargo-fmt + - id: cargo-clippy diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 0f8af64..0000000 --- a/.travis.yml +++ /dev/null @@ -1,7 +0,0 @@ -dist: bionic -language: rust -cache: cargo -rust: - - stable -env: - - RUST_BACKTRACE=short diff --git a/.vscode/launch.json b/.vscode/launch.json index acc54de..3629e14 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -38,4 +38,4 @@ "cwd": "${workspaceFolder}" } ] -}
\ No newline at end of file +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 7a5dde2..4538515 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,6 @@ { "explorer.sortOrder": "mixed", - "restructuredtext.confPath": "" -}
\ No newline at end of file + "restructuredtext.confPath": "", + "rust-analyzer.check.features": "all", + "rust-analyzer.check.command": "clippy", +} @@ -1,7 +1,7 @@ [workspace] members = [ - 'document_tree', - 'parser', - 'renderer', - 'rst', + 'document_tree', + 'parser', + 'renderer', + 'rst', ] @@ -1,9 +1,6 @@ -========================= -RuSTructuredText |travis| -========================= - -.. |travis| image:: https://travis-ci.com/flying-sheep/rust-rst.svg?branch=master - :target: https://travis-ci.com/flying-sheep/rust-rst +================ +RuSTructuredText +================ .. image:: rust-rst.svg diff --git a/document_tree/Cargo.toml b/document_tree/Cargo.toml index 9e1c481..9a3ccc8 100644 --- a/document_tree/Cargo.toml +++ b/document_tree/Cargo.toml @@ -1,6 +1,6 @@ [package] name = 'document_tree' -version = '0.3.0' +version = '0.4.0' authors = ['Philipp A. <flying-sheep@web.de>'] edition = '2018' description = 'reStructuredText’s DocumentTree representation' diff --git a/document_tree/src/attribute_types.rs b/document_tree/src/attribute_types.rs index 411b24d..5303c5b 100644 --- a/document_tree/src/attribute_types.rs +++ b/document_tree/src/attribute_types.rs @@ -1,155 +1,201 @@ use std::str::FromStr; -use failure::{Error,bail,format_err}; -use serde_derive::Serialize; +use failure::{bail, format_err, Error}; use regex::Regex; +use serde_derive::Serialize; use crate::url::Url; -#[derive(Debug,PartialEq,Eq,Hash,Serialize,Clone)] +#[derive(Debug, PartialEq, Eq, Hash, Serialize, Clone)] pub enum EnumeratedListType { - Arabic, - LowerAlpha, - UpperAlpha, - LowerRoman, - UpperRoman, + Arabic, + LowerAlpha, + UpperAlpha, + LowerRoman, + UpperRoman, } -#[derive(Debug,PartialEq,Eq,Hash,Serialize,Clone)] -pub enum FixedSpace { Default, Preserve } // yes, default really is not “Default” -impl Default for FixedSpace { fn default() -> FixedSpace { FixedSpace::Preserve } } +#[derive(Default, Debug, PartialEq, Eq, Hash, Serialize, Clone)] +pub enum FixedSpace { + Default, + // yes, default really is not “Default” + #[default] + Preserve, +} -#[derive(Debug,PartialEq,Eq,Hash,Serialize,Clone)] pub enum AlignH { Left, Center, Right} -#[derive(Debug,PartialEq,Eq,Hash,Serialize,Clone)] pub enum AlignHV { Top, Middle, Bottom, Left, Center, Right } -#[derive(Debug,PartialEq,Eq,Hash,Serialize,Clone)] pub enum AlignV { Top, Middle, Bottom } +#[derive(Debug, PartialEq, Eq, Hash, Serialize, Clone)] +pub enum AlignH { + Left, + Center, + Right, +} +#[derive(Debug, PartialEq, Eq, Hash, Serialize, Clone)] +pub enum AlignHV { + Top, + Middle, + Bottom, + Left, + Center, + Right, +} +#[derive(Debug, PartialEq, Eq, Hash, Serialize, Clone)] +pub enum AlignV { + Top, + Middle, + Bottom, +} -#[derive(Debug,PartialEq,Eq,Hash,Serialize,Clone)] pub enum TableAlignH { Left, Right, Center, Justify, Char } -#[derive(Debug,PartialEq,Eq,Hash,Serialize,Clone)] pub enum TableBorder { Top, Bottom, TopBottom, All, Sides, None } +#[derive(Debug, PartialEq, Eq, Hash, Serialize, Clone)] +pub enum TableAlignH { + Left, + Right, + Center, + Justify, + Char, +} +#[derive(Debug, PartialEq, Eq, Hash, Serialize, Clone)] +pub enum TableBorder { + Top, + Bottom, + TopBottom, + All, + Sides, + None, +} -#[derive(Debug,PartialEq,Eq,Hash,Serialize,Clone)] pub struct ID(pub String); -#[derive(Debug,PartialEq,Eq,Hash,Serialize,Clone)] pub struct NameToken(pub String); +#[derive(Debug, PartialEq, Eq, Hash, Serialize, Clone)] +pub struct ID(pub String); +#[derive(Debug, PartialEq, Eq, Hash, Serialize, Clone)] +pub struct NameToken(pub String); // The table DTD has the cols attribute of tgroup as required, but having // TableGroupCols not implement Default would leave no possible implementation // for TableGroup::with_children. -#[derive(Debug,PartialEq,Eq,Hash,Serialize,Clone)] pub struct TableGroupCols(pub usize); -impl Default for TableGroupCols { - fn default() -> Self { - TableGroupCols(0) - } -} +#[derive(Default, Debug, PartialEq, Eq, Hash, Serialize, Clone)] +pub struct TableGroupCols(pub usize); // no eq for f64 -#[derive(Debug,PartialEq,Serialize,Clone)] -pub enum Measure { // http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html#length-units - Em(f64), - Ex(f64), - Mm(f64), - Cm(f64), - In(f64), - Px(f64), - Pt(f64), - Pc(f64), +#[derive(Debug, PartialEq, Serialize, Clone)] +pub enum Measure { + // http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html#length-units + Em(f64), + Ex(f64), + Mm(f64), + Cm(f64), + In(f64), + Px(f64), + Pt(f64), + Pc(f64), } impl FromStr for AlignHV { - type Err = Error; - fn from_str(s: &str) -> Result<Self, Self::Err> { - use self::AlignHV::*; - Ok(match s { - "top" => Top, - "middle" => Middle, - "bottom" => Bottom, - "left" => Left, - "center" => Center, - "right" => Right, - s => bail!("Invalid Alignment {}", s), - }) - } + type Err = Error; + fn from_str(s: &str) -> Result<Self, Self::Err> { + use self::AlignHV::*; + Ok(match s { + "top" => Top, + "middle" => Middle, + "bottom" => Bottom, + "left" => Left, + "center" => Center, + "right" => Right, + s => bail!("Invalid Alignment {}", s), + }) + } } impl From<&str> for ID { - fn from(s: &str) -> Self { - ID(s.to_owned().replace(' ', "-")) - } + fn from(s: &str) -> Self { + ID(s.to_owned().replace(' ', "-")) + } } impl From<&str> for NameToken { - fn from(s: &str) -> Self { - NameToken(s.to_owned()) - } + fn from(s: &str) -> Self { + NameToken(s.to_owned()) + } } impl FromStr for Measure { - type Err = Error; - fn from_str(s: &str) -> Result<Self, Self::Err> { - use self::Measure::*; - let re = Regex::new(r"(?P<float>\d+\.\d*|\.?\d+)\s*(?P<unit>em|ex|mm|cm|in|px|pt|pc)").unwrap(); - let caps: regex::Captures = re.captures(s).ok_or_else(|| format_err!("Invalid measure"))?; - let value: f64 = caps["float"].parse()?; - Ok(match &caps["unit"] { - "em" => Em(value), - "ex" => Ex(value), - "mm" => Mm(value), - "cm" => Cm(value), - "in" => In(value), - "px" => Px(value), - "pt" => Pt(value), - "pc" => Pc(value), - _ => unreachable!(), - }) - } + type Err = Error; + fn from_str(s: &str) -> Result<Self, Self::Err> { + use self::Measure::*; + let re = + Regex::new(r"(?P<float>\d+\.\d*|\.?\d+)\s*(?P<unit>em|ex|mm|cm|in|px|pt|pc)").unwrap(); + let caps: regex::Captures = re + .captures(s) + .ok_or_else(|| format_err!("Invalid measure"))?; + let value: f64 = caps["float"].parse()?; + Ok(match &caps["unit"] { + "em" => Em(value), + "ex" => Ex(value), + "mm" => Mm(value), + "cm" => Cm(value), + "in" => In(value), + "px" => Px(value), + "pt" => Pt(value), + "pc" => Pc(value), + _ => unreachable!(), + }) + } } #[cfg(test)] mod parse_tests { - use super::*; - - #[test] - fn measure() { - let _a: Measure = "1.5em".parse().unwrap(); - let _b: Measure = "20 mm".parse().unwrap(); - let _c: Measure = ".5in".parse().unwrap(); - let _d: Measure = "1.pc".parse().unwrap(); - } + use super::*; + + #[test] + fn measure() { + let _a: Measure = "1.5em".parse().unwrap(); + let _b: Measure = "20 mm".parse().unwrap(); + let _c: Measure = ".5in".parse().unwrap(); + let _d: Measure = "1.pc".parse().unwrap(); + } } pub(crate) trait CanBeEmpty { - fn is_empty(&self) -> bool; + fn is_empty(&self) -> bool; } /* Specialization necessary impl<T> CanBeEmpty for T { - fn is_empty(&self) -> bool { false } + fn is_empty(&self) -> bool { false } } */ macro_rules! impl_cannot_be_empty { - ($t:ty) => { - impl CanBeEmpty for $t { - fn is_empty(&self) -> bool { false } - } - }; - ($t:ty, $($ts:ty),*) => { - impl_cannot_be_empty!($t); - impl_cannot_be_empty!($($ts),*); - }; + ($t:ty) => { + impl CanBeEmpty for $t { + fn is_empty(&self) -> bool { false } + } + }; + ($t:ty, $($ts:ty),*) => { + impl_cannot_be_empty!($t); + impl_cannot_be_empty!($($ts),*); + }; } impl_cannot_be_empty!(Url); impl_cannot_be_empty!(TableGroupCols); impl<T> CanBeEmpty for Option<T> { - fn is_empty(&self) -> bool { self.is_none() } + fn is_empty(&self) -> bool { + self.is_none() + } } impl<T> CanBeEmpty for Vec<T> { - fn is_empty(&self) -> bool { self.is_empty() } + fn is_empty(&self) -> bool { + self.is_empty() + } } impl CanBeEmpty for bool { - fn is_empty(&self) -> bool { !self } + fn is_empty(&self) -> bool { + !self + } } impl CanBeEmpty for FixedSpace { - fn is_empty(&self) -> bool { self == &FixedSpace::default() } + fn is_empty(&self) -> bool { + self == &FixedSpace::default() + } } - diff --git a/document_tree/src/element_categories.rs b/document_tree/src/element_categories.rs index 24a0798..e0a1886 100644 --- a/document_tree/src/element_categories.rs +++ b/document_tree/src/element_categories.rs @@ -1,130 +1,200 @@ -use std::fmt::{self,Debug,Formatter}; +use std::fmt::{self, Debug, Formatter}; use serde_derive::Serialize; use crate::elements::*; pub trait HasChildren<C> { - fn with_children(children: Vec<C>) -> Self; - fn children(&self) -> &Vec<C>; - fn children_mut(&mut self) -> &mut Vec<C>; - fn append_child<R: Into<C>>(&mut self, child: R) { - self.children_mut().push(child.into()); - } - fn append_children<R: Into<C> + Clone>(&mut self, more: &[R]) { - let children = self.children_mut(); - children.reserve(more.len()); - for child in more { - children.push(child.clone().into()); - } - } + fn with_children(children: Vec<C>) -> Self; + fn children(&self) -> &Vec<C>; + fn children_mut(&mut self) -> &mut Vec<C>; + fn append_child<R: Into<C>>(&mut self, child: R) { + self.children_mut().push(child.into()); + } + fn append_children<R: Into<C> + Clone>(&mut self, more: &[R]) { + let children = self.children_mut(); + children.reserve(more.len()); + for child in more { + children.push(child.clone().into()); + } + } } macro_rules! impl_into { - ([ $( (($subcat:ident :: $entry:ident), $supcat:ident), )+ ]) => { - $( impl_into!($subcat::$entry => $supcat); )+ - }; - ($subcat:ident :: $entry:ident => $supcat:ident ) => { - impl Into<$supcat> for $entry { - fn into(self) -> $supcat { - $supcat::$subcat(Box::new(self.into())) - } - } - }; + ([ $( (($subcat:ident :: $entry:ident), $supcat:ident), )+ ]) => { + $( impl_into!($subcat::$entry => $supcat); )+ + }; + ($subcat:ident :: $entry:ident => $supcat:ident ) => { + impl From<$entry> for $supcat { + fn from(inner: $entry) -> Self { + $supcat::$subcat(Box::new(inner.into())) + } + } + }; } macro_rules! synonymous_enum { - ( $subcat:ident : $($supcat:ident),+ ; $midcat:ident : $supsupcat:ident { $($entry:ident),+ $(,)* } ) => { - synonymous_enum!($subcat : $( $supcat ),+ , $midcat { $($entry,)* }); - $( impl_into!($midcat::$entry => $supsupcat); )+ - }; - ( $subcat:ident : $($supcat:ident),+ { $($entry:ident),+ $(,)* } ) => { - synonymous_enum!($subcat { $( $entry, )* }); - cartesian!(impl_into, [ $( ($subcat::$entry) ),+ ], [ $($supcat),+ ]); - }; - ( $name:ident { $( $entry:ident ),+ $(,)* } ) => { - #[derive(PartialEq,Serialize,Clone)] - pub enum $name { $( - $entry(Box<$entry>), - )* } - - impl Debug for $name { - fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> { - match *self { - $( $name::$entry(ref inner) => inner.fmt(fmt), )* - } - } - } - - $( impl Into<$name> for $entry { - fn into(self) -> $name { - $name::$entry(Box::new(self)) - } - } )* - }; + ( $subcat:ident : $($supcat:ident),+ ; $midcat:ident : $supsupcat:ident { $($entry:ident),+ $(,)* } ) => { + synonymous_enum!($subcat : $( $supcat ),+ , $midcat { $($entry,)* }); + $( impl_into!($midcat::$entry => $supsupcat); )+ + }; + ( $subcat:ident : $($supcat:ident),+ { $($entry:ident),+ $(,)* } ) => { + synonymous_enum!($subcat { $( $entry, )* }); + cartesian!(impl_into, [ $( ($subcat::$entry) ),+ ], [ $($supcat),+ ]); + }; + ( $name:ident { $( $entry:ident ),+ $(,)* } ) => { + #[derive(PartialEq,Serialize,Clone)] + pub enum $name { $( + $entry(Box<$entry>), + )* } + + impl Debug for $name { + fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> { + match *self { + $( $name::$entry(ref inner) => inner.fmt(fmt), )* + } + } + } + + $( impl From<$entry> for $name { + fn from(inner: $entry) -> Self { + $name::$entry(Box::new(inner)) + } + } )* + }; } -synonymous_enum!(StructuralSubElement { Title, Subtitle, Decoration, Docinfo, SubStructure }); +synonymous_enum!(StructuralSubElement { + Title, + Subtitle, + Decoration, + Docinfo, + SubStructure +}); synonymous_enum!(SubStructure: StructuralSubElement { Topic, Sidebar, Transition, Section, BodyElement }); synonymous_enum!(BodyElement: SubTopic, SubSidebar, SubBlockQuote, SubFootnote, SubFigure; SubStructure: StructuralSubElement { - //Simple - Paragraph, LiteralBlock, DoctestBlock, MathBlock, Rubric, SubstitutionDefinition, Comment, Pending, Target, Raw, Image, - //Compound - Compound, Container, - BulletList, EnumeratedList, DefinitionList, FieldList, OptionList, - LineBlock, BlockQuote, Admonition, Attention, Hint, Note, Caution, Danger, Error, Important, Tip, Warning, Footnote, Citation, SystemMessage, Figure, Table + //Simple + Paragraph, LiteralBlock, DoctestBlock, MathBlock, Rubric, SubstitutionDefinition, Comment, Pending, Target, Raw, Image, + //Compound + Compound, Container, + BulletList, EnumeratedList, DefinitionList, FieldList, OptionList, + LineBlock, BlockQuote, Admonition, Attention, Hint, Note, Caution, Danger, Error, Important, Tip, Warning, Footnote, Citation, SystemMessage, Figure, Table }); -synonymous_enum!(BibliographicElement { Author, Authors, Organization, Address, Contact, Version, Revision, Status, Date, Copyright, Field }); +synonymous_enum!(BibliographicElement { + Author, + Authors, + Organization, + Address, + Contact, + Version, + Revision, + Status, + Date, + Copyright, + Field +}); synonymous_enum!(TextOrInlineElement { - String, Emphasis, Strong, Literal, Reference, FootnoteReference, CitationReference, SubstitutionReference, TitleReference, Abbreviation, Acronym, Superscript, Subscript, Inline, Problematic, Generated, Math, - //also have non-inline versions. Inline image is no figure child, inline target has content - TargetInline, RawInline, ImageInline + String, + Emphasis, + Strong, + Literal, + Reference, + FootnoteReference, + CitationReference, + SubstitutionReference, + TitleReference, + Abbreviation, + Acronym, + Superscript, + Subscript, + Inline, + Problematic, + Generated, + Math, + //also have non-inline versions. Inline image is no figure child, inline target has content + TargetInline, + RawInline, + ImageInline }); //--------------\\ //Content Models\\ //--------------\\ -synonymous_enum!(AuthorInfo { Author, Organization, Address, Contact }); +synonymous_enum!(AuthorInfo { + Author, + Organization, + Address, + Contact +}); synonymous_enum!(DecorationElement { Header, Footer }); synonymous_enum!(SubTopic { Title, BodyElement }); -synonymous_enum!(SubSidebar { Topic, Title, Subtitle, BodyElement }); -synonymous_enum!(SubDLItem { Term, Classifier, Definition }); -synonymous_enum!(SubField { FieldName, FieldBody }); -synonymous_enum!(SubOptionListItem { OptionGroup, Description }); -synonymous_enum!(SubOption { OptionString, OptionArgument }); +synonymous_enum!(SubSidebar { + Topic, + Title, + Subtitle, + BodyElement +}); +synonymous_enum!(SubDLItem { + Term, + Classifier, + Definition +}); +synonymous_enum!(SubField { + FieldName, + FieldBody +}); +synonymous_enum!(SubOptionListItem { + OptionGroup, + Description +}); +synonymous_enum!(SubOption { + OptionString, + OptionArgument +}); synonymous_enum!(SubLineBlock { LineBlock, Line }); -synonymous_enum!(SubBlockQuote { Attribution, BodyElement }); +synonymous_enum!(SubBlockQuote { + Attribution, + BodyElement +}); synonymous_enum!(SubFootnote { Label, BodyElement }); -synonymous_enum!(SubFigure { Caption, Legend, BodyElement }); +synonymous_enum!(SubFigure { + Caption, + Legend, + BodyElement +}); synonymous_enum!(SubTable { Title, TableGroup }); -synonymous_enum!(SubTableGroup { TableColspec, TableHead, TableBody }); +synonymous_enum!(SubTableGroup { + TableColspec, + TableHead, + TableBody +}); #[cfg(test)] mod conversion_tests { - use std::default::Default; - use super::*; - - #[test] - fn basic() { - let _: BodyElement = Paragraph::default().into(); - } - - #[test] - fn more() { - let _: SubStructure = Paragraph::default().into(); - } - - #[test] - fn even_more() { - let _: StructuralSubElement = Paragraph::default().into(); - } - - #[test] - fn super_() { - let be: BodyElement = Paragraph::default().into(); - let _: StructuralSubElement = be.into(); - } + use super::*; + use std::default::Default; + + #[test] + fn basic() { + let _: BodyElement = Paragraph::default().into(); + } + + #[test] + fn more() { + let _: SubStructure = Paragraph::default().into(); + } + + #[test] + fn even_more() { + let _: StructuralSubElement = Paragraph::default().into(); + } + + #[test] + fn super_() { + let be: BodyElement = Paragraph::default().into(); + let _: StructuralSubElement = be.into(); + } } diff --git a/document_tree/src/element_types.rs b/document_tree/src/element_types.rs index 429573e..42571d1 100644 --- a/document_tree/src/element_types.rs +++ b/document_tree/src/element_types.rs @@ -1,96 +1,96 @@ // enum ElementType { -// //structual elements -// Section, Topic, Sidebar, -// -// //structural subelements -// Title, Subtitle, Decoration, Docinfo, Transition, -// -// //bibliographic elements -// Author, Authors, Organization, -// Address { space: FixedSpace }, -// Contact, Version, Revision, Status, -// Date, Copyright, Field, -// -// //decoration elements -// Header, Footer, -// -// //simple body elements -// Paragraph, -// LiteralBlock { space: FixedSpace }, -// DoctestBlock { space: FixedSpace }, -// MathBlock, Rubric, -// SubstitutionDefinition { ltrim: bool, rtrim: bool }, -// Comment { space: FixedSpace }, -// Pending, -// Target { refuri: Url, refid: ID, refname: Vec<NameToken>, anonymous: bool }, -// Raw { space: FixedSpace, format: Vec<NameToken> }, -// Image { -// align: AlignHV, -// uri: Url, -// alt: String, -// height: Measure, -// width: Measure, -// scale: f64, -// }, -// -// //compound body elements -// Compound, Container, -// -// BulletList { bullet: String }, -// EnumeratedList { enumtype: EnumeratedListType, prefix: String, suffix: String }, -// DefinitionList, FieldList, OptionList, -// -// LineBlock, BlockQuote, -// Admonition, Attention, Hint, Note, -// Caution, Danger, Error, Important, -// Tip, Warning, -// Footnote { backrefs: Vec<ID>, auto: bool }, -// Citation { backrefs: Vec<ID> }, -// SystemMessage { backrefs: Vec<ID>, level: usize, line: usize, type_: NameToken }, -// Figure { align: AlignH, width: usize }, -// Table, //TODO: Table -// -// //body sub elements -// ListItem, -// -// DefinitionListItem, Term, -// Classifier, Definition, -// -// FieldName, FieldBody, -// -// OptionListItem, OptionGroup, Description, Option_, OptionString, -// OptionArgument { delimiter: String }, -// -// Line, Attribution, Label, -// -// Caption, Legend, -// -// //inline elements -// Emphasis, Strong, Literal, -// Reference { name: String, refuri: Url, refid: ID, refname: Vec<NameToken> }, -// FootnoteReference { refid: ID, refname: Vec<NameToken>, auto: bool }, -// CitationReference { refid: ID, refname: Vec<NameToken> }, -// SubstitutionReference { refname: Vec<NameToken> }, -// TitleReference, -// Abbreviation, Acronym, -// Superscript, Subscript, -// Inline, -// Problematic { refid: ID }, -// Generated, Math, -// -// //also have non-inline versions. Inline image is no figure child, inline target has content -// TargetInline { refuri: Url, refid: ID, refname: Vec<NameToken>, anonymous: bool }, -// RawInline { space: FixedSpace, format: Vec<NameToken> }, -// ImageInline { -// align: AlignHV, -// uri: Url, -// alt: String, -// height: Measure, -// width: Measure, -// scale: f64, -// }, -// -// //text element -// TextElement, +// //structual elements +// Section, Topic, Sidebar, +// +// //structural subelements +// Title, Subtitle, Decoration, Docinfo, Transition, +// +// //bibliographic elements +// Author, Authors, Organization, +// Address { space: FixedSpace }, +// Contact, Version, Revision, Status, +// Date, Copyright, Field, +// +// //decoration elements +// Header, Footer, +// +// //simple body elements +// Paragraph, +// LiteralBlock { space: FixedSpace }, +// DoctestBlock { space: FixedSpace }, +// MathBlock, Rubric, +// SubstitutionDefinition { ltrim: bool, rtrim: bool }, +// Comment { space: FixedSpace }, +// Pending, +// Target { refuri: Url, refid: ID, refname: Vec<NameToken>, anonymous: bool }, +// Raw { space: FixedSpace, format: Vec<NameToken> }, +// Image { +// align: AlignHV, +// uri: Url, +// alt: String, +// height: Measure, +// width: Measure, +// scale: f64, +// }, +// +// //compound body elements +// Compound, Container, +// +// BulletList { bullet: String }, +// EnumeratedList { enumtype: EnumeratedListType, prefix: String, suffix: String }, +// DefinitionList, FieldList, OptionList, +// +// LineBlock, BlockQuote, +// Admonition, Attention, Hint, Note, +// Caution, Danger, Error, Important, +// Tip, Warning, +// Footnote { backrefs: Vec<ID>, auto: bool }, +// Citation { backrefs: Vec<ID> }, +// SystemMessage { backrefs: Vec<ID>, level: usize, line: usize, type_: NameToken }, +// Figure { align: AlignH, width: usize }, +// Table, //TODO: Table +// +// //body sub elements +// ListItem, +// +// DefinitionListItem, Term, +// Classifier, Definition, +// +// FieldName, FieldBody, +// +// OptionListItem, OptionGroup, Description, Option_, OptionString, +// OptionArgument { delimiter: String }, +// +// Line, Attribution, Label, +// +// Caption, Legend, +// +// //inline elements +// Emphasis, Strong, Literal, +// Reference { name: String, refuri: Url, refid: ID, refname: Vec<NameToken> }, +// FootnoteReference { refid: ID, refname: Vec<NameToken>, auto: bool }, +// CitationReference { refid: ID, refname: Vec<NameToken> }, +// SubstitutionReference { refname: Vec<NameToken> }, +// TitleReference, +// Abbreviation, Acronym, +// Superscript, Subscript, +// Inline, +// Problematic { refid: ID }, +// Generated, Math, +// +// //also have non-inline versions. Inline image is no figure child, inline target has content +// TargetInline { refuri: Url, refid: ID, refname: Vec<NameToken>, anonymous: bool }, +// RawInline { space: FixedSpace, format: Vec<NameToken> }, +// ImageInline { +// align: AlignHV, +// uri: Url, +// alt: String, +// height: Measure, +// width: Measure, +// scale: f64, +// }, +// +// //text element +// TextElement, // } diff --git a/document_tree/src/elements.rs b/document_tree/src/elements.rs index 1db0a24..4921e1a 100644 --- a/document_tree/src/elements.rs +++ b/document_tree/src/elements.rs @@ -1,288 +1,348 @@ -use std::path::PathBuf; use serde_derive::Serialize; +use std::path::PathBuf; -use crate::attribute_types::{CanBeEmpty,ID,NameToken}; -use crate::extra_attributes::{self,ExtraAttributes}; +use crate::attribute_types::{CanBeEmpty, NameToken, ID}; use crate::element_categories::*; - +use crate::extra_attributes::{self, ExtraAttributes}; //-----------------\\ //Element hierarchy\\ //-----------------\\ pub trait Element { - /// A list containing one or more unique identifier keys - fn ids (& self) -> & Vec<ID>; - fn ids_mut(&mut self) -> &mut Vec<ID>; - /// a list containing the names of an element, typically originating from the element's title or content. - /// Each name in names must be unique; if there are name conflicts (two or more elements want to the same name), - /// the contents will be transferred to the dupnames attribute on the duplicate elements. - /// An element may have at most one of the names or dupnames attributes, but not both. - fn names (& self) -> & Vec<NameToken>; - fn names_mut(&mut self) -> &mut Vec<NameToken>; - fn source (& self) -> & Option<PathBuf>; - fn source_mut(&mut self) -> &mut Option<PathBuf>; - fn classes (& self) -> & Vec<String>; - fn classes_mut(&mut self) -> &mut Vec<String>; + /// A list containing one or more unique identifier keys + fn ids(&self) -> &Vec<ID>; + fn ids_mut(&mut self) -> &mut Vec<ID>; + /// a list containing the names of an element, typically originating from the element's title or content. + /// Each name in names must be unique; if there are name conflicts (two or more elements want to the same name), + /// the contents will be transferred to the dupnames attribute on the duplicate elements. + /// An element may have at most one of the names or dupnames attributes, but not both. + fn names(&self) -> &Vec<NameToken>; + fn names_mut(&mut self) -> &mut Vec<NameToken>; + fn source(&self) -> &Option<PathBuf>; + fn source_mut(&mut self) -> &mut Option<PathBuf>; + fn classes(&self) -> &Vec<String>; + fn classes_mut(&mut self) -> &mut Vec<String>; } -#[derive(Debug,Default,PartialEq,Serialize,Clone)] +#[derive(Debug, Default, PartialEq, Serialize, Clone)] pub struct CommonAttributes { - #[serde(skip_serializing_if = "CanBeEmpty::is_empty")] - ids: Vec<ID>, - #[serde(skip_serializing_if = "CanBeEmpty::is_empty")] - names: Vec<NameToken>, - #[serde(skip_serializing_if = "CanBeEmpty::is_empty")] - source: Option<PathBuf>, - #[serde(skip_serializing_if = "CanBeEmpty::is_empty")] - classes: Vec<String>, - //TODO: dupnames + #[serde(skip_serializing_if = "CanBeEmpty::is_empty")] + ids: Vec<ID>, + #[serde(skip_serializing_if = "CanBeEmpty::is_empty")] + names: Vec<NameToken>, + #[serde(skip_serializing_if = "CanBeEmpty::is_empty")] + source: Option<PathBuf>, + #[serde(skip_serializing_if = "CanBeEmpty::is_empty")] + classes: Vec<String>, + //TODO: dupnames } //----\\ //impl\\ //----\\ -macro_rules! impl_element { ($name:ident) => ( - impl Element for $name { - fn ids (& self) -> & Vec<ID> { & self.common.ids } - fn ids_mut(&mut self) -> &mut Vec<ID> { &mut self.common.ids } - fn names (& self) -> & Vec<NameToken> { & self.common.names } - fn names_mut(&mut self) -> &mut Vec<NameToken> { &mut self.common.names } - fn source (& self) -> & Option<PathBuf> { & self.common.source } - fn source_mut(&mut self) -> &mut Option<PathBuf> { &mut self.common.source } - fn classes (& self) -> & Vec<String> { & self.common.classes } - fn classes_mut(&mut self) -> &mut Vec<String> { &mut self.common.classes } - } -)} +macro_rules! impl_element { + ($name:ident) => { + impl Element for $name { + fn ids(&self) -> &Vec<ID> { + &self.common.ids + } + fn ids_mut(&mut self) -> &mut Vec<ID> { + &mut self.common.ids + } + fn names(&self) -> &Vec<NameToken> { + &self.common.names + } + fn names_mut(&mut self) -> &mut Vec<NameToken> { + &mut self.common.names + } + fn source(&self) -> &Option<PathBuf> { + &self.common.source + } + fn source_mut(&mut self) -> &mut Option<PathBuf> { + &mut self.common.source + } + fn classes(&self) -> &Vec<String> { + &self.common.classes + } + fn classes_mut(&mut self) -> &mut Vec<String> { + &mut self.common.classes + } + } + }; +} -macro_rules! impl_children { ($name:ident, $childtype:ident) => ( - impl HasChildren<$childtype> for $name { - #[allow(clippy::needless_update)] - fn with_children(children: Vec<$childtype>) -> $name { $name { children: children, ..Default::default() } } - fn children (& self) -> & Vec<$childtype> { & self.children } - fn children_mut(&mut self) -> &mut Vec<$childtype> { &mut self.children } - } -)} +macro_rules! impl_children { + ($name:ident, $childtype:ident) => { + impl HasChildren<$childtype> for $name { + #[allow(clippy::needless_update)] + fn with_children(children: Vec<$childtype>) -> $name { + $name { + children, + ..Default::default() + } + } + fn children(&self) -> &Vec<$childtype> { + &self.children + } + fn children_mut(&mut self) -> &mut Vec<$childtype> { + &mut self.children + } + } + }; +} macro_rules! impl_extra { ($name:ident $($more:tt)*) => ( - impl ExtraAttributes<extra_attributes::$name> for $name { - #[allow(clippy::needless_update)] - fn with_extra(extra: extra_attributes::$name) -> $name { $name { common: Default::default(), extra: extra $($more)* } } - fn extra (& self) -> & extra_attributes::$name { & self.extra } - fn extra_mut(&mut self) -> &mut extra_attributes::$name { &mut self.extra } - } + impl ExtraAttributes<extra_attributes::$name> for $name { + #[allow(clippy::needless_update)] + fn with_extra(extra: extra_attributes::$name) -> $name { $name { common: Default::default(), extra $($more)* } } + fn extra (& self) -> & extra_attributes::$name { & self.extra } + fn extra_mut(&mut self) -> &mut extra_attributes::$name { &mut self.extra } + } )} trait HasExtraAndChildren<C, A> { - fn with_extra_and_children(extra: A, children: Vec<C>) -> Self; + fn with_extra_and_children(extra: A, children: Vec<C>) -> Self; } -impl<T, C, A> HasExtraAndChildren<C, A> for T where T: HasChildren<C> + ExtraAttributes<A> { - #[allow(clippy::needless_update)] - fn with_extra_and_children(extra: A, mut children: Vec<C>) -> Self { - let mut r = Self::with_extra(extra); - r.children_mut().extend(children.drain(..)); - r - } +impl<T, C, A> HasExtraAndChildren<C, A> for T +where + T: HasChildren<C> + ExtraAttributes<A>, +{ + #[allow(clippy::needless_update)] + fn with_extra_and_children(extra: A, mut children: Vec<C>) -> Self { + let mut r = Self::with_extra(extra); + r.children_mut().append(&mut children); + r + } } macro_rules! impl_new {( - $(#[$attr:meta])* - pub struct $name:ident { $( - $(#[$fattr:meta])* - $field:ident : $typ:path - ),* $(,)* } + $(#[$attr:meta])* + pub struct $name:ident { $( + $(#[$fattr:meta])* + $field:ident : $typ:path + ),* $(,)* } ) => ( - $(#[$attr])* - #[derive(Debug,PartialEq,Serialize,Clone)] - pub struct $name { $( - $(#[$fattr])* $field: $typ, - )* } - impl $name { - pub fn new( $( $field: $typ, )* ) -> $name { $name { $( $field: $field, )* } } - } + $(#[$attr])* + #[derive(Debug,PartialEq,Serialize,Clone)] + pub struct $name { $( + $(#[$fattr])* $field: $typ, + )* } + impl $name { + pub fn new( $( $field: $typ, )* ) -> $name { $name { $( $field, )* } } + } )} macro_rules! impl_elem { - ($name:ident) => { - impl_new!(#[derive(Default)] pub struct $name { - #[serde(flatten)] common: CommonAttributes, - }); - impl_element!($name); - }; - ($name:ident; +) => { - impl_new!(#[derive(Default)] pub struct $name { - #[serde(flatten)] common: CommonAttributes, - #[serde(flatten)] extra: extra_attributes::$name, - }); - impl_element!($name); impl_extra!($name, ..Default::default()); - }; - ($name:ident; *) => { //same as above with no default - impl_new!(pub struct $name { - #[serde(flatten)] common: CommonAttributes, - #[serde(flatten)] extra: extra_attributes::$name - }); - impl_element!($name); impl_extra!($name); - }; - ($name:ident, $childtype:ident) => { - impl_new!(#[derive(Default)] pub struct $name { - #[serde(flatten)] common: CommonAttributes, - children: Vec<$childtype>, - }); - impl_element!($name); impl_children!($name, $childtype); - }; - ($name:ident, $childtype:ident; +) => { - impl_new!(#[derive(Default)] pub struct $name { - #[serde(flatten)] common: CommonAttributes, - #[serde(flatten)] extra: extra_attributes::$name, - children: Vec<$childtype>, - }); - impl_element!($name); impl_extra!($name, ..Default::default()); impl_children!($name, $childtype); - }; + ($name:ident) => { + impl_new!( + #[derive(Default)] + pub struct $name { + #[serde(flatten)] + common: CommonAttributes, + } + ); + impl_element!($name); + }; + ($name:ident; +) => { + impl_new!( + #[derive(Default)] + pub struct $name { + #[serde(flatten)] + common: CommonAttributes, + #[serde(flatten)] + extra: extra_attributes::$name, + } + ); + impl_element!($name); + impl_extra!($name, ..Default::default()); + }; + ($name:ident; *) => { + //same as above with no default + impl_new!( + pub struct $name { + #[serde(flatten)] + common: CommonAttributes, + #[serde(flatten)] + extra: extra_attributes::$name, + } + ); + impl_element!($name); + impl_extra!($name); + }; + ($name:ident, $childtype:ident) => { + impl_new!( + #[derive(Default)] + pub struct $name { + #[serde(flatten)] + common: CommonAttributes, + children: Vec<$childtype>, + } + ); + impl_element!($name); + impl_children!($name, $childtype); + }; + ($name:ident, $childtype:ident; +) => { + impl_new!( + #[derive(Default)] + pub struct $name { + #[serde(flatten)] + common: CommonAttributes, + #[serde(flatten)] + extra: extra_attributes::$name, + children: Vec<$childtype>, + } + ); + impl_element!($name); + impl_extra!($name, ..Default::default()); + impl_children!($name, $childtype); + }; } macro_rules! impl_elems { ( $( ($($args:tt)*) )* ) => ( - $( impl_elem!($($args)*); )* + $( impl_elem!($($args)*); )* )} - -#[derive(Default,Debug,Serialize)] -pub struct Document { children: Vec<StructuralSubElement> } +#[derive(Default, Debug, Serialize)] +pub struct Document { + children: Vec<StructuralSubElement>, +} impl_children!(Document, StructuralSubElement); impl_elems!( - //structual elements - (Section, StructuralSubElement) - (Topic, SubTopic) - (Sidebar, SubSidebar) - - //structural subelements - (Title, TextOrInlineElement) - (Subtitle, TextOrInlineElement) - (Decoration, DecorationElement) - (Docinfo, BibliographicElement) - (Transition) - - //bibliographic elements - (Author, TextOrInlineElement) - (Authors, AuthorInfo) - (Organization, TextOrInlineElement) - (Address, TextOrInlineElement; +) - (Contact, TextOrInlineElement) - (Version, TextOrInlineElement) - (Revision, TextOrInlineElement) - (Status, TextOrInlineElement) - (Date, TextOrInlineElement) - (Copyright, TextOrInlineElement) - (Field, SubField) - - //decoration elements - (Header, BodyElement) - (Footer, BodyElement) - - //simple body elements - (Paragraph, TextOrInlineElement) - (LiteralBlock, TextOrInlineElement; +) - (DoctestBlock, TextOrInlineElement; +) - (MathBlock, String) - (Rubric, TextOrInlineElement) - (SubstitutionDefinition, TextOrInlineElement; +) - (Comment, TextOrInlineElement; +) - (Pending) - (Target; +) - (Raw, String; +) - (Image; *) - - //compound body elements - (Compound, BodyElement) - (Container, BodyElement) - - (BulletList, ListItem; +) - (EnumeratedList, ListItem; +) - (DefinitionList, DefinitionListItem) - (FieldList, Field) - (OptionList, OptionListItem) - - (LineBlock, SubLineBlock) - (BlockQuote, SubBlockQuote) - (Admonition, SubTopic) - (Attention, BodyElement) - (Hint, BodyElement) - (Note, BodyElement) - (Caution, BodyElement) - (Danger, BodyElement) - (Error, BodyElement) - (Important, BodyElement) - (Tip, BodyElement) - (Warning, BodyElement) - (Footnote, SubFootnote; +) - (Citation, SubFootnote; +) - (SystemMessage, BodyElement; +) - (Figure, SubFigure; +) - (Table, SubTable; +) - - //table elements - (TableGroup, SubTableGroup; +) - (TableHead, TableRow; +) - (TableBody, TableRow; +) - (TableRow, TableEntry; +) - (TableEntry, BodyElement; +) - (TableColspec; +) - - //body sub elements - (ListItem, BodyElement) - - (DefinitionListItem, SubDLItem) - (Term, TextOrInlineElement) - (Classifier, TextOrInlineElement) - (Definition, BodyElement) - - (FieldName, TextOrInlineElement) - (FieldBody, BodyElement) - - (OptionListItem, SubOptionListItem) - (OptionGroup, Option_) - (Description, BodyElement) - (Option_, SubOption) - (OptionString, String) - (OptionArgument, String; +) - - (Line, TextOrInlineElement) - (Attribution, TextOrInlineElement) - (Label, TextOrInlineElement) - - (Caption, TextOrInlineElement) - (Legend, BodyElement) - - //inline elements - (Emphasis, TextOrInlineElement) - (Literal, String) - (Reference, TextOrInlineElement; +) - (Strong, TextOrInlineElement) - (FootnoteReference, TextOrInlineElement; +) - (CitationReference, TextOrInlineElement; +) - (SubstitutionReference, TextOrInlineElement; +) - (TitleReference, TextOrInlineElement) - (Abbreviation, TextOrInlineElement) - (Acronym, TextOrInlineElement) - (Superscript, TextOrInlineElement) - (Subscript, TextOrInlineElement) - (Inline, TextOrInlineElement) - (Problematic, TextOrInlineElement; +) - (Generated, TextOrInlineElement) - (Math, String) - - //also have non-inline versions. Inline image is no figure child, inline target has content - (TargetInline, String; +) - (RawInline, String; +) - (ImageInline; *) - - //text element = String + //structual elements + (Section, StructuralSubElement) + (Topic, SubTopic) + (Sidebar, SubSidebar) + + //structural subelements + (Title, TextOrInlineElement) + (Subtitle, TextOrInlineElement) + (Decoration, DecorationElement) + (Docinfo, BibliographicElement) + (Transition) + + //bibliographic elements + (Author, TextOrInlineElement) + (Authors, AuthorInfo) + (Organization, TextOrInlineElement) + (Address, TextOrInlineElement; +) + (Contact, TextOrInlineElement) + (Version, TextOrInlineElement) + (Revision, TextOrInlineElement) + (Status, TextOrInlineElement) + (Date, TextOrInlineElement) + (Copyright, TextOrInlineElement) + (Field, SubField) + + //decoration elements + (Header, BodyElement) + (Footer, BodyElement) + + //simple body elements + (Paragraph, TextOrInlineElement) + (LiteralBlock, TextOrInlineElement; +) + (DoctestBlock, TextOrInlineElement; +) + (MathBlock, String) + (Rubric, TextOrInlineElement) + (SubstitutionDefinition, TextOrInlineElement; +) + (Comment, TextOrInlineElement; +) + (Pending) + (Target; +) + (Raw, String; +) + (Image; *) + + //compound body elements + (Compound, BodyElement) + (Container, BodyElement) + + (BulletList, ListItem; +) + (EnumeratedList, ListItem; +) + (DefinitionList, DefinitionListItem) + (FieldList, Field) + (OptionList, OptionListItem) + + (LineBlock, SubLineBlock) + (BlockQuote, SubBlockQuote) + (Admonition, SubTopic) + (Attention, BodyElement) + (Hint, BodyElement) + (Note, BodyElement) + (Caution, BodyElement) + (Danger, BodyElement) + (Error, BodyElement) + (Important, BodyElement) + (Tip, BodyElement) + (Warning, BodyElement) + (Footnote, SubFootnote; +) + (Citation, SubFootnote; +) + (SystemMessage, BodyElement; +) + (Figure, SubFigure; +) + (Table, SubTable; +) + + //table elements + (TableGroup, SubTableGroup; +) + (TableHead, TableRow; +) + (TableBody, TableRow; +) + (TableRow, TableEntry; +) + (TableEntry, BodyElement; +) + (TableColspec; +) + + //body sub elements + (ListItem, BodyElement) + + (DefinitionListItem, SubDLItem) + (Term, TextOrInlineElement) + (Classifier, TextOrInlineElement) + (Definition, BodyElement) + + (FieldName, TextOrInlineElement) + (FieldBody, BodyElement) + + (OptionListItem, SubOptionListItem) + (OptionGroup, Option_) + (Description, BodyElement) + (Option_, SubOption) + (OptionString, String) + (OptionArgument, String; +) + + (Line, TextOrInlineElement) + (Attribution, TextOrInlineElement) + (Label, TextOrInlineElement) + + (Caption, TextOrInlineElement) + (Legend, BodyElement) + + //inline elements + (Emphasis, TextOrInlineElement) + (Literal, String) + (Reference, TextOrInlineElement; +) + (Strong, TextOrInlineElement) + (FootnoteReference, TextOrInlineElement; +) + (CitationReference, TextOrInlineElement; +) + (SubstitutionReference, TextOrInlineElement; +) + (TitleReference, TextOrInlineElement) + (Abbreviation, TextOrInlineElement) + (Acronym, TextOrInlineElement) + (Superscript, TextOrInlineElement) + (Subscript, TextOrInlineElement) + (Inline, TextOrInlineElement) + (Problematic, TextOrInlineElement; +) + (Generated, TextOrInlineElement) + (Math, String) + + //also have non-inline versions. Inline image is no figure child, inline target has content + (TargetInline, String; +) + (RawInline, String; +) + (ImageInline; *) + + //text element = String ); impl<'a> From<&'a str> for TextOrInlineElement { - fn from(s: &'a str) -> Self { - s.to_owned().into() - } + fn from(s: &'a str) -> Self { + s.to_owned().into() + } } diff --git a/document_tree/src/extra_attributes.rs b/document_tree/src/extra_attributes.rs index 45fcf32..9dcabd7 100644 --- a/document_tree/src/extra_attributes.rs +++ b/document_tree/src/extra_attributes.rs @@ -1,62 +1,60 @@ use serde_derive::Serialize; -use crate::url::Url; use crate::attribute_types::{ - CanBeEmpty, - FixedSpace, - ID,NameToken, - AlignHV,AlignH,AlignV, - TableAlignH,TableBorder,TableGroupCols, - Measure, - EnumeratedListType, + AlignH, AlignHV, AlignV, CanBeEmpty, EnumeratedListType, FixedSpace, Measure, NameToken, + TableAlignH, TableBorder, TableGroupCols, ID, }; +use crate::url::Url; pub trait ExtraAttributes<A> { - fn with_extra(extra: A) -> Self; - fn extra (& self) -> & A; - fn extra_mut(&mut self) -> &mut A; + fn with_extra(extra: A) -> Self; + fn extra(&self) -> &A; + fn extra_mut(&mut self) -> &mut A; } macro_rules! impl_extra { - ( $name:ident { $( $(#[$pattr:meta])* $param:ident : $type:ty ),* $(,)* } ) => ( - impl_extra!( - #[derive(Default,Debug,PartialEq,Serialize,Clone)] - $name { $( $(#[$pattr])* $param : $type, )* } - ); - ); - ( $(#[$attr:meta])+ $name:ident { $( $(#[$pattr:meta])* $param:ident : $type:ty ),* $(,)* } ) => ( - $(#[$attr])+ - pub struct $name { $( - $(#[$pattr])* - #[serde(skip_serializing_if = "CanBeEmpty::is_empty")] - pub $param : $type, - )* } - ); + ( $name:ident { $( $(#[$pattr:meta])* $param:ident : $type:ty ),* $(,)* } ) => ( + impl_extra!( + #[derive(Default,Debug,PartialEq,Serialize,Clone)] + $name { $( $(#[$pattr])* $param : $type, )* } + ); + ); + ( $(#[$attr:meta])+ $name:ident { $( $(#[$pattr:meta])* $param:ident : $type:ty ),* $(,)* } ) => ( + $(#[$attr])+ + pub struct $name { $( + $(#[$pattr])* + #[serde(skip_serializing_if = "CanBeEmpty::is_empty")] + pub $param : $type, + )* } + ); } impl_extra!(Address { space: FixedSpace }); impl_extra!(LiteralBlock { space: FixedSpace }); impl_extra!(DoctestBlock { space: FixedSpace }); -impl_extra!(SubstitutionDefinition { ltrim: bool, rtrim: bool }); +impl_extra!(SubstitutionDefinition { + ltrim: bool, + rtrim: bool +}); impl_extra!(Comment { space: FixedSpace }); impl_extra!(Target { - /// External reference to a URI/URL - refuri: Option<Url>, - /// References to ids attributes in other elements - refid: Option<ID>, - /// Internal reference to the names attribute of another element. May resolve to either an internal or external reference. - refname: Vec<NameToken>, - anonymous: bool, + /// External reference to a URI/URL + refuri: Option<Url>, + /// References to ids attributes in other elements + refid: Option<ID>, + /// Internal reference to the names attribute of another element. May resolve to either an internal or external reference. + refname: Vec<NameToken>, + anonymous: bool, }); impl_extra!(Raw { space: FixedSpace, format: Vec<NameToken> }); impl_extra!(#[derive(Debug,PartialEq,Serialize,Clone)] Image { - uri: Url, - align: Option<AlignHV>, - alt: Option<String>, - height: Option<Measure>, - width: Option<Measure>, - scale: Option<u8>, - target: Option<Url>, // Not part of the DTD but a valid argument + uri: Url, + align: Option<AlignHV>, + alt: Option<String>, + height: Option<Measure>, + width: Option<Measure>, + scale: Option<u8>, + target: Option<Url>, // Not part of the DTD but a valid argument }); //bools usually are XML yesorno. “auto” however either exists and is set to something random like “1” or doesn’t exist @@ -81,13 +79,13 @@ impl_extra!(TableColspec { colnum: Option<usize>, colname: Option<NameToken>, co impl_extra!(OptionArgument { delimiter: Option<String> }); impl_extra!(Reference { - name: Option<NameToken>, //TODO: is CDATA in the DTD, so maybe no nametoken? - /// External reference to a URI/URL - refuri: Option<Url>, - /// References to ids attributes in other elements - refid: Option<ID>, - /// Internal reference to the names attribute of another element - refname: Vec<NameToken>, + name: Option<NameToken>, //TODO: is CDATA in the DTD, so maybe no nametoken? + /// External reference to a URI/URL + refuri: Option<Url>, + /// References to ids attributes in other elements + refid: Option<ID>, + /// Internal reference to the names attribute of another element + refname: Vec<NameToken>, }); impl_extra!(FootnoteReference { refid: Option<ID>, refname: Vec<NameToken>, auto: bool }); impl_extra!(CitationReference { refid: Option<ID>, refname: Vec<NameToken> }); @@ -96,25 +94,27 @@ impl_extra!(Problematic { refid: Option<ID> }); //also have non-inline versions. Inline image is no figure child, inline target has content impl_extra!(TargetInline { - /// External reference to a URI/URL - refuri: Option<Url>, - /// References to ids attributes in other elements - refid: Option<ID>, - /// Internal reference to the names attribute of another element. May resolve to either an internal or external reference. - refname: Vec<NameToken>, - anonymous: bool, + /// External reference to a URI/URL + refuri: Option<Url>, + /// References to ids attributes in other elements + refid: Option<ID>, + /// Internal reference to the names attribute of another element. May resolve to either an internal or external reference. + refname: Vec<NameToken>, + anonymous: bool, }); impl_extra!(RawInline { space: FixedSpace, format: Vec<NameToken> }); pub type ImageInline = Image; impl Image { - pub fn new(uri: Url) -> Image { Image { - uri, - align: None, - alt: None, - height: None, - width: None, - scale: None, - target: None, - } } + pub fn new(uri: Url) -> Image { + Image { + uri, + align: None, + alt: None, + height: None, + width: None, + scale: None, + target: None, + } + } } diff --git a/document_tree/src/lib.rs b/document_tree/src/lib.rs index 9154725..4b34ea9 100644 --- a/document_tree/src/lib.rs +++ b/document_tree/src/lib.rs @@ -1,50 +1,52 @@ -#![recursion_limit="256"] +#![recursion_limit = "256"] -///http://docutils.sourceforge.net/docs/ref/doctree.html -///serves as AST +/// See [doctree][] reference. +/// Serves as AST. +/// +/// [doctree]: http://docutils.sourceforge.net/docs/ref/doctree.html #[macro_use] mod macro_util; -pub mod url; -pub mod elements; +pub mod attribute_types; pub mod element_categories; +pub mod elements; pub mod extra_attributes; -pub mod attribute_types; +pub mod url; +pub use self::element_categories::HasChildren; pub use self::elements::*; //Element,CommonAttributes,HasExtraAndChildren pub use self::extra_attributes::ExtraAttributes; -pub use self::element_categories::HasChildren; #[cfg(test)] mod tests { - use super::*; - use std::default::Default; - - #[test] - fn imperative() { - let mut doc = Document::default(); - let mut title = Title::default(); - let url = "https://example.com/image.jpg".parse().unwrap(); - let image = ImageInline::with_extra(extra_attributes::ImageInline::new(url)); - title.append_child("Hi"); - title.append_child(image); - doc.append_child(title); - - println!("{:?}", doc); - } - - #[test] - fn descriptive() { - let doc = Document::with_children(vec![ - Title::with_children(vec![ - "Hi".into(), - ImageInline::with_extra(extra_attributes::ImageInline::new( - "https://example.com/image.jpg".parse().unwrap() - )).into(), - ]).into() - ]); - - println!("{:?}", doc); - } + use super::*; + use std::default::Default; + + #[test] + fn imperative() { + let mut doc = Document::default(); + let mut title = Title::default(); + let url = "https://example.com/image.jpg".parse().unwrap(); + let image = ImageInline::with_extra(extra_attributes::ImageInline::new(url)); + title.append_child("Hi"); + title.append_child(image); + doc.append_child(title); + + println!("{:?}", doc); + } + + #[test] + fn descriptive() { + let doc = Document::with_children(vec![Title::with_children(vec![ + "Hi".into(), + ImageInline::with_extra(extra_attributes::ImageInline::new( + "https://example.com/image.jpg".parse().unwrap(), + )) + .into(), + ]) + .into()]); + + println!("{:?}", doc); + } } diff --git a/document_tree/src/macro_util.rs b/document_tree/src/macro_util.rs index dcf3725..e370011 100644 --- a/document_tree/src/macro_util.rs +++ b/document_tree/src/macro_util.rs @@ -1,42 +1,41 @@ macro_rules! cartesian_impl { - ($out:tt [] $b:tt $init_b:tt $submacro:tt) => { - $submacro!{$out} - }; - ($out:tt [$a:tt, $($at:tt)*] [] $init_b:tt $submacro:tt) => { - cartesian_impl!{$out [$($at)*] $init_b $init_b $submacro} - }; - ([$($out:tt)*] [$a:tt, $($at:tt)*] [$b:tt, $($bt:tt)*] $init_b:tt $submacro:tt) => { - cartesian_impl!{[$($out)* ($a, $b),] [$a, $($at)*] [$($bt)*] $init_b $submacro} - }; + ($out:tt [] $b:tt $init_b:tt $submacro:tt) => { + $submacro!{$out} + }; + ($out:tt [$a:tt, $($at:tt)*] [] $init_b:tt $submacro:tt) => { + cartesian_impl!{$out [$($at)*] $init_b $init_b $submacro} + }; + ([$($out:tt)*] [$a:tt, $($at:tt)*] [$b:tt, $($bt:tt)*] $init_b:tt $submacro:tt) => { + cartesian_impl!{[$($out)* ($a, $b),] [$a, $($at)*] [$($bt)*] $init_b $submacro} + }; } macro_rules! cartesian { - ( $submacro:tt, [$($a:tt)*], [$($b:tt)*]) => { - cartesian_impl!{[] [$($a)*,] [$($b)*,] [$($b)*,] $submacro} - }; + ( $submacro:tt, [$($a:tt)*], [$($b:tt)*]) => { + cartesian_impl!{[] [$($a)*,] [$($b)*,] [$($b)*,] $submacro} + }; } - #[cfg(test)] mod tests { - macro_rules! print_cartesian { - ( [ $(($a1:tt, $a2:tt)),* , ] ) => { - fn test_f(x:i64, y:i64) -> Result<(i64, i64), ()> { - match (x, y) { - $( - ($a1, $a2) => { Ok(($a1, $a2)) } - )* - _ => { Err(()) } - } - } - }; - } + macro_rules! print_cartesian { + ( [ $(($a1:tt, $a2:tt)),* , ] ) => { + fn test_f(x:i64, y:i64) -> Result<(i64, i64), ()> { + match (x, y) { + $( + ($a1, $a2) => { Ok(($a1, $a2)) } + )* + _ => { Err(()) } + } + } + }; + } - #[test] - fn print_cartesian() { - cartesian!(print_cartesian, [1, 2, 3], [4, 5, 6]); - assert_eq!(test_f(1, 4), Ok((1, 4))); - assert_eq!(test_f(1, 3), Err(())); - assert_eq!(test_f(3, 5), Ok((3, 5))); - } + #[test] + fn print_cartesian() { + cartesian!(print_cartesian, [1, 2, 3], [4, 5, 6]); + assert_eq!(test_f(1, 4), Ok((1, 4))); + assert_eq!(test_f(1, 3), Err(())); + assert_eq!(test_f(3, 5), Ok((3, 5))); + } } diff --git a/document_tree/src/url.rs b/document_tree/src/url.rs index 31a0536..be320e9 100644 --- a/document_tree/src/url.rs +++ b/document_tree/src/url.rs @@ -1,78 +1,74 @@ use std::fmt; use std::str::FromStr; -use url::{self,ParseError}; use serde_derive::Serialize; - +use url::{self, ParseError}; fn starts_with_scheme(input: &str) -> bool { - let scheme = input.split(':').next().unwrap(); - if scheme == input || scheme.is_empty() { - return false; - } - let mut chars = input.chars(); - // First character. - if !chars.next().unwrap().is_ascii_alphabetic() { - return false; - } - for ch in chars { - if !ch.is_ascii_alphanumeric() && ch != '+' && ch != '-' && ch != '.' { - return false; - } - } - true + let scheme = input.split(':').next().unwrap(); + if scheme == input || scheme.is_empty() { + return false; + } + let mut chars = input.chars(); + // First character. + if !chars.next().unwrap().is_ascii_alphabetic() { + return false; + } + for ch in chars { + if !ch.is_ascii_alphanumeric() && ch != '+' && ch != '-' && ch != '.' { + return false; + } + } + true } /// The string representation of a URL, either absolute or relative, that has /// been verified as a valid URL on construction. -#[derive(Debug,PartialEq,Serialize,Clone)] +#[derive(Debug, PartialEq, Serialize, Clone)] #[serde(transparent)] pub struct Url(String); impl Url { - pub fn parse_absolute(input: &str) -> Result<Self, ParseError> { - Ok(url::Url::parse(input)?.into()) - } - pub fn parse_relative(input: &str) -> Result<Self, ParseError> { - // We're assuming that any scheme through which RsT documents are being - // accessed is a hierarchical scheme, and so we can parse relative to a - // random hierarchical URL. - if input.starts_with('/') || !starts_with_scheme(input) { - // Continue only if the parse succeeded, disregarding its result. - let random_base_url = url::Url::parse("https://a/b").unwrap(); - url::Url::options() - .base_url(Some(&random_base_url)) - .parse(input)?; - Ok(Url(input.into())) - } else { - // If this is a URL at all, it's an absolute one. - // There's no appropriate variant of url::ParseError really. - Err(ParseError::SetHostOnCannotBeABaseUrl) - } - } - pub fn as_str(&self) -> &str { - self.0.as_str() - } + pub fn parse_absolute(input: &str) -> Result<Self, ParseError> { + Ok(url::Url::parse(input)?.into()) + } + pub fn parse_relative(input: &str) -> Result<Self, ParseError> { + // We're assuming that any scheme through which RsT documents are being + // accessed is a hierarchical scheme, and so we can parse relative to a + // random hierarchical URL. + if input.starts_with('/') || !starts_with_scheme(input) { + // Continue only if the parse succeeded, disregarding its result. + let random_base_url = url::Url::parse("https://a/b").unwrap(); + url::Url::options() + .base_url(Some(&random_base_url)) + .parse(input)?; + Ok(Url(input.into())) + } else { + // If this is a URL at all, it's an absolute one. + // There's no appropriate variant of url::ParseError really. + Err(ParseError::SetHostOnCannotBeABaseUrl) + } + } + pub fn as_str(&self) -> &str { + self.0.as_str() + } } impl From<url::Url> for Url { - fn from(url: url::Url) -> Self { - Url(url.into_string()) - } + fn from(url: url::Url) -> Self { + Url(url.into()) + } } - impl fmt::Display for Url { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.as_str()) - } + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.as_str()) + } } - impl FromStr for Url { - type Err = ParseError; - fn from_str(input: &str) -> Result<Self, Self::Err> { - Url::parse_absolute(input) - .or_else(|_| Url::parse_relative(input)) - } + type Err = ParseError; + fn from_str(input: &str) -> Result<Self, Self::Err> { + Url::parse_absolute(input).or_else(|_| Url::parse_relative(input)) + } } diff --git a/justfile b/justfile new file mode 100644 index 0000000..43bf768 --- /dev/null +++ b/justfile @@ -0,0 +1,27 @@ +# just manual: https://github.com/casey/just/#readme + +_default: + @just --list + +watch: + cargo watch -s 'just doc' -s 'just fmt' + +# Build package +build: + cargo hack --feature-powerset build --verbose + +# Runs clippy on the sources +check: + cargo hack --feature-powerset clippy --locked -- -D warnings + +# Runs unit tests +test: + cargo hack --feature-powerset --skip=extension-module test --locked + +# Build documentation +doc: + RUSTDOCFLAGS="-Dwarnings -Z unstable-options --enable-index-page" cargo +nightly doc --all-features + +# Format code +fmt *args: + cargo fmt {{args}} diff --git a/parser/Cargo.toml b/parser/Cargo.toml index 18e38ae..721d6da 100644 --- a/parser/Cargo.toml +++ b/parser/Cargo.toml @@ -1,6 +1,6 @@ [package] name = 'rst_parser' -version = '0.3.2' +version = '0.4.0' authors = ['Philipp A. <flying-sheep@web.de>'] edition = '2018' description = 'a reStructuredText parser' @@ -12,7 +12,7 @@ homepage = 'https://github.com/flying-sheep/rust-rst' repository = 'https://github.com/flying-sheep/rust-rst' [dependencies] -document_tree = { path = '../document_tree', version = '0.3.0' } +document_tree = { path = '../document_tree', version = '0.4.0' } pest = '2.1.2' pest_derive = '2.1.0' diff --git a/parser/src/conversion.rs b/parser/src/conversion.rs index de5f091..4bd5a8c 100644 --- a/parser/src/conversion.rs +++ b/parser/src/conversion.rs @@ -7,90 +7,95 @@ use failure::Error; use pest::iterators::Pairs; use document_tree::{ - Element,HasChildren, - elements as e, - element_categories as c, - attribute_types as at, + attribute_types as at, element_categories as c, elements as e, Element, HasChildren, }; use crate::pest_rst::Rule; - fn ssubel_to_section_unchecked_mut(ssubel: &mut c::StructuralSubElement) -> &mut e::Section { - match ssubel { - c::StructuralSubElement::SubStructure(ref mut b) => match **b { - c::SubStructure::Section(ref mut s) => s, - _ => unreachable!(), - }, - _ => unreachable!(), - } + match ssubel { + c::StructuralSubElement::SubStructure(ref mut b) => match **b { + c::SubStructure::Section(ref mut s) => s, + _ => unreachable!(), + }, + _ => unreachable!(), + } } - -fn get_level<'tl>(toplevel: &'tl mut Vec<c::StructuralSubElement>, section_idxs: &[Option<usize>]) -> &'tl mut Vec<c::StructuralSubElement> { - let mut level = toplevel; - for maybe_i in section_idxs { - if let Some(i) = *maybe_i { - level = ssubel_to_section_unchecked_mut(&mut level[i]).children_mut(); - } - } - level +fn get_level<'tl>( + toplevel: &'tl mut Vec<c::StructuralSubElement>, + section_idxs: &[Option<usize>], +) -> &'tl mut Vec<c::StructuralSubElement> { + let mut level = toplevel; + for maybe_i in section_idxs { + if let Some(i) = *maybe_i { + level = ssubel_to_section_unchecked_mut(&mut level[i]).children_mut(); + } + } + level } - pub fn convert_document(pairs: Pairs<Rule>) -> Result<e::Document, Error> { - use self::block::TitleOrSsubel::*; - - let mut toplevel: Vec<c::StructuralSubElement> = vec![]; - // The kinds of section titles encountered. - // `section_idx[x]` has the kind `kinds[x]`, but `kinds` can be longer - let mut kinds: Vec<block::TitleKind> = vec![]; - // Recursive indices into the tree, pointing at the active sections. - // `None`s indicate skipped section levels: - // toplevel[section_idxs.flatten()[0]].children[section_idxs.flatten()[1]]... - let mut section_idxs: Vec<Option<usize>> = vec![]; - - for pair in pairs { - if let Some(ssubel) = block::convert_ssubel(pair)? { match ssubel { - Title(title, kind) => { - match kinds.iter().position(|k| k == &kind) { - // Idx points to the level we want to add, - // so idx-1 needs to be the last valid index. - Some(idx) => { - // If idx < len: Remove found section and all below - section_idxs.truncate(idx); - // If idx > len: Add None for skipped levels - // TODO: test skipped levels - while section_idxs.len() < idx { section_idxs.push(None) } - }, - None => kinds.push(kind), - } - let super_level = get_level(&mut toplevel, §ion_idxs); - let slug = title.names().iter().next().map(|at::NameToken(name)| at::ID(name.to_owned())); - let mut section = e::Section::with_children(vec![title.into()]); - section.ids_mut().extend(slug.into_iter()); - super_level.push(section.into()); - section_idxs.push(Some(super_level.len() - 1)); - }, - Ssubel(elem) => get_level(&mut toplevel, §ion_idxs).push(elem), - }} - } - Ok(e::Document::with_children(toplevel)) + use self::block::TitleOrSsubel::*; + + let mut toplevel: Vec<c::StructuralSubElement> = vec![]; + // The kinds of section titles encountered. + // `section_idx[x]` has the kind `kinds[x]`, but `kinds` can be longer + let mut kinds: Vec<block::TitleKind> = vec![]; + // Recursive indices into the tree, pointing at the active sections. + // `None`s indicate skipped section levels: + // toplevel[section_idxs.flatten()[0]].children[section_idxs.flatten()[1]]... + let mut section_idxs: Vec<Option<usize>> = vec![]; + + for pair in pairs { + if let Some(ssubel) = block::convert_ssubel(pair)? { + match ssubel { + Title(title, kind) => { + match kinds.iter().position(|k| k == &kind) { + // Idx points to the level we want to add, + // so idx-1 needs to be the last valid index. + Some(idx) => { + // If idx < len: Remove found section and all below + section_idxs.truncate(idx); + // If idx > len: Add None for skipped levels + // TODO: test skipped levels + while section_idxs.len() < idx { + section_idxs.push(None) + } + } + None => kinds.push(kind), + } + let super_level = get_level(&mut toplevel, §ion_idxs); + let slug = title + .names() + .iter() + .next() + .map(|at::NameToken(name)| at::ID(name.to_owned())); + let mut section = e::Section::with_children(vec![title.into()]); + section.ids_mut().extend(slug.into_iter()); + super_level.push(section.into()); + section_idxs.push(Some(super_level.len() - 1)); + } + Ssubel(elem) => get_level(&mut toplevel, §ion_idxs).push(elem), + } + } + } + Ok(e::Document::with_children(toplevel)) } /// Normalizes a name in terms of whitespace. Equivalent to docutils's /// `docutils.nodes.whitespace_normalize_name`. pub fn whitespace_normalize_name(name: &str) -> String { - // Python's string.split() defines whitespace differently than Rust does. - let split_iter = name.split( - |ch: char| ch.is_whitespace() || (ch >= '\x1C' && ch <= '\x1F') - ).filter(|split| !split.is_empty()); - let mut ret = String::new(); - for split in split_iter { - if !ret.is_empty() { - ret.push(' '); - } - ret.push_str(split); - } - ret + // Python's string.split() defines whitespace differently than Rust does. + let split_iter = name + .split(|ch: char| ch.is_whitespace() || ('\x1C'..='\x1F').contains(&ch)) + .filter(|split| !split.is_empty()); + let mut ret = String::new(); + for split in split_iter { + if !ret.is_empty() { + ret.push(' '); + } + ret.push_str(split); + } + ret } diff --git a/parser/src/conversion/block.rs b/parser/src/conversion/block.rs index 626bc20..97f0e23 100644 --- a/parser/src/conversion/block.rs +++ b/parser/src/conversion/block.rs @@ -1,256 +1,286 @@ -use failure::{Error,bail}; +use failure::{bail, Error}; use pest::iterators::Pair; use document_tree::{ - Element,HasChildren,ExtraAttributes, - elements as e, - element_categories as c, - extra_attributes as a, - attribute_types as at + attribute_types as at, element_categories as c, elements as e, extra_attributes as a, Element, + ExtraAttributes, HasChildren, }; -use crate::{ - pest_rst::Rule, - pair_ext_parse::PairExt, -}; -use super::{whitespace_normalize_name, inline::convert_inlines}; - +use super::{inline::convert_inlines, whitespace_normalize_name}; +use crate::{pair_ext_parse::PairExt, pest_rst::Rule}; #[derive(PartialEq)] -pub(super) enum TitleKind { Double(char), Single(char) } +pub(super) enum TitleKind { + Double(char), + Single(char), +} pub(super) enum TitleOrSsubel { - Title(e::Title, TitleKind), - Ssubel(c::StructuralSubElement), + Title(e::Title, TitleKind), + Ssubel(c::StructuralSubElement), } - pub(super) fn convert_ssubel(pair: Pair<Rule>) -> Result<Option<TitleOrSsubel>, Error> { - use self::TitleOrSsubel::*; - Ok(Some(match pair.as_rule() { - Rule::title => { let (t, k) = convert_title(pair)?; Title(t, k) }, - //TODO: subtitle, decoration, docinfo - Rule::EOI => return Ok(None), - _ => Ssubel(convert_substructure(pair)?.into()), - })) + use self::TitleOrSsubel::*; + Ok(Some(match pair.as_rule() { + Rule::title => { + let (t, k) = convert_title(pair)?; + Title(t, k) + } + //TODO: subtitle, decoration, docinfo + Rule::EOI => return Ok(None), + _ => Ssubel(convert_substructure(pair)?.into()), + })) } - fn convert_substructure(pair: Pair<Rule>) -> Result<c::SubStructure, Error> { - Ok(match pair.as_rule() { - // todo: Topic, Sidebar, Transition - // no section here, as it’s constructed from titles - _ => convert_body_elem(pair)?.into(), - }) + #[allow(clippy::match_single_binding)] + Ok(match pair.as_rule() { + // TODO: Topic, Sidebar, Transition + // no section here, as it’s constructed from titles + _ => convert_body_elem(pair)?.into(), + }) } - fn convert_body_elem(pair: Pair<Rule>) -> Result<c::BodyElement, Error> { - Ok(match pair.as_rule() { - Rule::paragraph => convert_paragraph(pair)?.into(), - Rule::target => convert_target(pair)?.into(), - Rule::substitution_def => convert_substitution_def(pair)?.into(), - Rule::admonition_gen => convert_admonition_gen(pair)?.into(), - Rule::image => convert_image::<e::Image>(pair)?.into(), - Rule::bullet_list => convert_bullet_list(pair)?.into(), - Rule::literal_block => convert_literal_block(pair).into(), - Rule::code_directive => convert_code_directive(pair).into(), - Rule::raw_directive => convert_raw_directive(pair).into(), - Rule::block_comment => convert_comment(pair).into(), - rule => unimplemented!("unhandled rule {:?}", rule), - }) + Ok(match pair.as_rule() { + Rule::paragraph => convert_paragraph(pair)?.into(), + Rule::target => convert_target(pair)?.into(), + Rule::substitution_def => convert_substitution_def(pair)?.into(), + Rule::admonition_gen => convert_admonition_gen(pair)?, + Rule::image => convert_image::<e::Image>(pair)?.into(), + Rule::bullet_list => convert_bullet_list(pair)?.into(), + Rule::literal_block => convert_literal_block(pair).into(), + Rule::code_directive => convert_code_directive(pair).into(), + Rule::raw_directive => convert_raw_directive(pair).into(), + Rule::block_comment => convert_comment(pair).into(), + rule => unimplemented!("unhandled rule {:?}", rule), + }) } - fn convert_title(pair: Pair<Rule>) -> Result<(e::Title, TitleKind), Error> { - let mut title: Option<String> = None; - let mut title_inlines: Option<Vec<c::TextOrInlineElement>> = None; - let mut adornment_char: Option<char> = None; - // title_double or title_single. Extract kind before consuming - let inner_pair = pair.into_inner().next().unwrap(); - let kind = inner_pair.as_rule(); - for p in inner_pair.into_inner() { - match p.as_rule() { - Rule::line => { - title = Some(p.as_str().to_owned()); - title_inlines = Some(convert_inlines(p)?); - }, - Rule::adornments => adornment_char = Some(p.as_str().chars().next().expect("Empty adornment?")), - rule => unimplemented!("Unexpected rule in title: {:?}", rule), - }; - } - // now we encountered one line of text and one of adornments - // TODO: emit error if the adornment line is too short (has to match title length) - let mut elem = e::Title::with_children(title_inlines.expect("No text in title")); - if let Some(title) = title { - //TODO: slugify properly - let slug = title.to_lowercase().replace("\n", "").replace(" ", "-"); - elem.names_mut().push(at::NameToken(slug)); - } - let title_kind = match kind { - Rule::title_double => TitleKind::Double(adornment_char.unwrap()), - Rule::title_single => TitleKind::Single(adornment_char.unwrap()), - _ => unreachable!(), - }; - Ok((elem, title_kind)) + let mut title: Option<String> = None; + let mut title_inlines: Option<Vec<c::TextOrInlineElement>> = None; + let mut adornment_char: Option<char> = None; + // title_double or title_single. Extract kind before consuming + let inner_pair = pair.into_inner().next().unwrap(); + let kind = inner_pair.as_rule(); + for p in inner_pair.into_inner() { + match p.as_rule() { + Rule::line => { + title = Some(p.as_str().to_owned()); + title_inlines = Some(convert_inlines(p)?); + } + Rule::adornments => { + adornment_char = Some(p.as_str().chars().next().expect("Empty adornment?")) + } + rule => unimplemented!("Unexpected rule in title: {:?}", rule), + }; + } + // now we encountered one line of text and one of adornments + // TODO: emit error if the adornment line is too short (has to match title length) + let mut elem = e::Title::with_children(title_inlines.expect("No text in title")); + if let Some(title) = title { + //TODO: slugify properly + let slug = title.to_lowercase().replace('\n', "").replace(' ', "-"); + elem.names_mut().push(at::NameToken(slug)); + } + let title_kind = match kind { + Rule::title_double => TitleKind::Double(adornment_char.unwrap()), + Rule::title_single => TitleKind::Single(adornment_char.unwrap()), + _ => unreachable!(), + }; + Ok((elem, title_kind)) } - fn convert_paragraph(pair: Pair<Rule>) -> Result<e::Paragraph, Error> { - Ok(e::Paragraph::with_children(convert_inlines(pair)?)) + Ok(e::Paragraph::with_children(convert_inlines(pair)?)) } - fn convert_target(pair: Pair<Rule>) -> Result<e::Target, Error> { - let mut elem: e::Target = Default::default(); - elem.extra_mut().anonymous = false; - for p in pair.into_inner() { - match p.as_rule() { - Rule::target_name_uq | Rule::target_name_qu => { - elem.ids_mut().push(p.as_str().into()); - elem.names_mut().push(p.as_str().into()); - }, - // TODO: also handle non-urls - Rule::link_target => elem.extra_mut().refuri = Some(p.parse()?), - rule => panic!("Unexpected rule in target: {:?}", rule), - } - } - Ok(elem) + let mut elem: e::Target = Default::default(); + elem.extra_mut().anonymous = false; + for p in pair.into_inner() { + match p.as_rule() { + Rule::target_name_uq | Rule::target_name_qu => { + elem.ids_mut().push(p.as_str().into()); + elem.names_mut().push(p.as_str().into()); + } + // TODO: also handle non-urls + Rule::link_target => elem.extra_mut().refuri = Some(p.parse()?), + rule => panic!("Unexpected rule in target: {:?}", rule), + } + } + Ok(elem) } fn convert_substitution_def(pair: Pair<Rule>) -> Result<e::SubstitutionDefinition, Error> { - let mut pairs = pair.into_inner(); - let name = whitespace_normalize_name(pairs.next().unwrap().as_str()); // Rule::substitution_name - let inner_pair = pairs.next().unwrap(); - let inner: Vec<c::TextOrInlineElement> = match inner_pair.as_rule() { - Rule::replace => convert_replace(inner_pair)?, - Rule::image => vec![convert_image::<e::ImageInline>(inner_pair)?.into()], - rule => panic!("Unknown substitution rule {:?}", rule), - }; - let mut subst_def = e::SubstitutionDefinition::with_children(inner); - subst_def.names_mut().push(at::NameToken(name)); - Ok(subst_def) + let mut pairs = pair.into_inner(); + let name = whitespace_normalize_name(pairs.next().unwrap().as_str()); // Rule::substitution_name + let inner_pair = pairs.next().unwrap(); + let inner: Vec<c::TextOrInlineElement> = match inner_pair.as_rule() { + Rule::replace => convert_replace(inner_pair)?, + Rule::image => vec![convert_image::<e::ImageInline>(inner_pair)?.into()], + rule => panic!("Unknown substitution rule {:?}", rule), + }; + let mut subst_def = e::SubstitutionDefinition::with_children(inner); + subst_def.names_mut().push(at::NameToken(name)); + Ok(subst_def) } fn convert_replace(pair: Pair<Rule>) -> Result<Vec<c::TextOrInlineElement>, Error> { - let mut pairs = pair.into_inner(); - let paragraph = pairs.next().unwrap(); - convert_inlines(paragraph) + let mut pairs = pair.into_inner(); + let paragraph = pairs.next().unwrap(); + convert_inlines(paragraph) } -fn convert_image<I>(pair: Pair<Rule>) -> Result<I, Error> where I: Element + ExtraAttributes<a::Image> { - let mut pairs = pair.into_inner(); - let mut image = I::with_extra(a::Image::new( - pairs.next().unwrap().as_str().trim().parse()?, // line - )); - for opt in pairs { - let mut opt_iter = opt.into_inner(); - let opt_name = opt_iter.next().unwrap(); - let opt_val = opt_iter.next().unwrap(); - match opt_name.as_str() { - "class" => image.classes_mut().push(opt_val.as_str().to_owned()), - "name" => image.names_mut().push(opt_val.as_str().into()), - "alt" => image.extra_mut().alt = Some(opt_val.as_str().to_owned()), - "height" => image.extra_mut().height = Some(opt_val.parse()?), - "width" => image.extra_mut().width = Some(opt_val.parse()?), - "scale" => image.extra_mut().scale = Some(parse_scale(&opt_val)?), - "align" => image.extra_mut().align = Some(opt_val.parse()?), - "target" => image.extra_mut().target = Some(opt_val.parse()?), - name => bail!("Unknown Image option {}", name), - } - } - Ok(image) +fn convert_image<I>(pair: Pair<Rule>) -> Result<I, Error> +where + I: Element + ExtraAttributes<a::Image>, +{ + let mut pairs = pair.into_inner(); + let mut image = I::with_extra(a::Image::new( + pairs.next().unwrap().as_str().trim().parse()?, // line + )); + for opt in pairs { + let mut opt_iter = opt.into_inner(); + let opt_name = opt_iter.next().unwrap(); + let opt_val = opt_iter.next().unwrap(); + match opt_name.as_str() { + "class" => image.classes_mut().push(opt_val.as_str().to_owned()), + "name" => image.names_mut().push(opt_val.as_str().into()), + "alt" => image.extra_mut().alt = Some(opt_val.as_str().to_owned()), + "height" => image.extra_mut().height = Some(opt_val.parse()?), + "width" => image.extra_mut().width = Some(opt_val.parse()?), + "scale" => image.extra_mut().scale = Some(parse_scale(&opt_val)?), + "align" => image.extra_mut().align = Some(opt_val.parse()?), + "target" => image.extra_mut().target = Some(opt_val.parse()?), + name => bail!("Unknown Image option {}", name), + } + } + Ok(image) } fn parse_scale(pair: &Pair<Rule>) -> Result<u8, Error> { - let input = if pair.as_str().chars().rev().next() == Some('%') { &pair.as_str()[..pair.as_str().len()-1] } else { pair.as_str() }; - use pest::error::{Error,ErrorVariant}; - Ok(input.parse().map_err(|e: std::num::ParseIntError| { - let var: ErrorVariant<Rule> = ErrorVariant::CustomError { message: e.to_string() }; - Error::new_from_span(var, pair.as_span()) - })?) + let input = if pair.as_str().ends_with('%') { + &pair.as_str()[..pair.as_str().len() - 1] + } else { + pair.as_str() + }; + use pest::error::{Error, ErrorVariant}; + Ok(input.parse().map_err(|e: std::num::ParseIntError| { + let var: ErrorVariant<Rule> = ErrorVariant::CustomError { + message: e.to_string(), + }; + Error::new_from_span(var, pair.as_span()) + })?) } fn convert_admonition_gen(pair: Pair<Rule>) -> Result<c::BodyElement, Error> { - let mut iter = pair.into_inner(); - let typ = iter.next().unwrap().as_str(); - // TODO: in reality it contains body elements. - let children: Vec<c::BodyElement> = iter.map(|p| e::Paragraph::with_children(vec![p.as_str().into()]).into()).collect(); - Ok(match typ { - "attention" => e::Attention::with_children(children).into(), - "hint" => e::Hint::with_children(children).into(), - "note" => e::Note::with_children(children).into(), - "caution" => e::Caution::with_children(children).into(), - "danger" => e::Danger::with_children(children).into(), - "error" => e::Error::with_children(children).into(), - "important" => e::Important::with_children(children).into(), - "tip" => e::Tip::with_children(children).into(), - "warning" => e::Warning::with_children(children).into(), - typ => panic!("Unknown admontion type {}!", typ), - }) + let mut iter = pair.into_inner(); + let typ = iter.next().unwrap().as_str(); + // TODO: in reality it contains body elements. + let children: Vec<c::BodyElement> = iter + .map(|p| e::Paragraph::with_children(vec![p.as_str().into()]).into()) + .collect(); + Ok(match typ { + "attention" => e::Attention::with_children(children).into(), + "hint" => e::Hint::with_children(children).into(), + "note" => e::Note::with_children(children).into(), + "caution" => e::Caution::with_children(children).into(), + "danger" => e::Danger::with_children(children).into(), + "error" => e::Error::with_children(children).into(), + "important" => e::Important::with_children(children).into(), + "tip" => e::Tip::with_children(children).into(), + "warning" => e::Warning::with_children(children).into(), + typ => panic!("Unknown admontion type {}!", typ), + }) } fn convert_bullet_list(pair: Pair<Rule>) -> Result<e::BulletList, Error> { - Ok(e::BulletList::with_children(pair.into_inner().map(convert_bullet_item).collect::<Result<_, _>>()?)) + Ok(e::BulletList::with_children( + pair.into_inner() + .map(convert_bullet_item) + .collect::<Result<_, _>>()?, + )) } fn convert_bullet_item(pair: Pair<Rule>) -> Result<e::ListItem, Error> { - let mut iter = pair.into_inner(); - let mut children: Vec<c::BodyElement> = vec![ - convert_paragraph(iter.next().unwrap())?.into() - ]; - for p in iter { - children.push(convert_body_elem(p)?); - } - Ok(e::ListItem::with_children(children)) + let mut iter = pair.into_inner(); + let mut children: Vec<c::BodyElement> = vec![convert_paragraph(iter.next().unwrap())?.into()]; + for p in iter { + children.push(convert_body_elem(p)?); + } + Ok(e::ListItem::with_children(children)) } fn convert_literal_block(pair: Pair<Rule>) -> e::LiteralBlock { - convert_literal_lines(pair.into_inner().next().unwrap()) + convert_literal_lines(pair.into_inner().next().unwrap()) } fn convert_literal_lines(pair: Pair<Rule>) -> e::LiteralBlock { - let children = pair.into_inner().map(|l| match l.as_rule() { - Rule::literal_line => l.as_str(), - Rule::literal_line_blank => "\n", - _ => unreachable!(), - }.into()).collect(); - return e::LiteralBlock::with_children(children); + let children = pair + .into_inner() + .map(|l| { + match l.as_rule() { + Rule::literal_line => l.as_str(), + Rule::literal_line_blank => "\n", + _ => unreachable!(), + } + .into() + }) + .collect(); + e::LiteralBlock::with_children(children) } fn convert_code_directive(pair: Pair<Rule>) -> e::LiteralBlock { - let mut iter = pair.into_inner(); - let (lang, code) = match (iter.next().unwrap(), iter.next()) { - (lang, Some(code)) => (Some(lang), code), - (code, None) => (None, code), - }; - let mut code_block = convert_literal_lines(code); - code_block.classes_mut().push("code".to_owned()); - if let Some(lang) = lang { - code_block.classes_mut().push(lang.as_str().to_owned()); - }; - code_block + let mut iter = pair.into_inner(); + let (lang, code) = match (iter.next().unwrap(), iter.next()) { + (lang, Some(code)) => (Some(lang), code), + (code, None) => (None, code), + }; + let mut code_block = convert_literal_lines(code); + code_block.classes_mut().push("code".to_owned()); + if let Some(lang) = lang { + code_block.classes_mut().push(lang.as_str().to_owned()); + }; + code_block } fn convert_raw_directive(pair: Pair<Rule>) -> e::Raw { - let mut iter = pair.into_inner(); - let format = iter.next().unwrap(); + let mut iter = pair.into_inner(); + let format = iter.next().unwrap(); let block = iter.next().unwrap(); - let children = block.into_inner().map(|l| match l.as_rule() { - Rule::raw_line => l.as_str(), - Rule::raw_line_blank => "\n", - _ => unreachable!(), - }.into()).collect(); - let mut raw_block = e::Raw::with_children(children); - raw_block.extra_mut().format.push(at::NameToken(format.as_str().to_owned())); - raw_block + let children = block + .into_inner() + .map(|l| { + match l.as_rule() { + Rule::raw_line => l.as_str(), + Rule::raw_line_blank => "\n", + _ => unreachable!(), + } + .into() + }) + .collect(); + let mut raw_block = e::Raw::with_children(children); + raw_block + .extra_mut() + .format + .push(at::NameToken(format.as_str().to_owned())); + raw_block } fn convert_comment(pair: Pair<Rule>) -> e::Comment { - let lines = pair.into_inner().map(|l| match l.as_rule() { - Rule::comment_line_blank => "\n", - Rule::comment_line => l.as_str(), - _ => unreachable!(), - }.into()).collect(); - e::Comment::with_children(lines) + let lines = pair + .into_inner() + .map(|l| { + match l.as_rule() { + Rule::comment_line_blank => "\n", + Rule::comment_line => l.as_str(), + _ => unreachable!(), + } + .into() + }) + .collect(); + e::Comment::with_children(lines) } diff --git a/parser/src/conversion/inline.rs b/parser/src/conversion/inline.rs index 82a74e7..a0dcb88 100644 --- a/parser/src/conversion/inline.rs +++ b/parser/src/conversion/inline.rs @@ -2,159 +2,155 @@ use failure::Error; use pest::iterators::Pair; use document_tree::{ - HasChildren, - elements as e, - url::Url, - element_categories as c, - extra_attributes as a, - attribute_types as at, + attribute_types as at, element_categories as c, elements as e, extra_attributes as a, url::Url, + HasChildren, }; -use crate::{ - pest_rst::Rule, -// pair_ext_parse::PairExt, -}; use super::whitespace_normalize_name; - +use crate::pest_rst::Rule; pub fn convert_inline(pair: Pair<Rule>) -> Result<c::TextOrInlineElement, Error> { - Ok(match pair.as_rule() { - Rule::str | Rule::str_nested => pair.as_str().into(), - Rule::ws_newline => " ".to_owned().into(), - Rule::reference => convert_reference(pair)?, - Rule::substitution_name => convert_substitution_ref(pair)?.into(), - Rule::emph => e::Emphasis::with_children(convert_inlines(pair)?).into(), - Rule::strong => e::Strong::with_children(convert_inlines(pair)?).into(), - Rule::literal => e::Literal::with_children(vec![pair.as_str().to_owned()]).into(), - rule => unimplemented!("unknown rule {:?}", rule), - }) + Ok(match pair.as_rule() { + Rule::str | Rule::str_nested => pair.as_str().into(), + Rule::ws_newline => " ".to_owned().into(), + Rule::reference => convert_reference(pair)?, + Rule::substitution_name => convert_substitution_ref(pair)?.into(), + Rule::emph => e::Emphasis::with_children(convert_inlines(pair)?).into(), + Rule::strong => e::Strong::with_children(convert_inlines(pair)?).into(), + Rule::literal => e::Literal::with_children(vec![pair.as_str().to_owned()]).into(), + rule => unimplemented!("unknown rule {:?}", rule), + }) } pub fn convert_inlines(pair: Pair<Rule>) -> Result<Vec<c::TextOrInlineElement>, Error> { - pair.into_inner().map(convert_inline).collect() + pair.into_inner().map(convert_inline).collect() } fn convert_reference(pair: Pair<Rule>) -> Result<c::TextOrInlineElement, Error> { - let name; - let refuri; - let refid; - let mut refname = vec![]; - let mut children: Vec<c::TextOrInlineElement> = vec![]; - let concrete = pair.into_inner().next().unwrap(); - match concrete.as_rule() { - Rule::reference_target => { - let rt_inner = concrete.into_inner().next().unwrap(); // reference_target_uq or target_name_qu - match rt_inner.as_rule() { - Rule::reference_target_uq => { - refid = None; - name = Some(rt_inner.as_str().into()); - refuri = None; - refname.push(rt_inner.as_str().into()); - children.push(rt_inner.as_str().into()); - }, - Rule::reference_target_qu => { - let (text, reference) = { - let mut text = None; - let mut reference = None; - for inner in rt_inner.clone().into_inner() { - match inner.as_rule() { - Rule::reference_text => text = Some(inner), - Rule::reference_bracketed => reference = Some(inner), - _ => unreachable!() - } - } - (text, reference) - }; - let trimmed_text = match (&text, &reference) { - (Some(text), None) => text.as_str(), - (_, Some(reference)) => { - text - .map(|text| text.as_str().trim_end_matches(|ch| " \n\r".contains(ch))) - .filter(|text| !text.is_empty()) - .unwrap_or_else(|| reference.clone().into_inner().next().unwrap().as_str()) - } - (None, None) => unreachable!() - }; - refid = None; - name = Some(trimmed_text.into()); - refuri = if let Some(reference) = reference { - let inner = reference.into_inner().next().unwrap(); - match inner.as_rule() { - // The URL rules in our parser accept a narrow superset of - // valid URLs, so we need to handle false positives. - Rule::url => if let Ok(target) = Url::parse_absolute(inner.as_str()) { - Some(target) - } else if inner.as_str().ends_with('_') { - // like target_name_qu (minus the final underscore) - let full_str = inner.as_str(); - refname.push(full_str[0..full_str.len() - 1].into()); - None - } else { - // like relative_reference - Some(Url::parse_relative(inner.as_str())?) - }, - Rule::target_name_qu => { - refname.push(inner.as_str().into()); - None - }, - Rule::relative_reference => { - Some(Url::parse_relative(inner.as_str())?) - }, - _ => unreachable!() - } - } else { - refname.push(trimmed_text.into()); - None - }; - children.push(trimmed_text.into()); - }, - _ => unreachable!() - } - }, - Rule::reference_explicit => unimplemented!("explicit reference"), - Rule::reference_auto => { - let rt_inner = concrete.into_inner().next().unwrap(); - match rt_inner.as_rule() { - Rule::url_auto => match Url::parse_absolute(rt_inner.as_str()) { - Ok(target) => { - refuri = Some(target); - name = None; - refid = None; - children.push(rt_inner.as_str().into()); - }, - // if our parser got a URL wrong, return it as a string - Err(_) => return Ok(rt_inner.as_str().into()) - }, - Rule::email => { - let mailto_url = String::from("mailto:") + rt_inner.as_str(); - match Url::parse_absolute(&mailto_url) { - Ok(target) => { - refuri = Some(target); - name = None; - refid = None; - children.push(rt_inner.as_str().into()); - }, - // if our parser got a URL wrong, return it as a string - Err(_) => return Ok(rt_inner.as_str().into()) - } - }, - _ => unreachable!() - } - }, - _ => unreachable!(), - }; - Ok(e::Reference::new( - Default::default(), - a::Reference { name, refuri, refid, refname }, - children - ).into()) + let name; + let refuri; + let refid; + let mut refname = vec![]; + let mut children: Vec<c::TextOrInlineElement> = vec![]; + let concrete = pair.into_inner().next().unwrap(); + match concrete.as_rule() { + Rule::reference_target => { + let rt_inner = concrete.into_inner().next().unwrap(); // reference_target_uq or target_name_qu + match rt_inner.as_rule() { + Rule::reference_target_uq => { + refid = None; + name = Some(rt_inner.as_str().into()); + refuri = None; + refname.push(rt_inner.as_str().into()); + children.push(rt_inner.as_str().into()); + } + Rule::reference_target_qu => { + let (text, reference) = { + let mut text = None; + let mut reference = None; + for inner in rt_inner.clone().into_inner() { + match inner.as_rule() { + Rule::reference_text => text = Some(inner), + Rule::reference_bracketed => reference = Some(inner), + _ => unreachable!(), + } + } + (text, reference) + }; + let trimmed_text = match (&text, &reference) { + (Some(text), None) => text.as_str(), + (_, Some(reference)) => text + .map(|text| text.as_str().trim_end_matches(|ch| " \n\r".contains(ch))) + .filter(|text| !text.is_empty()) + .unwrap_or_else(|| { + reference.clone().into_inner().next().unwrap().as_str() + }), + (None, None) => unreachable!(), + }; + refid = None; + name = Some(trimmed_text.into()); + refuri = if let Some(reference) = reference { + let inner = reference.into_inner().next().unwrap(); + match inner.as_rule() { + // The URL rules in our parser accept a narrow superset of + // valid URLs, so we need to handle false positives. + Rule::url => { + if let Ok(target) = Url::parse_absolute(inner.as_str()) { + Some(target) + } else if inner.as_str().ends_with('_') { + // like target_name_qu (minus the final underscore) + let full_str = inner.as_str(); + refname.push(full_str[0..full_str.len() - 1].into()); + None + } else { + // like relative_reference + Some(Url::parse_relative(inner.as_str())?) + } + } + Rule::target_name_qu => { + refname.push(inner.as_str().into()); + None + } + Rule::relative_reference => Some(Url::parse_relative(inner.as_str())?), + _ => unreachable!(), + } + } else { + refname.push(trimmed_text.into()); + None + }; + children.push(trimmed_text.into()); + } + _ => unreachable!(), + } + } + Rule::reference_explicit => unimplemented!("explicit reference"), + Rule::reference_auto => { + let rt_inner = concrete.into_inner().next().unwrap(); + match rt_inner.as_rule() { + Rule::url_auto => match Url::parse_absolute(rt_inner.as_str()) { + Ok(target) => { + refuri = Some(target); + name = None; + refid = None; + children.push(rt_inner.as_str().into()); + } + // if our parser got a URL wrong, return it as a string + Err(_) => return Ok(rt_inner.as_str().into()), + }, + Rule::email => { + let mailto_url = String::from("mailto:") + rt_inner.as_str(); + match Url::parse_absolute(&mailto_url) { + Ok(target) => { + refuri = Some(target); + name = None; + refid = None; + children.push(rt_inner.as_str().into()); + } + // if our parser got a URL wrong, return it as a string + Err(_) => return Ok(rt_inner.as_str().into()), + } + } + _ => unreachable!(), + } + } + _ => unreachable!(), + }; + Ok(e::Reference::new( + Default::default(), + a::Reference { + name, + refuri, + refid, + refname, + }, + children, + ) + .into()) } fn convert_substitution_ref(pair: Pair<Rule>) -> Result<e::SubstitutionReference, Error> { - let name = whitespace_normalize_name(pair.as_str()); - Ok(a::ExtraAttributes::with_extra( - a::SubstitutionReference { - refname: vec![at::NameToken(name)] - } - )) + let name = whitespace_normalize_name(pair.as_str()); + Ok(a::ExtraAttributes::with_extra(a::SubstitutionReference { + refname: vec![at::NameToken(name)], + })) } diff --git a/parser/src/conversion/tests.rs b/parser/src/conversion/tests.rs index 89b0a1c..8fcb408 100644 --- a/parser/src/conversion/tests.rs +++ b/parser/src/conversion/tests.rs @@ -1,19 +1,15 @@ -use document_tree::{ - elements as e, - element_categories as c, - HasChildren, -}; +use document_tree::{element_categories as c, elements as e, HasChildren}; use crate::parse; fn ssubel_to_section(ssubel: &c::StructuralSubElement) -> &e::Section { - match ssubel { - c::StructuralSubElement::SubStructure(ref b) => match **b { - c::SubStructure::Section(ref s) => s, - ref c => panic!("Expected section, not {:?}", c), - }, - ref c => panic!("Expected SubStructure, not {:?}", c), - } + match ssubel { + c::StructuralSubElement::SubStructure(ref b) => match **b { + c::SubStructure::Section(ref s) => s, + ref c => panic!("Expected section, not {:?}", c), + }, + ref c => panic!("Expected SubStructure, not {:?}", c), + } } const SECTIONS: &str = "\ @@ -38,28 +34,61 @@ L3 again, skipping L2 #[test] fn convert_skipped_section() { - let doctree = parse(SECTIONS).unwrap(); - let lvl0 = doctree.children(); - assert_eq!(lvl0.len(), 3, "Should be a paragraph and 2 sections: {:?}", lvl0); - - assert_eq!(lvl0[0], e::Paragraph::with_children(vec![ - "Intro before first section title".to_owned().into() - ]).into(), "The intro text should fit"); - - let lvl1a = ssubel_to_section(&lvl0[1]).children(); - assert_eq!(lvl1a.len(), 2, "The 1st lvl1 section should have (a title and) a single lvl2 section as child: {:?}", lvl1a); - //TODO: test title lvl1a[0] - let lvl2 = ssubel_to_section(&lvl1a[1]).children(); - assert_eq!(lvl2.len(), 2, "The lvl2 section should have (a title and) a single lvl3 section as child: {:?}", lvl2); - //TODO: test title lvl2[0] - let lvl3a = ssubel_to_section(&lvl2[1]).children(); - assert_eq!(lvl3a.len(), 1, "The 1st lvl3 section should just a title: {:?}", lvl3a); - //TODO: test title lvl3a[0] - - let lvl1b = ssubel_to_section(&lvl0[2]).children(); - assert_eq!(lvl1b.len(), 2, "The 2nd lvl1 section should have (a title and) a single lvl2 section as child: {:?}", lvl1b); - //TODO: test title lvl1b[0] - let lvl3b = ssubel_to_section(&lvl1b[1]).children(); - assert_eq!(lvl3b.len(), 1, "The 2nd lvl3 section should have just a title: {:?}", lvl3b); - //TODO: test title lvl3b[0] + let doctree = parse(SECTIONS).unwrap(); + let lvl0 = doctree.children(); + assert_eq!( + lvl0.len(), + 3, + "Should be a paragraph and 2 sections: {:?}", + lvl0 + ); + + assert_eq!( + lvl0[0], + e::Paragraph::with_children(vec!["Intro before first section title".to_owned().into()]) + .into(), + "The intro text should fit" + ); + + let lvl1a = ssubel_to_section(&lvl0[1]).children(); + assert_eq!( + lvl1a.len(), + 2, + "The 1st lvl1 section should have (a title and) a single lvl2 section as child: {:?}", + lvl1a + ); + //TODO: test title lvl1a[0] + let lvl2 = ssubel_to_section(&lvl1a[1]).children(); + assert_eq!( + lvl2.len(), + 2, + "The lvl2 section should have (a title and) a single lvl3 section as child: {:?}", + lvl2 + ); + //TODO: test title lvl2[0] + let lvl3a = ssubel_to_section(&lvl2[1]).children(); + assert_eq!( + lvl3a.len(), + 1, + "The 1st lvl3 section should just a title: {:?}", + lvl3a + ); + //TODO: test title lvl3a[0] + + let lvl1b = ssubel_to_section(&lvl0[2]).children(); + assert_eq!( + lvl1b.len(), + 2, + "The 2nd lvl1 section should have (a title and) a single lvl2 section as child: {:?}", + lvl1b + ); + //TODO: test title lvl1b[0] + let lvl3b = ssubel_to_section(&lvl1b[1]).children(); + assert_eq!( + lvl3b.len(), + 1, + "The 2nd lvl3 section should have just a title: {:?}", + lvl3b + ); + //TODO: test title lvl3b[0] } diff --git a/parser/src/lib.rs b/parser/src/lib.rs index 23e97c7..303e26a 100644 --- a/parser/src/lib.rs +++ b/parser/src/lib.rs @@ -1,28 +1,27 @@ -pub mod token; mod conversion; -mod simplify; -mod pest_rst; mod pair_ext_parse; +mod pest_rst; +mod simplify; #[cfg(test)] pub mod tests; +pub mod token; use failure::Error; use pest::Parser; use document_tree::Document; -use self::pest_rst::{RstParser,Rule}; use self::conversion::convert_document; +use self::pest_rst::{RstParser, Rule}; use self::simplify::resolve_references; - /// Parse into a document tree and resolve sections, but not references. pub fn parse_only(source: &str) -> Result<Document, Error> { - let pairs = RstParser::parse(Rule::document, source)?; - convert_document(pairs) + let pairs = RstParser::parse(Rule::document, source)?; + convert_document(pairs) } -/// Parse into a document tree and resolve sections and references. +/// Parse into a document tree and resolve sections and references. pub fn parse(source: &str) -> Result<Document, Error> { - parse_only(source).map(resolve_references) + parse_only(source).map(resolve_references) } diff --git a/parser/src/pair_ext_parse.rs b/parser/src/pair_ext_parse.rs index a04b3dd..cbb51b0 100644 --- a/parser/src/pair_ext_parse.rs +++ b/parser/src/pair_ext_parse.rs @@ -1,21 +1,41 @@ use std::str::FromStr; -use pest::Span; +use pest::error::{Error, ErrorVariant}; use pest::iterators::Pair; -use pest::error::{Error,ErrorVariant}; - +use pest::Span; -pub trait PairExt<R> where R: pest::RuleType { - fn parse<T, E>(&self) -> Result<T, Error<R>> where T: FromStr<Err = E>, E: ToString; +pub trait PairExt<R> +where + R: pest::RuleType, +{ + fn parse<T, E>(&self) -> Result<T, Box<Error<R>>> + where + T: FromStr<Err = E>, + E: ToString; } -impl<'l, R> PairExt<R> for Pair<'l, R> where R: pest::RuleType { - fn parse<T, E>(&self) -> Result<T, Error<R>> where T: FromStr<Err = E>, E: ToString { - self.as_str().parse().map_err(|e| to_parse_error(self.as_span(), &e)) - } +impl<'l, R> PairExt<R> for Pair<'l, R> +where + R: pest::RuleType, +{ + fn parse<T, E>(&self) -> Result<T, Box<Error<R>>> + where + T: FromStr<Err = E>, + E: ToString, + { + self.as_str() + .parse() + .map_err(|e| to_parse_error(self.as_span(), &e)) + } } -pub(crate) fn to_parse_error<E, R>(span: Span, e: &E) -> Error<R> where E: ToString, R: pest::RuleType { - let var: ErrorVariant<R> = ErrorVariant::CustomError { message: e.to_string() }; - Error::new_from_span(var, span) +pub(crate) fn to_parse_error<E, R>(span: Span, e: &E) -> Box<Error<R>> +where + E: ToString, + R: pest::RuleType, +{ + let var: ErrorVariant<R> = ErrorVariant::CustomError { + message: e.to_string(), + }; + Box::new(Error::new_from_span(var, span)) } diff --git a/parser/src/rst.pest b/parser/src/rst.pest index d9fb668..f942547 100644 --- a/parser/src/rst.pest +++ b/parser/src/rst.pest @@ -5,7 +5,7 @@ // Section headers define the hierarchy by their delimiters, // and pest only has one stack that we need for indentation. -document = _{ SOI ~ blocks ~ EOI } +document = _{ SOI ~ blank_line* ~ blocks ~ EOI } blocks = _{ block ~ (blank_line* ~ block)* ~ blank_line? } block = _{ PEEK[..] ~ hanging_block } @@ -257,10 +257,10 @@ known_scheme = { "wss"|"wtai"|"wyciwyg"|"xcon"|"xcon-userid"|"xfire"|"xmlrpc.beep"|"xmlrpc.beeps"|"xmpp"|"xri"|"ymsgr"|"z39.50"|"z39.50r"|"z39.50s" } url_unit = { - ASCII_ALPHANUMERIC | - "!"|"$"|"&"|"'"|"("|")"|"*"|"+"|","|"-"|"."|"/"|":"|";"|"="|"?"|"@"|"_"|"~" | - (!(SURROGATE|NONCHARACTER_CODE_POINT) ~ '\u{A0}'..'\u{10FFFD}') | - ("%" ~ ASCII_HEX_DIGIT{2}) + ASCII_ALPHANUMERIC | + "!"|"$"|"&"|"'"|"("|")"|"*"|"+"|","|"-"|"."|"/"|":"|";"|"="|"?"|"@"|"_"|"~" | + (!(SURROGATE|NONCHARACTER_CODE_POINT) ~ '\u{A0}'..'\u{10FFFD}') | + ("%" ~ ASCII_HEX_DIGIT{2}) } /* diff --git a/parser/src/simplify.rs b/parser/src/simplify.rs index 4c254af..17e9ee3 100644 --- a/parser/src/simplify.rs +++ b/parser/src/simplify.rs @@ -4,15 +4,15 @@ http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html#hyperlink-tar Links can have internal or external targets. In the source, targets look like: - .. targetname1: - .. targetname2: + .. targetname1: + .. targetname2: - some paragraph or list item or so + some paragraph or list item or so or: .. targetname1: - .. targetname2: https://link + .. targetname2: https://link There’s also anonymous links and targets without names. @@ -22,641 +22,692 @@ TODO: continue documenting how it’s done via https://repo.or.cz/docutils.git/b use std::collections::HashMap; use document_tree::{ - url::Url, - Document, - HasChildren, - attribute_types::NameToken, - elements::{self as e, Element}, - element_categories as c, - extra_attributes::ExtraAttributes, + attribute_types::NameToken, + element_categories as c, + elements::{self as e, Element}, + extra_attributes::ExtraAttributes, + url::Url, + Document, HasChildren, }; - #[derive(Debug)] +#[allow(dead_code)] enum NamedTargetType { - NumberedFootnote(usize), - LabeledFootnote(usize), - Citation, - InternalLink, - ExternalLink(Url), - IndirectLink(NameToken), - SectionTitle, + NumberedFootnote(usize), + LabeledFootnote(usize), + Citation, + InternalLink, + ExternalLink(Url), + IndirectLink(NameToken), + SectionTitle, } impl NamedTargetType { - fn is_implicit_target(&self) -> bool { - match self { - NamedTargetType::SectionTitle => true, - _ => false, - } - } + #[allow(dead_code)] + fn is_implicit_target(&self) -> bool { + matches!(self, NamedTargetType::SectionTitle) + } } #[derive(Clone, Debug)] struct Substitution { - content: Vec<c::TextOrInlineElement>, - /// If true and the sibling before the reference is a text node, - /// the text node gets right-trimmed. - ltrim: bool, - /// Same as `ltrim` with the sibling after the reference. - rtrim: bool, + content: Vec<c::TextOrInlineElement>, + /// If true and the sibling before the reference is a text node, + /// the text node gets right-trimmed. + ltrim: bool, + /// Same as `ltrim` with the sibling after the reference. + rtrim: bool, } #[derive(Default, Debug)] struct TargetsCollected { - named_targets: HashMap<NameToken, NamedTargetType>, - substitutions: HashMap<NameToken, Substitution>, - normalized_substitutions: HashMap<String, Substitution>, + named_targets: HashMap<NameToken, NamedTargetType>, + substitutions: HashMap<NameToken, Substitution>, + normalized_substitutions: HashMap<String, Substitution>, } impl TargetsCollected { - fn target_url<'t>(self: &'t TargetsCollected, refname: &[NameToken]) -> Option<&'t Url> { - // TODO: Check if the target would expand circularly - if refname.len() != 1 { - panic!("Expected exactly one name in a reference."); - } - let name = refname[0].clone(); - match self.named_targets.get(&name)? { - NamedTargetType::ExternalLink(url) => Some(url), - _ => unimplemented!(), - } - } - - fn substitution<'t>(self: &'t TargetsCollected, refname: &[NameToken]) -> Option<&'t Substitution> { - // TODO: Check if the substitution would expand circularly - if refname.len() != 1 { - panic!("Expected exactly one name in a substitution reference."); - } - let name = refname[0].clone(); - self.substitutions.get(&name).or_else(|| { - self.normalized_substitutions.get(&name.0.to_lowercase()) - }) - } + fn target_url<'t>(self: &'t TargetsCollected, refname: &[NameToken]) -> Option<&'t Url> { + // TODO: Check if the target would expand circularly + if refname.len() != 1 { + panic!("Expected exactly one name in a reference."); + } + let name = refname[0].clone(); + match self.named_targets.get(&name)? { + NamedTargetType::ExternalLink(url) => Some(url), + _ => unimplemented!(), + } + } + + fn substitution<'t>( + self: &'t TargetsCollected, + refname: &[NameToken], + ) -> Option<&'t Substitution> { + // TODO: Check if the substitution would expand circularly + if refname.len() != 1 { + panic!("Expected exactly one name in a substitution reference."); + } + let name = refname[0].clone(); + self.substitutions + .get(&name) + .or_else(|| self.normalized_substitutions.get(&name.0.to_lowercase())) + } } trait ResolvableRefs { - fn populate_targets(&self, refs: &mut TargetsCollected); - fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> where Self: Sized; + fn populate_targets(&self, refs: &mut TargetsCollected); + fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> + where + Self: Sized; } pub fn resolve_references(mut doc: Document) -> Document { - let mut references: TargetsCollected = Default::default(); - for c in doc.children() { - c.populate_targets(&mut references); - } - let new: Vec<_> = doc.children_mut().drain(..).flat_map(|c| c.resolve_refs(&references)).collect(); - Document::with_children(new) + let mut references: TargetsCollected = Default::default(); + for c in doc.children() { + c.populate_targets(&mut references); + } + let new: Vec<_> = doc + .children_mut() + .drain(..) + .flat_map(|c| c.resolve_refs(&references)) + .collect(); + Document::with_children(new) } -fn sub_pop<P, C>(parent: &P, refs: &mut TargetsCollected) where P: HasChildren<C>, C: ResolvableRefs { - for c in parent.children() { - c.populate_targets(refs); - } +fn sub_pop<P, C>(parent: &P, refs: &mut TargetsCollected) +where + P: HasChildren<C>, + C: ResolvableRefs, +{ + for c in parent.children() { + c.populate_targets(refs); + } } -fn sub_res<P, C>(mut parent: P, refs: &TargetsCollected) -> P where P: e::Element + HasChildren<C>, C: ResolvableRefs { - let new: Vec<_> = parent.children_mut().drain(..).flat_map(|c| c.resolve_refs(refs)).collect(); - parent.children_mut().extend(new); - parent +fn sub_res<P, C>(mut parent: P, refs: &TargetsCollected) -> P +where + P: e::Element + HasChildren<C>, + C: ResolvableRefs, +{ + let new: Vec<_> = parent + .children_mut() + .drain(..) + .flat_map(|c| c.resolve_refs(refs)) + .collect(); + parent.children_mut().extend(new); + parent } -fn sub_sub_pop<P, C1, C2>(parent: &P, refs: &mut TargetsCollected) where P: HasChildren<C1>, C1: HasChildren<C2>, C2: ResolvableRefs { - for c in parent.children() { - sub_pop(c, refs); - } +fn sub_sub_pop<P, C1, C2>(parent: &P, refs: &mut TargetsCollected) +where + P: HasChildren<C1>, + C1: HasChildren<C2>, + C2: ResolvableRefs, +{ + for c in parent.children() { + sub_pop(c, refs); + } } -fn sub_sub_res<P, C1, C2>(mut parent: P, refs: &TargetsCollected) -> P where P: e::Element + HasChildren<C1>, C1: e::Element + HasChildren<C2>, C2: ResolvableRefs { - let new: Vec<_> = parent.children_mut().drain(..).map(|c| sub_res(c, refs)).collect(); - parent.children_mut().extend(new); - parent +fn sub_sub_res<P, C1, C2>(mut parent: P, refs: &TargetsCollected) -> P +where + P: e::Element + HasChildren<C1>, + C1: e::Element + HasChildren<C2>, + C2: ResolvableRefs, +{ + let new: Vec<_> = parent + .children_mut() + .drain(..) + .map(|c| sub_res(c, refs)) + .collect(); + parent.children_mut().extend(new); + parent } impl ResolvableRefs for c::StructuralSubElement { - fn populate_targets(&self, refs: &mut TargetsCollected) { - use c::StructuralSubElement::*; - match self { - Title(e) => sub_pop(&**e, refs), - Subtitle(e) => sub_pop(&**e, refs), - Decoration(e) => sub_pop(&**e, refs), - Docinfo(e) => sub_pop(&**e, refs), - SubStructure(e) => e.populate_targets(refs), - } - } - fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { - use c::StructuralSubElement::*; - vec![match self { - Title(e) => sub_res(*e, refs).into(), - Subtitle(e) => sub_res(*e, refs).into(), - Decoration(e) => sub_res(*e, refs).into(), - Docinfo(e) => sub_res(*e, refs).into(), - SubStructure(e) => return e.resolve_refs(refs).drain(..).map(Into::into).collect(), - }] - } + fn populate_targets(&self, refs: &mut TargetsCollected) { + use c::StructuralSubElement::*; + match self { + Title(e) => sub_pop(&**e, refs), + Subtitle(e) => sub_pop(&**e, refs), + Decoration(e) => sub_pop(&**e, refs), + Docinfo(e) => sub_pop(&**e, refs), + SubStructure(e) => e.populate_targets(refs), + } + } + fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { + use c::StructuralSubElement::*; + vec![match self { + Title(e) => sub_res(*e, refs).into(), + Subtitle(e) => sub_res(*e, refs).into(), + Decoration(e) => sub_res(*e, refs).into(), + Docinfo(e) => sub_res(*e, refs).into(), + SubStructure(e) => return e.resolve_refs(refs).drain(..).map(Into::into).collect(), + }] + } } impl ResolvableRefs for c::SubStructure { - fn populate_targets(&self, refs: &mut TargetsCollected) { - use c::SubStructure::*; - match self { - Topic(e) => sub_pop(&**e, refs), - Sidebar(e) => sub_pop(&**e, refs), - Transition(_) => {}, - Section(e) => sub_pop(&**e, refs), - BodyElement(e) => e.populate_targets(refs), - } - } - fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { - use c::SubStructure::*; - vec![match self { - Topic(e) => sub_res(*e, refs).into(), - Sidebar(e) => sub_res(*e, refs).into(), - Transition(e) => Transition(e), - Section(e) => sub_res(*e, refs).into(), - BodyElement(e) => return e.resolve_refs(refs).drain(..).map(Into::into).collect(), - }] - } + fn populate_targets(&self, refs: &mut TargetsCollected) { + use c::SubStructure::*; + match self { + Topic(e) => sub_pop(&**e, refs), + Sidebar(e) => sub_pop(&**e, refs), + Transition(_) => {} + Section(e) => sub_pop(&**e, refs), + BodyElement(e) => e.populate_targets(refs), + } + } + fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { + use c::SubStructure::*; + vec![match self { + Topic(e) => sub_res(*e, refs).into(), + Sidebar(e) => sub_res(*e, refs).into(), + Transition(e) => Transition(e), + Section(e) => sub_res(*e, refs).into(), + BodyElement(e) => return e.resolve_refs(refs).drain(..).map(Into::into).collect(), + }] + } } impl ResolvableRefs for c::BodyElement { - fn populate_targets(&self, refs: &mut TargetsCollected) { - use c::BodyElement::*; - match self { - Paragraph(e) => sub_pop(&**e, refs), - LiteralBlock(e) => sub_pop(&**e, refs), - DoctestBlock(e) => sub_pop(&**e, refs), - MathBlock(_) => {}, - Rubric(e) => sub_pop(&**e, refs), - SubstitutionDefinition(e) => { - let subst = Substitution { - content: e.children().clone(), - ltrim: e.extra().ltrim, - rtrim: e.extra().rtrim - }; - for name in e.names() { - if refs.substitutions.contains_key(name) { - // TODO: Duplicate substitution name (level 3 system message). - } - // Intentionally overriding any previous values. - refs.substitutions.insert(name.clone(), subst.clone()); - refs.normalized_substitutions.insert(name.0.to_lowercase(), subst.clone()); - } - }, - Comment(_) => {}, - Pending(_) => { - unimplemented!(); - }, - Target(e) => { - if let Some(uri) = &e.extra().refuri { - for name in e.names() { - refs.named_targets.insert(name.clone(), NamedTargetType::ExternalLink(uri.clone())); - } - } - // TODO: as is, people can only refer to the target directly containing the URL. - // add refid and refnames to some HashMap and follow those later. - }, - Raw(_) => {}, - Image(_) => {}, - Compound(e) => sub_pop(&**e, refs), - Container(e) => sub_pop(&**e, refs), - BulletList(e) => sub_sub_pop(&**e, refs), - EnumeratedList(e) => sub_sub_pop(&**e, refs), - DefinitionList(e) => sub_sub_pop(&**e, refs), - FieldList(e) => sub_sub_pop(&**e, refs), - OptionList(e) => sub_sub_pop(&**e, refs), - LineBlock(e) => sub_pop(&**e, refs), - BlockQuote(e) => sub_pop(&**e, refs), - Admonition(e) => sub_pop(&**e, refs), - Attention(e) => sub_pop(&**e, refs), - Hint(e) => sub_pop(&**e, refs), - Note(e) => sub_pop(&**e, refs), - Caution(e) => sub_pop(&**e, refs), - Danger(e) => sub_pop(&**e, refs), - Error(e) => sub_pop(&**e, refs), - Important(e) => sub_pop(&**e, refs), - Tip(e) => sub_pop(&**e, refs), - Warning(e) => sub_pop(&**e, refs), - Footnote(e) => sub_pop(&**e, refs), - Citation(e) => sub_pop(&**e, refs), - SystemMessage(e) => sub_pop(&**e, refs), - Figure(e) => sub_pop(&**e, refs), - Table(e) => sub_pop(&**e, refs) - } - } - fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { - use c::BodyElement::*; - vec![match self { - Paragraph(e) => sub_res(*e, refs).into(), - LiteralBlock(e) => sub_res(*e, refs).into(), - DoctestBlock(e) => sub_res(*e, refs).into(), - MathBlock(e) => MathBlock(e), - Rubric(e) => sub_res(*e, refs).into(), - SubstitutionDefinition(_) => return vec![], - Comment(e) => Comment(e), - Pending(e) => Pending(e), - Target(e) => Target(e), - Raw(e) => Raw(e), - Image(e) => Image(e), - Compound(e) => sub_res(*e, refs).into(), - Container(e) => sub_res(*e, refs).into(), - BulletList(e) => sub_sub_res(*e, refs).into(), - EnumeratedList(e) => sub_sub_res(*e, refs).into(), - DefinitionList(e) => sub_sub_res(*e, refs).into(), - FieldList(e) => sub_sub_res(*e, refs).into(), - OptionList(e) => sub_sub_res(*e, refs).into(), - LineBlock(e) => sub_res(*e, refs).into(), - BlockQuote(e) => sub_res(*e, refs).into(), - Admonition(e) => sub_res(*e, refs).into(), - Attention(e) => sub_res(*e, refs).into(), - Hint(e) => sub_res(*e, refs).into(), - Note(e) => sub_res(*e, refs).into(), - Caution(e) => sub_res(*e, refs).into(), - Danger(e) => sub_res(*e, refs).into(), - Error(e) => sub_res(*e, refs).into(), - Important(e) => sub_res(*e, refs).into(), - Tip(e) => sub_res(*e, refs).into(), - Warning(e) => sub_res(*e, refs).into(), - Footnote(e) => sub_res(*e, refs).into(), - Citation(e) => sub_res(*e, refs).into(), - SystemMessage(e) => sub_res(*e, refs).into(), - Figure(e) => sub_res(*e, refs).into(), - Table(e) => sub_res(*e, refs).into() - }] - } + fn populate_targets(&self, refs: &mut TargetsCollected) { + use c::BodyElement::*; + match self { + Paragraph(e) => sub_pop(&**e, refs), + LiteralBlock(e) => sub_pop(&**e, refs), + DoctestBlock(e) => sub_pop(&**e, refs), + MathBlock(_) => {} + Rubric(e) => sub_pop(&**e, refs), + SubstitutionDefinition(e) => { + let subst = Substitution { + content: e.children().clone(), + ltrim: e.extra().ltrim, + rtrim: e.extra().rtrim, + }; + for name in e.names() { + if refs.substitutions.contains_key(name) { + // TODO: Duplicate substitution name (level 3 system message). + } + // Intentionally overriding any previous values. + refs.substitutions.insert(name.clone(), subst.clone()); + refs.normalized_substitutions + .insert(name.0.to_lowercase(), subst.clone()); + } + } + Comment(_) => {} + Pending(_) => { + unimplemented!(); + } + Target(e) => { + if let Some(uri) = &e.extra().refuri { + for name in e.names() { + refs.named_targets + .insert(name.clone(), NamedTargetType::ExternalLink(uri.clone())); + } + } + // TODO: as is, people can only refer to the target directly containing the URL. + // add refid and refnames to some HashMap and follow those later. + } + Raw(_) => {} + Image(_) => {} + Compound(e) => sub_pop(&**e, refs), + Container(e) => sub_pop(&**e, refs), + BulletList(e) => sub_sub_pop(&**e, refs), + EnumeratedList(e) => sub_sub_pop(&**e, refs), + DefinitionList(e) => sub_sub_pop(&**e, refs), + FieldList(e) => sub_sub_pop(&**e, refs), + OptionList(e) => sub_sub_pop(&**e, refs), + LineBlock(e) => sub_pop(&**e, refs), + BlockQuote(e) => sub_pop(&**e, refs), + Admonition(e) => sub_pop(&**e, refs), + Attention(e) => sub_pop(&**e, refs), + Hint(e) => sub_pop(&**e, refs), + Note(e) => sub_pop(&**e, refs), + Caution(e) => sub_pop(&**e, refs), + Danger(e) => sub_pop(&**e, refs), + Error(e) => sub_pop(&**e, refs), + Important(e) => sub_pop(&**e, refs), + Tip(e) => sub_pop(&**e, refs), + Warning(e) => sub_pop(&**e, refs), + Footnote(e) => sub_pop(&**e, refs), + Citation(e) => sub_pop(&**e, refs), + SystemMessage(e) => sub_pop(&**e, refs), + Figure(e) => sub_pop(&**e, refs), + Table(e) => sub_pop(&**e, refs), + } + } + fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { + use c::BodyElement::*; + vec![match self { + Paragraph(e) => sub_res(*e, refs).into(), + LiteralBlock(e) => sub_res(*e, refs).into(), + DoctestBlock(e) => sub_res(*e, refs).into(), + MathBlock(e) => MathBlock(e), + Rubric(e) => sub_res(*e, refs).into(), + SubstitutionDefinition(_) => return vec![], + Comment(e) => Comment(e), + Pending(e) => Pending(e), + Target(e) => Target(e), + Raw(e) => Raw(e), + Image(e) => Image(e), + Compound(e) => sub_res(*e, refs).into(), + Container(e) => sub_res(*e, refs).into(), + BulletList(e) => sub_sub_res(*e, refs).into(), + EnumeratedList(e) => sub_sub_res(*e, refs).into(), + DefinitionList(e) => sub_sub_res(*e, refs).into(), + FieldList(e) => sub_sub_res(*e, refs).into(), + OptionList(e) => sub_sub_res(*e, refs).into(), + LineBlock(e) => sub_res(*e, refs).into(), + BlockQuote(e) => sub_res(*e, refs).into(), + Admonition(e) => sub_res(*e, refs).into(), + Attention(e) => sub_res(*e, refs).into(), + Hint(e) => sub_res(*e, refs).into(), + Note(e) => sub_res(*e, refs).into(), + Caution(e) => sub_res(*e, refs).into(), + Danger(e) => sub_res(*e, refs).into(), + Error(e) => sub_res(*e, refs).into(), + Important(e) => sub_res(*e, refs).into(), + Tip(e) => sub_res(*e, refs).into(), + Warning(e) => sub_res(*e, refs).into(), + Footnote(e) => sub_res(*e, refs).into(), + Citation(e) => sub_res(*e, refs).into(), + SystemMessage(e) => sub_res(*e, refs).into(), + Figure(e) => sub_res(*e, refs).into(), + Table(e) => sub_res(*e, refs).into(), + }] + } } impl ResolvableRefs for c::BibliographicElement { - fn populate_targets(&self, refs: &mut TargetsCollected) { - use c::BibliographicElement::*; - match self { - Author(e) => sub_pop(&**e, refs), - Authors(e) => sub_pop(&**e, refs), - Organization(e) => sub_pop(&**e, refs), - Address(e) => sub_pop(&**e, refs), - Contact(e) => sub_pop(&**e, refs), - Version(e) => sub_pop(&**e, refs), - Revision(e) => sub_pop(&**e, refs), - Status(e) => sub_pop(&**e, refs), - Date(e) => sub_pop(&**e, refs), - Copyright(e) => sub_pop(&**e, refs), - Field(e) => sub_pop(&**e, refs), - } - } - fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { - use c::BibliographicElement::*; - vec![match self { - Author(e) => sub_res(*e, refs).into(), - Authors(e) => sub_res(*e, refs).into(), - Organization(e) => sub_res(*e, refs).into(), - Address(e) => sub_res(*e, refs).into(), - Contact(e) => sub_res(*e, refs).into(), - Version(e) => sub_res(*e, refs).into(), - Revision(e) => sub_res(*e, refs).into(), - Status(e) => sub_res(*e, refs).into(), - Date(e) => sub_res(*e, refs).into(), - Copyright(e) => sub_res(*e, refs).into(), - Field(e) => sub_res(*e, refs).into(), - }] - } + fn populate_targets(&self, refs: &mut TargetsCollected) { + use c::BibliographicElement::*; + match self { + Author(e) => sub_pop(&**e, refs), + Authors(e) => sub_pop(&**e, refs), + Organization(e) => sub_pop(&**e, refs), + Address(e) => sub_pop(&**e, refs), + Contact(e) => sub_pop(&**e, refs), + Version(e) => sub_pop(&**e, refs), + Revision(e) => sub_pop(&**e, refs), + Status(e) => sub_pop(&**e, refs), + Date(e) => sub_pop(&**e, refs), + Copyright(e) => sub_pop(&**e, refs), + Field(e) => sub_pop(&**e, refs), + } + } + fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { + use c::BibliographicElement::*; + vec![match self { + Author(e) => sub_res(*e, refs).into(), + Authors(e) => sub_res(*e, refs).into(), + Organization(e) => sub_res(*e, refs).into(), + Address(e) => sub_res(*e, refs).into(), + Contact(e) => sub_res(*e, refs).into(), + Version(e) => sub_res(*e, refs).into(), + Revision(e) => sub_res(*e, refs).into(), + Status(e) => sub_res(*e, refs).into(), + Date(e) => sub_res(*e, refs).into(), + Copyright(e) => sub_res(*e, refs).into(), + Field(e) => sub_res(*e, refs).into(), + }] + } } impl ResolvableRefs for c::TextOrInlineElement { - fn populate_targets(&self, refs: &mut TargetsCollected) { - use c::TextOrInlineElement::*; - match self { - String(_) => {}, - Emphasis(e) => sub_pop(&**e, refs), - Strong(e) => sub_pop(&**e, refs), - Literal(_) => {}, - Reference(e) => sub_pop(&**e, refs), - FootnoteReference(e) => sub_pop(&**e, refs), - CitationReference(e) => sub_pop(&**e, refs), - SubstitutionReference(e) => sub_pop(&**e, refs), - TitleReference(e) => sub_pop(&**e, refs), - Abbreviation(e) => sub_pop(&**e, refs), - Acronym(e) => sub_pop(&**e, refs), - Superscript(e) => sub_pop(&**e, refs), - Subscript(e) => sub_pop(&**e, refs), - Inline(e) => sub_pop(&**e, refs), - Problematic(e) => sub_pop(&**e, refs), - Generated(e) => sub_pop(&**e, refs), - Math(_) => {}, - TargetInline(_) => { - unimplemented!(); - }, - RawInline(_) => {}, - ImageInline(_) => {} - } - } - fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { - use c::TextOrInlineElement::*; - vec![match self { - String(e) => String(e), - Emphasis(e) => sub_res(*e, refs).into(), - Strong(e) => sub_res(*e, refs).into(), - Literal(e) => Literal(e), - Reference(mut e) => { - if e.extra().refuri.is_none() { - if let Some(uri) = refs.target_url(&e.extra().refname) { - e.extra_mut().refuri = Some(uri.clone()); - } - } - (*e).into() - }, - FootnoteReference(e) => sub_res(*e, refs).into(), - CitationReference(e) => sub_res(*e, refs).into(), - SubstitutionReference(e) => match refs.substitution(&e.extra().refname) { - Some(Substitution {content, ltrim, rtrim}) => { - // (level 3 system message). - // TODO: ltrim and rtrim. - if *ltrim || *rtrim { - dbg!(content, ltrim, rtrim); - } - return content.clone() - }, - None => { - // Undefined substitution name (level 3 system message). - // TODO: This replaces the reference by a Problematic node. - // The corresponding SystemMessage node should go in a generated - // section with class "system-messages" at the end of the document. - use document_tree::Problematic; - let mut replacement: Box<Problematic> = Box::new(Default::default()); - replacement.children_mut().push( - c::TextOrInlineElement::String(Box::new(format!("|{}|", e.extra().refname[0].0))) - ); - // TODO: Create an ID for replacement for the system_message to reference. - // TODO: replacement.refid pointing to the system_message. - Problematic(replacement) - } - }, - TitleReference(e) => sub_res(*e, refs).into(), - Abbreviation(e) => sub_res(*e, refs).into(), - Acronym(e) => sub_res(*e, refs).into(), - Superscript(e) => sub_res(*e, refs).into(), - Subscript(e) => sub_res(*e, refs).into(), - Inline(e) => sub_res(*e, refs).into(), - Problematic(e) => sub_res(*e, refs).into(), - Generated(e) => sub_res(*e, refs).into(), - Math(e) => Math(e), - TargetInline(e) => TargetInline(e), - RawInline(e) => RawInline(e), - ImageInline(e) => ImageInline(e) - }] - } + fn populate_targets(&self, refs: &mut TargetsCollected) { + use c::TextOrInlineElement::*; + match self { + String(_) => {} + Emphasis(e) => sub_pop(&**e, refs), + Strong(e) => sub_pop(&**e, refs), + Literal(_) => {} + Reference(e) => sub_pop(&**e, refs), + FootnoteReference(e) => sub_pop(&**e, refs), + CitationReference(e) => sub_pop(&**e, refs), + SubstitutionReference(e) => sub_pop(&**e, refs), + TitleReference(e) => sub_pop(&**e, refs), + Abbreviation(e) => sub_pop(&**e, refs), + Acronym(e) => sub_pop(&**e, refs), + Superscript(e) => sub_pop(&**e, refs), + Subscript(e) => sub_pop(&**e, refs), + Inline(e) => sub_pop(&**e, refs), + Problematic(e) => sub_pop(&**e, refs), + Generated(e) => sub_pop(&**e, refs), + Math(_) => {} + TargetInline(_) => { + unimplemented!(); + } + RawInline(_) => {} + ImageInline(_) => {} + } + } + fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { + use c::TextOrInlineElement::*; + vec![match self { + String(e) => String(e), + Emphasis(e) => sub_res(*e, refs).into(), + Strong(e) => sub_res(*e, refs).into(), + Literal(e) => Literal(e), + Reference(mut e) => { + if e.extra().refuri.is_none() { + if let Some(uri) = refs.target_url(&e.extra().refname) { + e.extra_mut().refuri = Some(uri.clone()); + } + } + (*e).into() + } + FootnoteReference(e) => sub_res(*e, refs).into(), + CitationReference(e) => sub_res(*e, refs).into(), + SubstitutionReference(e) => match refs.substitution(&e.extra().refname) { + Some(Substitution { + content, + ltrim, + rtrim, + }) => { + // (level 3 system message). + // TODO: ltrim and rtrim. + if *ltrim || *rtrim { + dbg!(content, ltrim, rtrim); + } + return content.clone(); + } + None => { + // Undefined substitution name (level 3 system message). + // TODO: This replaces the reference by a Problematic node. + // The corresponding SystemMessage node should go in a generated + // section with class "system-messages" at the end of the document. + use document_tree::Problematic; + let mut replacement: Box<Problematic> = Box::default(); + replacement + .children_mut() + .push(c::TextOrInlineElement::String(Box::new(format!( + "|{}|", + e.extra().refname[0].0 + )))); + // TODO: Create an ID for replacement for the system_message to reference. + // TODO: replacement.refid pointing to the system_message. + Problematic(replacement) + } + }, + TitleReference(e) => sub_res(*e, refs).into(), + Abbreviation(e) => sub_res(*e, refs).into(), + Acronym(e) => sub_res(*e, refs).into(), + Superscript(e) => sub_res(*e, refs).into(), + Subscript(e) => sub_res(*e, refs).into(), + Inline(e) => sub_res(*e, refs).into(), + Problematic(e) => sub_res(*e, refs).into(), + Generated(e) => sub_res(*e, refs).into(), + Math(e) => Math(e), + TargetInline(e) => TargetInline(e), + RawInline(e) => RawInline(e), + ImageInline(e) => ImageInline(e), + }] + } } impl ResolvableRefs for c::AuthorInfo { - fn populate_targets(&self, refs: &mut TargetsCollected) { - use c::AuthorInfo::*; - match self { - Author(e) => sub_pop(&**e, refs), - Organization(e) => sub_pop(&**e, refs), - Address(e) => sub_pop(&**e, refs), - Contact(e) => sub_pop(&**e, refs), - } - } - fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { - use c::AuthorInfo::*; - vec![match self { - Author(e) => sub_res(*e, refs).into(), - Organization(e) => sub_res(*e, refs).into(), - Address(e) => sub_res(*e, refs).into(), - Contact(e) => sub_res(*e, refs).into(), - }] - } + fn populate_targets(&self, refs: &mut TargetsCollected) { + use c::AuthorInfo::*; + match self { + Author(e) => sub_pop(&**e, refs), + Organization(e) => sub_pop(&**e, refs), + Address(e) => sub_pop(&**e, refs), + Contact(e) => sub_pop(&**e, refs), + } + } + fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { + use c::AuthorInfo::*; + vec![match self { + Author(e) => sub_res(*e, refs).into(), + Organization(e) => sub_res(*e, refs).into(), + Address(e) => sub_res(*e, refs).into(), + Contact(e) => sub_res(*e, refs).into(), + }] + } } impl ResolvableRefs for c::DecorationElement { - fn populate_targets(&self, refs: &mut TargetsCollected) { - use c::DecorationElement::*; - match self { - Header(e) => sub_pop(&**e, refs), - Footer(e) => sub_pop(&**e, refs), - } - } - fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { - use c::DecorationElement::*; - vec![match self { - Header(e) => sub_res(*e, refs).into(), - Footer(e) => sub_res(*e, refs).into(), - }] - } + fn populate_targets(&self, refs: &mut TargetsCollected) { + use c::DecorationElement::*; + match self { + Header(e) => sub_pop(&**e, refs), + Footer(e) => sub_pop(&**e, refs), + } + } + fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { + use c::DecorationElement::*; + vec![match self { + Header(e) => sub_res(*e, refs).into(), + Footer(e) => sub_res(*e, refs).into(), + }] + } } impl ResolvableRefs for c::SubTopic { - fn populate_targets(&self, refs: &mut TargetsCollected) { - use c::SubTopic::*; - match self { - Title(e) => sub_pop(&**e, refs), - BodyElement(e) => e.populate_targets(refs), - } - } - fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { - use c::SubTopic::*; - match self { - Title(e) => vec![sub_res(*e, refs).into()], - BodyElement(e) => e.resolve_refs(refs).drain(..).map(Into::into).collect(), - } - } + fn populate_targets(&self, refs: &mut TargetsCollected) { + use c::SubTopic::*; + match self { + Title(e) => sub_pop(&**e, refs), + BodyElement(e) => e.populate_targets(refs), + } + } + fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { + use c::SubTopic::*; + match self { + Title(e) => vec![sub_res(*e, refs).into()], + BodyElement(e) => e.resolve_refs(refs).drain(..).map(Into::into).collect(), + } + } } impl ResolvableRefs for c::SubSidebar { - fn populate_targets(&self, refs: &mut TargetsCollected) { - use c::SubSidebar::*; - match self { - Topic(e) => sub_pop(&**e, refs), - Title(e) => sub_pop(&**e, refs), - Subtitle(e) => sub_pop(&**e, refs), - BodyElement(e) => e.populate_targets(refs), - } - } - fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { - use c::SubSidebar::*; - vec![match self { - Topic(e) => sub_res(*e, refs).into(), - Title(e) => sub_res(*e, refs).into(), - Subtitle(e) => sub_res(*e, refs).into(), - BodyElement(e) => return e.resolve_refs(refs).drain(..).map(Into::into).collect(), - }] - } + fn populate_targets(&self, refs: &mut TargetsCollected) { + use c::SubSidebar::*; + match self { + Topic(e) => sub_pop(&**e, refs), + Title(e) => sub_pop(&**e, refs), + Subtitle(e) => sub_pop(&**e, refs), + BodyElement(e) => e.populate_targets(refs), + } + } + fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { + use c::SubSidebar::*; + vec![match self { + Topic(e) => sub_res(*e, refs).into(), + Title(e) => sub_res(*e, refs).into(), + Subtitle(e) => sub_res(*e, refs).into(), + BodyElement(e) => return e.resolve_refs(refs).drain(..).map(Into::into).collect(), + }] + } } impl ResolvableRefs for c::SubDLItem { - fn populate_targets(&self, refs: &mut TargetsCollected) { - use c::SubDLItem::*; - match self { - Term(e) => sub_pop(&**e, refs), - Classifier(e) => sub_pop(&**e, refs), - Definition(e) => sub_pop(&**e, refs), - } - } - fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { - use c::SubDLItem::*; - vec![match self { - Term(e) => sub_res(*e, refs).into(), - Classifier(e) => sub_res(*e, refs).into(), - Definition(e) => sub_res(*e, refs).into(), - }] - } + fn populate_targets(&self, refs: &mut TargetsCollected) { + use c::SubDLItem::*; + match self { + Term(e) => sub_pop(&**e, refs), + Classifier(e) => sub_pop(&**e, refs), + Definition(e) => sub_pop(&**e, refs), + } + } + fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { + use c::SubDLItem::*; + vec![match self { + Term(e) => sub_res(*e, refs).into(), + Classifier(e) => sub_res(*e, refs).into(), + Definition(e) => sub_res(*e, refs).into(), + }] + } } impl ResolvableRefs for c::SubField { - fn populate_targets(&self, refs: &mut TargetsCollected) { - use c::SubField::*; - match self { - FieldName(e) => sub_pop(&**e, refs), - FieldBody(e) => sub_pop(&**e, refs), - } - } - fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { - use c::SubField::*; - vec![match self { - FieldName(e) => sub_res(*e, refs).into(), - FieldBody(e) => sub_res(*e, refs).into(), - }] - } + fn populate_targets(&self, refs: &mut TargetsCollected) { + use c::SubField::*; + match self { + FieldName(e) => sub_pop(&**e, refs), + FieldBody(e) => sub_pop(&**e, refs), + } + } + fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { + use c::SubField::*; + vec![match self { + FieldName(e) => sub_res(*e, refs).into(), + FieldBody(e) => sub_res(*e, refs).into(), + }] + } } impl ResolvableRefs for c::SubOptionListItem { - fn populate_targets(&self, refs: &mut TargetsCollected) { - use c::SubOptionListItem::*; - match self { - OptionGroup(e) => sub_sub_pop(&**e, refs), - Description(e) => sub_pop(&**e, refs), - } - } - fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { - use c::SubOptionListItem::*; - vec![match self { - OptionGroup(e) => sub_sub_res(*e, refs).into(), - Description(e) => sub_res(*e, refs).into(), - }] - } + fn populate_targets(&self, refs: &mut TargetsCollected) { + use c::SubOptionListItem::*; + match self { + OptionGroup(e) => sub_sub_pop(&**e, refs), + Description(e) => sub_pop(&**e, refs), + } + } + fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { + use c::SubOptionListItem::*; + vec![match self { + OptionGroup(e) => sub_sub_res(*e, refs).into(), + Description(e) => sub_res(*e, refs).into(), + }] + } } impl ResolvableRefs for c::SubOption { - fn populate_targets(&self, _: &mut TargetsCollected) {} - fn resolve_refs(self, _: &TargetsCollected) -> Vec<Self> { vec![self] } + fn populate_targets(&self, _: &mut TargetsCollected) {} + fn resolve_refs(self, _: &TargetsCollected) -> Vec<Self> { + vec![self] + } } impl ResolvableRefs for c::SubLineBlock { - fn populate_targets(&self, refs: &mut TargetsCollected) { - use c::SubLineBlock::*; - match self { - LineBlock(e) => sub_pop(&**e, refs), - Line(e) => sub_pop(&**e, refs), - } - } - fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { - use c::SubLineBlock::*; - vec![match self { - LineBlock(e) => sub_res(*e, refs).into(), - Line(e) => sub_res(*e, refs).into(), - }] - } + fn populate_targets(&self, refs: &mut TargetsCollected) { + use c::SubLineBlock::*; + match self { + LineBlock(e) => sub_pop(&**e, refs), + Line(e) => sub_pop(&**e, refs), + } + } + fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { + use c::SubLineBlock::*; + vec![match self { + LineBlock(e) => sub_res(*e, refs).into(), + Line(e) => sub_res(*e, refs).into(), + }] + } } impl ResolvableRefs for c::SubBlockQuote { - fn populate_targets(&self, refs: &mut TargetsCollected) { - use c::SubBlockQuote::*; - match self { - Attribution(e) => sub_pop(&**e, refs), - BodyElement(e) => e.populate_targets(refs), - } - } - fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { - use c::SubBlockQuote::*; - match self { - Attribution(e) => vec![sub_res(*e, refs).into()], - BodyElement(e) => e.resolve_refs(refs).drain(..).map(Into::into).collect(), - } - } + fn populate_targets(&self, refs: &mut TargetsCollected) { + use c::SubBlockQuote::*; + match self { + Attribution(e) => sub_pop(&**e, refs), + BodyElement(e) => e.populate_targets(refs), + } + } + fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { + use c::SubBlockQuote::*; + match self { + Attribution(e) => vec![sub_res(*e, refs).into()], + BodyElement(e) => e.resolve_refs(refs).drain(..).map(Into::into).collect(), + } + } } impl ResolvableRefs for c::SubFootnote { - fn populate_targets(&self, refs: &mut TargetsCollected) { - use c::SubFootnote::*; - match self { - Label(e) => sub_pop(&**e, refs), - BodyElement(e) => e.populate_targets(refs), - } - } - fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { - use c::SubFootnote::*; - match self { - Label(e) => vec![sub_res(*e, refs).into()], - BodyElement(e) => e.resolve_refs(refs).drain(..).map(Into::into).collect(), - } - } + fn populate_targets(&self, refs: &mut TargetsCollected) { + use c::SubFootnote::*; + match self { + Label(e) => sub_pop(&**e, refs), + BodyElement(e) => e.populate_targets(refs), + } + } + fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { + use c::SubFootnote::*; + match self { + Label(e) => vec![sub_res(*e, refs).into()], + BodyElement(e) => e.resolve_refs(refs).drain(..).map(Into::into).collect(), + } + } } impl ResolvableRefs for c::SubFigure { - fn populate_targets(&self, refs: &mut TargetsCollected) { - use c::SubFigure::*; - match self { - Caption(e) => sub_pop(&**e, refs), - Legend(e) => sub_pop(&**e, refs), - BodyElement(e) => e.populate_targets(refs), - } - } - fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { - use c::SubFigure::*; - vec![match self { - Caption(e) => sub_res(*e, refs).into(), - Legend(e) => sub_res(*e, refs).into(), - BodyElement(e) => return e.resolve_refs(refs).drain(..).map(Into::into).collect(), - }] - } + fn populate_targets(&self, refs: &mut TargetsCollected) { + use c::SubFigure::*; + match self { + Caption(e) => sub_pop(&**e, refs), + Legend(e) => sub_pop(&**e, refs), + BodyElement(e) => e.populate_targets(refs), + } + } + fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { + use c::SubFigure::*; + vec![match self { + Caption(e) => sub_res(*e, refs).into(), + Legend(e) => sub_res(*e, refs).into(), + BodyElement(e) => return e.resolve_refs(refs).drain(..).map(Into::into).collect(), + }] + } } impl ResolvableRefs for c::SubTable { - fn populate_targets(&self, refs: &mut TargetsCollected) { - use c::SubTable::*; - match self { - Title(e) => sub_pop(&**e, refs), - TableGroup(e) => sub_pop(&**e, refs), - } - } - fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { - use c::SubTable::*; - vec![match self { - Title(e) => sub_res(*e, refs).into(), - TableGroup(e) => sub_res(*e, refs).into(), - }] - } + fn populate_targets(&self, refs: &mut TargetsCollected) { + use c::SubTable::*; + match self { + Title(e) => sub_pop(&**e, refs), + TableGroup(e) => sub_pop(&**e, refs), + } + } + fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { + use c::SubTable::*; + vec![match self { + Title(e) => sub_res(*e, refs).into(), + TableGroup(e) => sub_res(*e, refs).into(), + }] + } } impl ResolvableRefs for c::SubTableGroup { - fn populate_targets(&self, refs: &mut TargetsCollected) { - use c::SubTableGroup::*; - match self { - TableColspec(_) => { - unimplemented!(); - }, - TableHead(e) => { - for c in e.children() { - sub_sub_pop(c, refs); - } - }, - TableBody(e) => { - for c in e.children() { - sub_sub_pop(c, refs); - } - }, - } - } - fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { - use c::SubTableGroup::*; - vec![match self { - TableColspec(e) => TableColspec(e), - TableHead(mut e) => { - let new: Vec<_> = e.children_mut().drain(..).map(|c| sub_sub_res(c, refs)).collect(); - e.children_mut().extend(new); - TableHead(e) - }, - TableBody(mut e) => { - let new: Vec<_> = e.children_mut().drain(..).map(|c| sub_sub_res(c, refs)).collect(); - e.children_mut().extend(new); - TableBody(e) - }, - }] - } + fn populate_targets(&self, refs: &mut TargetsCollected) { + use c::SubTableGroup::*; + match self { + TableColspec(_) => { + unimplemented!(); + } + TableHead(e) => { + for c in e.children() { + sub_sub_pop(c, refs); + } + } + TableBody(e) => { + for c in e.children() { + sub_sub_pop(c, refs); + } + } + } + } + fn resolve_refs(self, refs: &TargetsCollected) -> Vec<Self> { + use c::SubTableGroup::*; + vec![match self { + TableColspec(e) => TableColspec(e), + TableHead(mut e) => { + let new: Vec<_> = e + .children_mut() + .drain(..) + .map(|c| sub_sub_res(c, refs)) + .collect(); + e.children_mut().extend(new); + TableHead(e) + } + TableBody(mut e) => { + let new: Vec<_> = e + .children_mut() + .drain(..) + .map(|c| sub_sub_res(c, refs)) + .collect(); + e.children_mut().extend(new); + TableBody(e) + } + }] + } } diff --git a/parser/src/tests.rs b/parser/src/tests.rs index ffe0ee7..b5dfe6a 100644 --- a/parser/src/tests.rs +++ b/parser/src/tests.rs @@ -5,103 +5,103 @@ use crate::pest_rst::{RstParser, Rule}; #[test] fn plain() { - parses_to! { - parser: RstParser, - input: "line\n", - rule: Rule::paragraph, - tokens: [ - paragraph(0, 4, [ - str(0, 4) - ]) - ] - }; + parses_to! { + parser: RstParser, + input: "line\n", + rule: Rule::paragraph, + tokens: [ + paragraph(0, 4, [ + str(0, 4) + ]) + ] + }; } #[test] fn emph_only() { - parses_to! { - parser: RstParser, - input: "*emphasis*", - rule: Rule::emph_outer, - tokens: [ - emph(1, 9, [str_nested(1, 9)]) - ] - }; + parses_to! { + parser: RstParser, + input: "*emphasis*", + rule: Rule::emph_outer, + tokens: [ + emph(1, 9, [str_nested(1, 9)]) + ] + }; } #[test] fn emph() { - parses_to! { - parser: RstParser, - input: "line *with markup*\n", - rule: Rule::paragraph, - tokens: [ - paragraph(0, 18, [ - str(0, 5), - emph(6, 17, [str_nested(6, 17)]), - ]) - ] - }; + parses_to! { + parser: RstParser, + input: "line *with markup*\n", + rule: Rule::paragraph, + tokens: [ + paragraph(0, 18, [ + str(0, 5), + emph(6, 17, [str_nested(6, 17)]), + ]) + ] + }; } #[test] fn title() { - parses_to! { - parser: RstParser, - input: "\ + parses_to! { + parser: RstParser, + input: "\ Title ===== ", - rule: Rule::title, - tokens: [ - title(0, 12, [ title_single(0, 12, [ - line(0, 6, [ str(0, 5) ]), - adornments(6, 11), - ]) ]) - ] - }; + rule: Rule::title, + tokens: [ + title(0, 12, [ title_single(0, 12, [ + line(0, 6, [ str(0, 5) ]), + adornments(6, 11), + ]) ]) + ] + }; } #[test] fn title_overline() { - parses_to! { - parser: RstParser, - input: "\ + parses_to! { + parser: RstParser, + input: "\ ----- Title ----- ", - rule: Rule::title, - tokens: [ - title(0, 17, [ title_double(0, 17, [ - adornments(0, 5), - line(6, 12, [ str(6, 11) ]), - ]) ]) - ] - }; + rule: Rule::title, + tokens: [ + title(0, 17, [ title_double(0, 17, [ + adornments(0, 5), + line(6, 12, [ str(6, 11) ]), + ]) ]) + ] + }; } #[allow(clippy::cognitive_complexity)] #[test] fn two_targets() { - parses_to! { - parser: RstParser, - input: "\ + parses_to! { + parser: RstParser, + input: "\ .. _a: http://example.com .. _`b_`: https://example.org ", - rule: Rule::document, - tokens: [ - target(0, 26, [ - target_name_uq(4, 5), - link_target(7, 25), - ]), - target(26, 56, [ - target_name_qu(31, 33), - link_target(36, 55), - ]), - ] - }; + rule: Rule::document, + tokens: [ + target(0, 26, [ + target_name_uq(4, 5), + link_target(7, 25), + ]), + target(26, 56, [ + target_name_qu(31, 33), + link_target(36, 55), + ]), + ] + }; } #[test] @@ -119,9 +119,9 @@ fn inline_code_literal_with_underscore() { #[allow(clippy::cognitive_complexity)] #[test] fn admonitions() { - parses_to! { - parser: RstParser, - input: "\ + parses_to! { + parser: RstParser, + input: "\ .. note:: Just next line .. admonition:: In line title @@ -130,30 +130,30 @@ fn admonitions() { .. danger:: Just this line ", - rule: Rule::document, - tokens: [ - admonition_gen(0, 27, [ - admonition_type(3, 7), - paragraph(13, 27, [ str(13, 27) ]), - ]), - admonition(28, 71, [ - line(43, 58, [ str(43, 57) ]), - paragraph(62, 71, [ str(62, 71) ]), - ]), - admonition_gen(73, 100, [ - admonition_type(76, 82), - line(84, 100, [ str(84, 99) ]), - ]), - ] - }; + rule: Rule::document, + tokens: [ + admonition_gen(0, 27, [ + admonition_type(3, 7), + paragraph(13, 27, [ str(13, 27) ]), + ]), + admonition(28, 71, [ + line(43, 58, [ str(43, 57) ]), + paragraph(62, 71, [ str(62, 71) ]), + ]), + admonition_gen(73, 100, [ + admonition_type(76, 82), + line(84, 100, [ str(84, 99) ]), + ]), + ] + }; } #[allow(clippy::cognitive_complexity)] #[test] fn literal_block() { - parses_to! { - parser: RstParser, - input: "\ + parses_to! { + parser: RstParser, + input: "\ :: print('x') @@ -162,26 +162,26 @@ fn literal_block() { The end ", - rule: Rule::document, - tokens: [ - literal_block(0, 36, [ - literal_lines(7, 36, [ - literal_line(7, 18), - literal_line_blank(18, 19), - literal_line(22, 36), - ]), - ]), - paragraph(37, 44, [ str(37, 44) ]), - ] - }; + rule: Rule::document, + tokens: [ + literal_block(0, 36, [ + literal_lines(7, 36, [ + literal_line(7, 18), + literal_line_blank(18, 19), + literal_line(22, 36), + ]), + ]), + paragraph(37, 44, [ str(37, 44) ]), + ] + }; } #[allow(clippy::cognitive_complexity)] #[test] fn code_directive() { - parses_to! { - parser: RstParser, - input: "\ + parses_to! { + parser: RstParser, + input: "\ .. code:: Single line @@ -194,30 +194,30 @@ fn code_directive() { The end ", - rule: Rule::document, - tokens: [ - code_directive(0, 26, [ - literal_lines(14, 26, [ literal_line(14, 26) ]), - ]), - code_directive(27, 83, [ - source(43, 49), - literal_lines(54, 83, [ - literal_line(54, 65), - literal_line_blank(65, 66), - literal_line(69, 83), - ]), - ]), - paragraph(84, 91, [ str(84, 91) ]), - ] - }; + rule: Rule::document, + tokens: [ + code_directive(0, 26, [ + literal_lines(14, 26, [ literal_line(14, 26) ]), + ]), + code_directive(27, 83, [ + source(43, 49), + literal_lines(54, 83, [ + literal_line(54, 65), + literal_line_blank(65, 66), + literal_line(69, 83), + ]), + ]), + paragraph(84, 91, [ str(84, 91) ]), + ] + }; } #[allow(clippy::cognitive_complexity)] #[test] fn raw() { - parses_to! { - parser: RstParser, - input: "\ + parses_to! { + parser: RstParser, + input: "\ .. raw:: html hello <span>world</span> @@ -230,33 +230,33 @@ fn raw() { The end ", - rule: Rule::document, - tokens: [ - raw_directive(0, 43, [ - raw_output_format(9, 13), - raw_block(18, 43, [ - raw_line(18, 43), - ]), - ]), - raw_directive(44, 100, [ - raw_output_format(53, 57), - raw_block(62, 100, [ - raw_line(62, 79), - raw_line_blank(79, 80), - raw_line(83, 100), - ]), - ]), - paragraph(101, 108, [ str(101, 108) ]), - ] - }; + rule: Rule::document, + tokens: [ + raw_directive(0, 43, [ + raw_output_format(9, 13), + raw_block(18, 43, [ + raw_line(18, 43), + ]), + ]), + raw_directive(44, 100, [ + raw_output_format(53, 57), + raw_block(62, 100, [ + raw_line(62, 79), + raw_line_blank(79, 80), + raw_line(83, 100), + ]), + ]), + paragraph(101, 108, [ str(101, 108) ]), + ] + }; } #[allow(clippy::cognitive_complexity)] #[test] fn comments() { - parses_to! { - parser: RstParser, - input: "\ + parses_to! { + parser: RstParser, + input: "\ .. This is a comment .. @@ -280,118 +280,117 @@ fn comments() { .. Comments can also be run-in like this ", - rule: Rule::document, - tokens: [ - block_comment(0, 22, [ - comment_line(3, 21), - ]), - block_comment(22, 43, [ - comment_line(28, 42), - ]), - block_comment(43, 63, [ - comment_line(49, 63), - ]), - block_comment(63, 81, [ - comment_line(69, 81), - ]), - block_comment(81, 99, [ - comment_line(87, 99), - ]), - block_comment(99, 121, [ - comment_line(105, 121), - ]), - block_comment(121, 216, [ - comment_line(124, 139), - comment_line_blank(139, 140), - comment_line(143, 163), - comment_line(166, 195), - comment_line_blank(195, 196), - comment_line(199, 216), - ]), - block_comment(216, 219), - block_comment(219, 263, [ - comment_line(222, 243), - comment_line(246, 263), - ]), - ] - }; + rule: Rule::document, + tokens: [ + block_comment(0, 22, [ + comment_line(3, 21), + ]), + block_comment(22, 43, [ + comment_line(28, 42), + ]), + block_comment(43, 63, [ + comment_line(49, 63), + ]), + block_comment(63, 81, [ + comment_line(69, 81), + ]), + block_comment(81, 99, [ + comment_line(87, 99), + ]), + block_comment(99, 121, [ + comment_line(105, 121), + ]), + block_comment(121, 216, [ + comment_line(124, 139), + comment_line_blank(139, 140), + comment_line(143, 163), + comment_line(166, 195), + comment_line_blank(195, 196), + comment_line(199, 216), + ]), + block_comment(216, 219), + block_comment(219, 263, [ + comment_line(222, 243), + comment_line(246, 263), + ]), + ] + }; } #[allow(clippy::cognitive_complexity)] #[test] fn substitutions() { - parses_to! { - parser: RstParser, - input: "\ + parses_to! { + parser: RstParser, + input: "\ A |subst| in-line .. |subst| replace:: substitution .. |subst2| replace:: it can also be hanging ", - rule: Rule::document, - tokens: [ - paragraph(0, 17, [ - str(0, 2), - substitution_name(3, 8), - str(9, 17), - ]), - substitution_def(19, 52, [ - substitution_name(23, 28), - replace(30, 52, [ paragraph(40, 52, [str(40, 52)]) ]), - ]), - substitution_def(53, 101, [ - substitution_name(57, 63), - replace(65, 101, [ paragraph(75, 101, [ - str(75, 86), ws_newline(86, 87), - str(88, 100), - ]) ]), - ]), - ] - }; + rule: Rule::document, + tokens: [ + paragraph(0, 17, [ + str(0, 2), + substitution_name(3, 8), + str(9, 17), + ]), + substitution_def(19, 52, [ + substitution_name(23, 28), + replace(30, 52, [ paragraph(40, 52, [str(40, 52)]) ]), + ]), + substitution_def(53, 101, [ + substitution_name(57, 63), + replace(65, 101, [ paragraph(75, 101, [ + str(75, 86), ws_newline(86, 87), + str(88, 100), + ]) ]), + ]), + ] + }; } #[test] fn substitution_in_literal() { - parses_to! { - parser: RstParser, - input: "Just ``|code|``, really ``*code* |only|``", - rule: Rule::document, - tokens: [ - paragraph(0, 41, [ - str(0, 5), - literal(7, 13), - str(15, 24), - literal(26, 39), - ]), - ] - }; + parses_to! { + parser: RstParser, + input: "Just ``|code|``, really ``*code* |only|``", + rule: Rule::document, + tokens: [ + paragraph(0, 41, [ + str(0, 5), + literal(7, 13), + str(15, 24), + literal(26, 39), + ]), + ] + }; } - #[allow(clippy::cognitive_complexity)] #[test] fn substitution_image() { - parses_to! { - parser: RstParser, - input: "\ + parses_to! { + parser: RstParser, + input: "\ .. |subst| image:: thing.png :target: foo.html ", - rule: Rule::document, - tokens: [ - substitution_def(0, 50, [ - substitution_name(4, 9), - image(11, 50, [ - line(18, 29, [ str(18, 28) ]), - image_option(32, 50, [ - image_opt_name(33, 39), - line(40, 50, [ str(40, 49) ]), - ]), - ]), - ]), - ] - }; + rule: Rule::document, + tokens: [ + substitution_def(0, 50, [ + substitution_name(4, 9), + image(11, 50, [ + line(18, 29, [ str(18, 28) ]), + image_option(32, 50, [ + image_opt_name(33, 39), + line(40, 50, [ str(40, 49) ]), + ]), + ]), + ]), + ] + }; } // TODO: test images @@ -399,9 +398,9 @@ fn substitution_image() { #[allow(clippy::cognitive_complexity)] #[test] fn nested_lists() { - parses_to! { - parser: RstParser, - input: "\ + parses_to! { + parser: RstParser, + input: "\ paragraph - item 1 @@ -413,27 +412,27 @@ paragraph - nested item 2 - nested item 3 ", - rule: Rule::document, - tokens: [ - paragraph(0, 9, [ str(0, 9) ]), - bullet_list(11, 131, [ - bullet_item(11, 21, [ - line(14, 21, [ str(14, 20) ]), - ]), - bullet_item(21, 131, [ - line(24, 31, [ str(24, 30) ]), - paragraph(34, 74, [ - str(34, 43), ws_newline(43, 44), - str(47, 58), ws_newline(58, 59), - str(62, 73), - ]), - bullet_list(77, 131, [ - bullet_item( 77, 93, [ line( 79, 93, [str( 79, 92)]) ]), - bullet_item( 96, 112, [ line( 98, 112, [str( 98, 111)]) ]), - bullet_item(115, 131, [ line(117, 131, [str(117, 130)]) ]), - ]), - ]), - ]), - ] - } + rule: Rule::document, + tokens: [ + paragraph(0, 9, [ str(0, 9) ]), + bullet_list(11, 131, [ + bullet_item(11, 21, [ + line(14, 21, [ str(14, 20) ]), + ]), + bullet_item(21, 131, [ + line(24, 31, [ str(24, 30) ]), + paragraph(34, 74, [ + str(34, 43), ws_newline(43, 44), + str(47, 58), ws_newline(58, 59), + str(62, 73), + ]), + bullet_list(77, 131, [ + bullet_item( 77, 93, [ line( 79, 93, [str( 79, 92)]) ]), + bullet_item( 96, 112, [ line( 98, 112, [str( 98, 111)]) ]), + bullet_item(115, 131, [ line(117, 131, [str(117, 130)]) ]), + ]), + ]), + ]), + ] + } } diff --git a/parser/src/token.rs b/parser/src/token.rs index b3b7bac..1b1d49d 100644 --- a/parser/src/token.rs +++ b/parser/src/token.rs @@ -1,16 +1,68 @@ //http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html#bullet-lists // *, +, -, •, ‣, ⁃ -pub enum BulletListType { Ast, Plus, Minus, Bullet, TriBullet, HyphenBullet } +pub enum BulletListType { + Ast, + Plus, + Minus, + Bullet, + TriBullet, + HyphenBullet, +} // 1, A, a, I, i -pub enum EnumListChar { Arabic, AlphaUpper, AlphaLower, RomanUpper, RomanLower, Auto } +pub enum EnumListChar { + Arabic, + AlphaUpper, + AlphaLower, + RomanUpper, + RomanLower, + Auto, +} // 1., (1), 1) -pub enum EnumListType { Period, ParenEnclosed, Paren } +pub enum EnumListType { + Period, + ParenEnclosed, + Paren, +} // ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~ pub enum AdornmentChar { - Bang, DQuote, Hash, Dollar, Percent, Amp, SQuote, LParen, RParen, Ast, Plus, Comma, - Minus, Period, Slash, Colon, Semicolon, Less, Eq, More, Question, At, LBrack, - Backslash, RBrack, Caret, Underscore, Backtick, LBrace, Pipe, RBrace, Tilde, + Bang, + DQuote, + Hash, + Dollar, + Percent, + Amp, + SQuote, + LParen, + RParen, + Ast, + Plus, + Comma, + Minus, + Period, + Slash, + Colon, + Semicolon, + Less, + Eq, + More, + Question, + At, + LBrack, + Backslash, + RBrack, + Caret, + Underscore, + Backtick, + LBrace, + Pipe, + RBrace, + Tilde, } // [1], [#], [*], [#foo] -pub enum FootnoteType { Numbered(usize), AutoNumber, AutoSymbol, AutoNamed(String) } +pub enum FootnoteType { + Numbered(usize), + AutoNumber, + AutoSymbol, + AutoNamed(String), +} diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml index 051d175..20b92bc 100644 --- a/renderer/Cargo.toml +++ b/renderer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = 'rst_renderer' -version = '0.3.1' +version = '0.4.0' authors = ['Philipp A. <flying-sheep@web.de>'] edition = '2018' description = 'a reStructuredText renderer' @@ -12,7 +12,7 @@ homepage = 'https://github.com/flying-sheep/rust-rst' repository = 'https://github.com/flying-sheep/rust-rst' [dependencies] -document_tree = { path = '../document_tree', version = '0.3.0' } +document_tree = { path = '../document_tree', version = '0.4.0' } failure = '0.1.6' serde_json = '1.0.44' diff --git a/renderer/src/html.rs b/renderer/src/html.rs index 4244349..feef29d 100644 --- a/renderer/src/html.rs +++ b/renderer/src/html.rs @@ -7,406 +7,601 @@ use failure::Error; // use crate::url::Url; use document_tree::{ - Document,Element,HasChildren,ExtraAttributes, - elements as e, - extra_attributes as a, - element_categories as c, - attribute_types as at, + attribute_types as at, element_categories as c, elements as e, extra_attributes as a, Document, + Element, ExtraAttributes, HasChildren, }; - // static FOOTNOTE_SYMBOLS: [char; 10] = ['*', '†', '‡', '§', '¶', '#', '♠', '♥', '♦', '♣']; -pub fn render_html<W>(document: &Document, stream: W, standalone: bool) -> Result<(), Error> where W: Write { - let mut renderer = HTMLRenderer { stream, level: 0 }; - if standalone { - document.render_html(&mut renderer) - } else { - for c in document.children() { - (*c).render_html(&mut renderer)?; - writeln!(renderer.stream)?; - } - Ok(()) - } +pub fn render_html<W>(document: &Document, stream: W, standalone: bool) -> Result<(), Error> +where + W: Write, +{ + let mut renderer = HTMLRenderer { stream, level: 0 }; + if standalone { + document.render_html(&mut renderer) + } else { + for c in document.children() { + (*c).render_html(&mut renderer)?; + writeln!(renderer.stream)?; + } + Ok(()) + } } fn escape_html(text: &str) -> String { - text.replace('&', "&") - .replace('<', "<") - .replace('>', ">") - .replace('"', """) + text.replace('&', "&") + .replace('<', "<") + .replace('>', ">") + .replace('"', """) } -struct HTMLRenderer<W> where W: Write { - stream: W, - level: u8, +struct HTMLRenderer<W> +where + W: Write, +{ + stream: W, + level: u8, } trait HTMLRender { - fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write; + fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> + where + W: Write; } macro_rules! impl_html_render_cat {($cat:ident { $($member:ident),+ }) => { - impl HTMLRender for c::$cat { - fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { - match self {$( - c::$cat::$member(elem) => (**elem).render_html(renderer), - )+} - } - } + impl HTMLRender for c::$cat { + fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { + match self {$( + c::$cat::$member(elem) => (**elem).render_html(renderer), + )+} + } + } }} macro_rules! impl_html_render_simple { - ( - $type1:ident => $tag1:ident $( [$($post1:tt)+] )?, - $( $type:ident => $tag:ident $( [$($post:tt)+] )? ),+ - ) => { - impl_html_render_simple!($type1 => $tag1 $([$($post1)+])?); - $( impl_html_render_simple!($type => $tag $([$($post)+])?); )+ - }; - ( $type:ident => $tag:ident ) => { - impl_html_render_simple!($type => $tag[""]); - }; - ( $type:ident => $tag:ident [ $post:expr ] ) => { - impl HTMLRender for e::$type { - fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { - let multiple_children = self.children().len() > 1; - write!(renderer.stream, "<{}", stringify!($tag))?; - if self.classes().len() > 0 { - write!(renderer.stream, " class=\"{}\"", self.classes().join(" "))?; - } - write!(renderer.stream, ">")?; - if multiple_children { write!(renderer.stream, $post)?; } - for c in self.children() { - (*c).render_html(renderer)?; - if multiple_children { write!(renderer.stream, $post)?; } - } - write!(renderer.stream, "</{}>", stringify!($tag))?; - Ok(()) - } - } - }; + ( + $type1:ident => $tag1:ident $( [$($post1:tt)+] )?, + $( $type:ident => $tag:ident $( [$($post:tt)+] )? ),+ + ) => { + impl_html_render_simple!($type1 => $tag1 $([$($post1)+])?); + $( impl_html_render_simple!($type => $tag $([$($post)+])?); )+ + }; + ( $type:ident => $tag:ident ) => { + impl_html_render_simple!($type => $tag[""]); + }; + ( $type:ident => $tag:ident [ $post:expr ] ) => { + impl HTMLRender for e::$type { + fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { + let multiple_children = self.children().len() > 1; + write!(renderer.stream, "<{}", stringify!($tag))?; + if self.classes().len() > 0 { + write!(renderer.stream, " class=\"{}\"", self.classes().join(" "))?; + } + write!(renderer.stream, ">")?; + if multiple_children { write!(renderer.stream, $post)?; } + for c in self.children() { + (*c).render_html(renderer)?; + if multiple_children { write!(renderer.stream, $post)?; } + } + write!(renderer.stream, "</{}>", stringify!($tag))?; + Ok(()) + } + } + }; } macro_rules! impl_html_render_simple_nochildren {( $($type:ident => $tag:ident),+ ) => { $( - impl HTMLRender for e::$type { - fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { - write!(renderer.stream, "<{0}></{0}>", stringify!($tag))?; - Ok(()) - } - } + impl HTMLRender for e::$type { + fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { + write!(renderer.stream, "<{0}></{0}>", stringify!($tag))?; + Ok(()) + } + } )+ }} // Impl impl HTMLRender for Document { - fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { - writeln!(renderer.stream, "<!doctype html><html>")?; - for c in self.children() { - (*c).render_html(renderer)?; - writeln!(renderer.stream)?; - } - writeln!(renderer.stream, "</html>")?; - Ok(()) - } -} - -impl_html_render_cat!(StructuralSubElement { Title, Subtitle, Decoration, Docinfo, SubStructure }); + fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> + where + W: Write, + { + writeln!(renderer.stream, "<!doctype html><html>")?; + for c in self.children() { + (*c).render_html(renderer)?; + writeln!(renderer.stream)?; + } + writeln!(renderer.stream, "</html>")?; + Ok(()) + } +} + +impl_html_render_cat!(StructuralSubElement { + Title, + Subtitle, + Decoration, + Docinfo, + SubStructure +}); impl_html_render_simple!(Subtitle => h2); impl HTMLRender for e::Title { - fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { - let level = if renderer.level > 6 { 6 } else { renderer.level }; - write!(renderer.stream, "<h{0}>", level)?; - for c in self.children() { - (*c).render_html(renderer)?; - } - write!(renderer.stream, "</h{0}>", level)?; - Ok(()) - } + fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> + where + W: Write, + { + let level = if renderer.level > 6 { + 6 + } else { + renderer.level + }; + write!(renderer.stream, "<h{0}>", level)?; + for c in self.children() { + (*c).render_html(renderer)?; + } + write!(renderer.stream, "</h{0}>", level)?; + Ok(()) + } } impl HTMLRender for e::Docinfo { - fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { - // Like “YAML frontmatter” in Markdown - unimplemented!(); - } + fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> + where + W: Write, + { + // Like “YAML frontmatter” in Markdown + unimplemented!(); + } } impl HTMLRender for e::Decoration { - fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { - // Header or footer - unimplemented!(); - } -} - -impl_html_render_cat!(SubStructure { Topic, Sidebar, Transition, Section, BodyElement }); + fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> + where + W: Write, + { + // Header or footer + unimplemented!(); + } +} + +impl_html_render_cat!(SubStructure { + Topic, + Sidebar, + Transition, + Section, + BodyElement +}); impl_html_render_simple!(Sidebar => aside); impl HTMLRender for e::Section { - fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { - renderer.level += 1; - writeln!(renderer.stream, "<section id=\"{0}\">", self.ids()[0].0)?; - for c in self.children() { - (*c).render_html(renderer)?; - writeln!(renderer.stream)?; - } - write!(renderer.stream, "</section>")?; - renderer.level -= 1; - Ok(()) - } + fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> + where + W: Write, + { + renderer.level += 1; + writeln!(renderer.stream, "<section id=\"{0}\">", self.ids()[0].0)?; + for c in self.children() { + (*c).render_html(renderer)?; + writeln!(renderer.stream)?; + } + write!(renderer.stream, "</section>")?; + renderer.level -= 1; + Ok(()) + } } impl HTMLRender for e::Transition { - fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { - write!(renderer.stream, "<hr/>")?; - Ok(()) - } + fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> + where + W: Write, + { + write!(renderer.stream, "<hr/>")?; + Ok(()) + } } impl HTMLRender for e::Topic { - fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { - // A mini section with title - unimplemented!(); - } -} - -impl_html_render_cat!(BodyElement { Paragraph, LiteralBlock, DoctestBlock, MathBlock, Rubric, SubstitutionDefinition, Comment, Pending, Target, Raw, Image, Compound, Container, BulletList, EnumeratedList, DefinitionList, FieldList, OptionList, LineBlock, BlockQuote, Admonition, Attention, Hint, Note, Caution, Danger, Error, Important, Tip, Warning, Footnote, Citation, SystemMessage, Figure, Table }); + fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> + where + W: Write, + { + // A mini section with title + unimplemented!(); + } +} + +impl_html_render_cat!(BodyElement { + Paragraph, + LiteralBlock, + DoctestBlock, + MathBlock, + Rubric, + SubstitutionDefinition, + Comment, + Pending, + Target, + Raw, + Image, + Compound, + Container, + BulletList, + EnumeratedList, + DefinitionList, + FieldList, + OptionList, + LineBlock, + BlockQuote, + Admonition, + Attention, + Hint, + Note, + Caution, + Danger, + Error, + Important, + Tip, + Warning, + Footnote, + Citation, + SystemMessage, + Figure, + Table +}); impl_html_render_simple!(Paragraph => p, MathBlock => math, Rubric => a, Compound => p, Container => div, BulletList => ul["\n"], EnumeratedList => ol["\n"], DefinitionList => dl["\n"], FieldList => dl["\n"], OptionList => pre, LineBlock => div["\n"], BlockQuote => blockquote, Admonition => aside, Attention => aside, Hint => aside, Note => aside, Caution => aside, Danger => aside, Error => aside, Important => aside, Tip => aside, Warning => aside, Figure => figure); -impl_html_render_simple_nochildren!(Table => table); //TODO: after implementing the table, move it to elems with children +impl_html_render_simple_nochildren!(Table => table); //TODO: after implementing the table, move it to elems with children // circumvent E0119 -trait IMark {} impl IMark for e::Image {} impl IMark for e::ImageInline {} -impl<I> HTMLRender for I where I: e::Element + a::ExtraAttributes<a::Image> + IMark { - fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { - let extra = self.extra(); - if let Some(ref target) = extra.target { - write!(renderer.stream, "<a href=\"{}\">", escape_html(target.as_str()))?; - } - write!(renderer.stream, "<img")?; - if let Some(ref alt) = extra.alt { - write!(renderer.stream, " alt=\"{}\"", escape_html(alt))?; - } - // TODO: align: Option<AlignHV> - // TODO: height: Option<Measure> - // TODO: width: Option<Measure> - // TODO: scale: Option<u8> - write!(renderer.stream, " src=\"{}\" />", escape_html(extra.uri.as_str()))?; - if extra.target.is_some() { - write!(renderer.stream, "</a>")?; - } - Ok(()) - } +trait IMark {} +impl IMark for e::Image {} +impl IMark for e::ImageInline {} +impl<I> HTMLRender for I +where + I: e::Element + a::ExtraAttributes<a::Image> + IMark, +{ + fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> + where + W: Write, + { + let extra = self.extra(); + if let Some(ref target) = extra.target { + write!( + renderer.stream, + "<a href=\"{}\">", + escape_html(target.as_str()) + )?; + } + write!(renderer.stream, "<img")?; + if let Some(ref alt) = extra.alt { + write!(renderer.stream, " alt=\"{}\"", escape_html(alt))?; + } + // TODO: align: Option<AlignHV> + // TODO: height: Option<Measure> + // TODO: width: Option<Measure> + // TODO: scale: Option<u8> + write!( + renderer.stream, + " src=\"{}\" />", + escape_html(extra.uri.as_str()) + )?; + if extra.target.is_some() { + write!(renderer.stream, "</a>")?; + } + Ok(()) + } } impl HTMLRender for e::LiteralBlock { - fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { - let mut cls_iter = self.classes().iter(); - let is_code = cls_iter.next() == Some(&"code".to_owned()); - write!(renderer.stream, "<pre>")?; - if is_code { // TODO: support those classes not being at the start - if let Some(lang) = cls_iter.next() { - write!(renderer.stream, "<code class=\"language-{}\">", lang)?; - } else { - write!(renderer.stream, "<code>")?; - } - } - for c in self.children() { - (*c).render_html(renderer)?; - } - if is_code { write!(renderer.stream, "</code>")?; } - write!(renderer.stream, "</pre>")?; - Ok(()) - } + fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> + where + W: Write, + { + let mut cls_iter = self.classes().iter(); + let is_code = cls_iter.next() == Some(&"code".to_owned()); + write!(renderer.stream, "<pre>")?; + if is_code { + // TODO: support those classes not being at the start + if let Some(lang) = cls_iter.next() { + write!(renderer.stream, "<code class=\"language-{}\">", lang)?; + } else { + write!(renderer.stream, "<code>")?; + } + } + for c in self.children() { + (*c).render_html(renderer)?; + } + if is_code { + write!(renderer.stream, "</code>")?; + } + write!(renderer.stream, "</pre>")?; + Ok(()) + } } impl HTMLRender for e::DoctestBlock { - fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { - // TODO - unimplemented!(); - } + fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> + where + W: Write, + { + // TODO + unimplemented!(); + } } impl HTMLRender for e::SubstitutionDefinition { - fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { - // TODO: Should those be removed after resolving them - Ok(()) - } + fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> + where + W: Write, + { + // TODO: Should those be removed after resolving them + Ok(()) + } } impl HTMLRender for e::Comment { - fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { - write!(renderer.stream, "<!--")?; - for c in self.children() { - (*c).render_html(renderer)?; - } - write!(renderer.stream, "-->")?; - Ok(()) - } + fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> + where + W: Write, + { + write!(renderer.stream, "<!--")?; + for c in self.children() { + (*c).render_html(renderer)?; + } + write!(renderer.stream, "-->")?; + Ok(()) + } } impl HTMLRender for e::Pending { - fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { - // Will those be resolved by the time we get here? - unimplemented!(); - } + fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> + where + W: Write, + { + // Will those be resolved by the time we get here? + unimplemented!(); + } } impl HTMLRender for e::Target { - fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { - // Should be resolved by now - Ok(()) - } + fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> + where + W: Write, + { + // Should be resolved by now + Ok(()) + } } impl HTMLRender for e::Raw { - fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { - let extra = self.extra(); - if extra.format.contains(&at::NameToken("html".to_owned())) { - for c in self.children() { - write!(renderer.stream, "{}", c)?; - } - } - Ok(()) - } + fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> + where + W: Write, + { + let extra = self.extra(); + if extra.format.contains(&at::NameToken("html".to_owned())) { + for c in self.children() { + write!(renderer.stream, "{}", c)?; + } + } + Ok(()) + } } impl HTMLRender for e::Footnote { - fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { - unimplemented!(); - } + fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> + where + W: Write, + { + unimplemented!(); + } } impl HTMLRender for e::Citation { - fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { - unimplemented!(); - } + fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> + where + W: Write, + { + unimplemented!(); + } } impl HTMLRender for e::SystemMessage { - fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { - write!(renderer.stream, "<figure><caption>System Message</caption>")?; - for c in self.children() { - (*c).render_html(renderer)?; - } - write!(renderer.stream, "</figure>")?; - Ok(()) - } -} - -impl_html_render_cat!(TextOrInlineElement { String, Emphasis, Strong, Literal, Reference, FootnoteReference, CitationReference, SubstitutionReference, TitleReference, Abbreviation, Acronym, Superscript, Subscript, Inline, Problematic, Generated, Math, TargetInline, RawInline, ImageInline }); + fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> + where + W: Write, + { + write!(renderer.stream, "<figure><caption>System Message</caption>")?; + for c in self.children() { + (*c).render_html(renderer)?; + } + write!(renderer.stream, "</figure>")?; + Ok(()) + } +} + +impl_html_render_cat!(TextOrInlineElement { + String, + Emphasis, + Strong, + Literal, + Reference, + FootnoteReference, + CitationReference, + SubstitutionReference, + TitleReference, + Abbreviation, + Acronym, + Superscript, + Subscript, + Inline, + Problematic, + Generated, + Math, + TargetInline, + RawInline, + ImageInline +}); impl_html_render_simple!(Emphasis => em, Strong => strong, Literal => code, FootnoteReference => a, CitationReference => a, TitleReference => a, Abbreviation => abbr, Acronym => acronym, Superscript => sup, Subscript => sub, Inline => span, Math => math, TargetInline => a); impl HTMLRender for String { - fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { - write!(renderer.stream, "{}", escape_html(self))?; - Ok(()) - } + fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> + where + W: Write, + { + write!(renderer.stream, "{}", escape_html(self))?; + Ok(()) + } } impl HTMLRender for e::Reference { - fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { - let extra = self.extra(); - write!(renderer.stream, "<a")?; - if let Some(ref target) = extra.refuri { - write!(renderer.stream, " href=\"{}\"", escape_html(target.as_str()))?; - } - /* - if let Some(ref name) = extra.name { - write!(renderer.stream, " title=\"{}\"", escape_html(&name.0))?; - } - */ - write!(renderer.stream, ">")?; - for c in self.children() { - (*c).render_html(renderer)?; - } - write!(renderer.stream, "</a>")?; - Ok(()) - } + fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> + where + W: Write, + { + let extra = self.extra(); + write!(renderer.stream, "<a")?; + if let Some(ref target) = extra.refuri { + write!( + renderer.stream, + " href=\"{}\"", + escape_html(target.as_str()) + )?; + } + /* + if let Some(ref name) = extra.name { + write!(renderer.stream, " title=\"{}\"", escape_html(&name.0))?; + } + */ + write!(renderer.stream, ">")?; + for c in self.children() { + (*c).render_html(renderer)?; + } + write!(renderer.stream, "</a>")?; + Ok(()) + } } impl HTMLRender for e::SubstitutionReference { - fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { - // Will those be resolved by the time we get here? - unimplemented!(); - } + fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> + where + W: Write, + { + // Will those be resolved by the time we get here? + unimplemented!(); + } } impl HTMLRender for e::Problematic { - fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { - // Broken inline markup leads to insertion of this in docutils - unimplemented!(); - } + fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> + where + W: Write, + { + // Broken inline markup leads to insertion of this in docutils + unimplemented!(); + } } impl HTMLRender for e::Generated { - fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { - // Section numbers and so on - unimplemented!(); - } + fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> + where + W: Write, + { + // Section numbers and so on + unimplemented!(); + } } impl HTMLRender for e::RawInline { - fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { - for c in self.children() { - write!(renderer.stream, "{}", c)?; - } - Ok(()) - } + fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> + where + W: Write, + { + for c in self.children() { + write!(renderer.stream, "{}", c)?; + } + Ok(()) + } } - //--------------\\ //Content Models\\ //--------------\\ impl_html_render_cat!(SubTopic { Title, BodyElement }); -impl_html_render_cat!(SubSidebar { Topic, Title, Subtitle, BodyElement }); +impl_html_render_cat!(SubSidebar { + Topic, + Title, + Subtitle, + BodyElement +}); impl_html_render_simple!(ListItem => li); impl HTMLRender for e::DefinitionListItem { - fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { - // Term→dt, Definition→dd, Classifier→??? - unimplemented!(); - } + fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> + where + W: Write, + { + // Term→dt, Definition→dd, Classifier→??? + unimplemented!(); + } } impl HTMLRender for e::Field { - fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { - // FieldName→dt, FieldBody→dd - unimplemented!(); - } + fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> + where + W: Write, + { + // FieldName→dt, FieldBody→dd + unimplemented!(); + } } impl HTMLRender for e::OptionListItem { - fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { - // OptionGroup→dt(s), Description→dd - unimplemented!(); - } + fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> + where + W: Write, + { + // OptionGroup→dt(s), Description→dd + unimplemented!(); + } } impl_html_render_cat!(SubLineBlock { LineBlock, Line }); impl HTMLRender for e::Line { - fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { - for c in self.children() { - (*c).render_html(renderer)?; - } - write!(renderer.stream, "<br>")?; - Ok(()) - } -} - -impl_html_render_cat!(SubBlockQuote { Attribution, BodyElement }); + fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> + where + W: Write, + { + for c in self.children() { + (*c).render_html(renderer)?; + } + write!(renderer.stream, "<br>")?; + Ok(()) + } +} + +impl_html_render_cat!(SubBlockQuote { + Attribution, + BodyElement +}); impl_html_render_simple!(Attribution => cite); //TODO: correct? -impl_html_render_cat!(SubFigure { Caption, Legend, BodyElement }); +impl_html_render_cat!(SubFigure { + Caption, + Legend, + BodyElement +}); impl_html_render_simple!(Caption => caption); impl HTMLRender for e::Legend { - fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write { - unimplemented!(); - } + fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error> + where + W: Write, + { + unimplemented!(); + } } //------------\\ diff --git a/renderer/src/html/tests.rs b/renderer/src/html/tests.rs index a4ec633..4734d65 100644 --- a/renderer/src/html/tests.rs +++ b/renderer/src/html/tests.rs @@ -5,43 +5,40 @@ use rst_parser::parse; use crate::html::render_html; fn check_renders_to(rst: &str, expected: &str) { - println!("Rendering:\n{}\n---", rst); - let doc = parse(rst).expect("Cannot parse"); - let mut result_data: Vec<u8> = vec![]; - render_html(&doc, &mut result_data, false).expect("Render error"); - let result = String::from_utf8(result_data).expect("Could not decode"); - assert_eq!(result.as_str().trim(), expected); - println!("{}", expected); + println!("Rendering:\n{}\n---", rst); + let doc = parse(rst).expect("Cannot parse"); + let mut result_data: Vec<u8> = vec![]; + render_html(&doc, &mut result_data, false).expect("Render error"); + let result = String::from_utf8(result_data).expect("Could not decode"); + assert_eq!(result.as_str().trim(), expected); + println!("{}", expected); } #[test] fn simple_string() { - check_renders_to( - "Simple String", - "<p>Simple String</p>", - ); + check_renders_to("Simple String", "<p>Simple String</p>"); } #[test] fn simple_string_with_markup() { - check_renders_to( - "Simple String with *emph* and **strong**", - "<p>Simple String with <em>emph</em> and <strong>strong</strong></p>", - ); + check_renders_to( + "Simple String with *emph* and **strong**", + "<p>Simple String with <em>emph</em> and <strong>strong</strong></p>", + ); } #[test] fn inline_literal() { - check_renders_to( - "Simple String with an even simpler ``inline literal``", - "<p>Simple String with an even simpler <code>inline literal</code></p>", - ); + check_renders_to( + "Simple String with an even simpler ``inline literal``", + "<p>Simple String with an even simpler <code>inline literal</code></p>", + ); } /* #[test] fn test_reference_anonymous() { - check_renders_to("\ + check_renders_to("\ A simple `anonymous reference`__ __ http://www.test.com/test_url @@ -53,28 +50,31 @@ __ http://www.test.com/test_url #[test] fn two_paragraphs() { - check_renders_to( - "One paragraph.\n\nTwo paragraphs.", - "<p>One paragraph.</p>\n<p>Two paragraphs.</p>", - ); + check_renders_to( + "One paragraph.\n\nTwo paragraphs.", + "<p>One paragraph.</p>\n<p>Two paragraphs.</p>", + ); } #[test] fn named_reference() { - check_renders_to("\ + check_renders_to( + "\ A simple `named reference`_ with stuff in between the reference and the target. .. _`named reference`: http://www.test.com/test_url -", "\ +", + "\ <p>A simple <a href=\"http://www.test.com/test_url\">named reference</a> with stuff in between the \ reference and the target.</p>\ -"); +", + ); } #[test] fn standalone_hyperlinks() { - check_renders_to("\ + check_renders_to("\ Some http://url and a not_url_scheme:foo that is not supposed to be an url. ", "\ <p>Some <a href=\"http://url/\">http://url</a> and a not_url_scheme:foo that is not supposed to be an url.</p>\ @@ -83,16 +83,20 @@ Some http://url and a not_url_scheme:foo that is not supposed to be an url. #[test] fn substitution() { - check_renders_to("\ + check_renders_to( + "\ A |subst|. .. |subst| replace:: text substitution -", "<p>A text substitution.</p>"); +", + "<p>A text substitution.</p>", + ); } #[test] fn not_substitution_literal() { - check_renders_to("\ + check_renders_to( + "\ hello ``foo.map(|world| world + 42)`` .. |world| replace:: something different @@ -104,15 +108,17 @@ hello ``foo.map(|world| world + 42)`` :: hay! |x| -", "<p>hello <code>foo.map(|world| world + 42)</code></p> +", + "<p>hello <code>foo.map(|world| world + 42)</code></p> <pre><code>foo.map(|world| world + 42)\n</code></pre> -<pre>hay! |x|\n</pre>"); +<pre>hay! |x|\n</pre>", + ); } /* #[test] fn test_section_hierarchy() { - check_renders_to("\ + check_renders_to("\ +++++ Title +++++ @@ -146,7 +152,7 @@ And even more stuff #[test] fn test_docinfo_title() { - check_renders_to("\ + check_renders_to("\ +++++ Title +++++ @@ -169,7 +175,8 @@ Some stuff #[test] fn section_hierarchy() { - check_renders_to("\ + check_renders_to( + "\ +++++ Title +++++ @@ -188,7 +195,8 @@ Another Section ............... And even more stuff -", "\ +", + "\ <section id=\"title\"> <h1>Title</h1> <section id=\"not-a-subtitle\"> @@ -204,12 +212,14 @@ And even more stuff </section> </section> </section>\ -"); +", + ); } #[test] fn many_sections() { - check_renders_to("\ + check_renders_to( + "\ +++++++++ heading 1 +++++++++ @@ -233,7 +243,8 @@ heading 3a ---------- Second detail -", "\ +", + "\ <section id=\"heading-1\"> <h1>heading 1</h1> <section id=\"heading-2\"> @@ -253,26 +264,30 @@ Second detail </section> </section> </section>\ -"); +", + ); } #[test] fn bullet_list() { - check_renders_to("\ + check_renders_to( + "\ * bullet * list -", "\ +", + "\ <ul> <li><p>bullet</p></li> <li><p>list</p></li> </ul>\ -"); +", + ); } /* #[test] fn test_table() { - check_renders_to("\ + check_renders_to("\ .. table:: :align: right @@ -302,25 +317,29 @@ fn test_table() { #[test] fn code() { - check_renders_to("\ + check_renders_to( + "\ .. code:: python def foo(): print('Hi!') - + # comment -", "\ +", + "\ <pre><code class=\"language-python\">def foo(): print('Hi!') # comment </code></pre>\ -"); +", + ); } #[test] fn raw_html() { - check_renders_to("\ + check_renders_to( + "\ .. raw:: html hello <span>world</span> @@ -333,19 +352,22 @@ after .. raw:: something_else into a black hole this goes -", "\ +", + "\ hello <span>world</span> <p>paragraph paragraph</p> <p>after</p>\ -"); +", + ); } #[test] fn comments() { - check_renders_to("\ + check_renders_to( + "\ .. Run-in comment @@ -354,7 +376,8 @@ fn comments() { block-like with blank lines -", "\ +", + "\ <!--Run-in comment --> @@ -362,13 +385,14 @@ comment with blank lines -->\ -"); +", + ); } /* #[test] fn test_field_list() { - check_renders_to("\ + check_renders_to("\ Not a docinfo. :This: .. _target: @@ -398,7 +422,7 @@ Not a docinfo. /* #[test] fn test_field_list_long() { - check_renders_to("\ + check_renders_to("\ Not a docinfo. :This is: a diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index 4d6bfdb..6e349be 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -1,21 +1,25 @@ mod html; - use std::io::Write; use failure::Error; use document_tree::Document; - -pub fn render_json<W>(document: &Document, stream: W) -> Result<(), Error> where W: Write { - serde_json::to_writer(stream, &document)?; - Ok(()) +pub fn render_json<W>(document: &Document, stream: W) -> Result<(), Error> +where + W: Write, +{ + serde_json::to_writer(stream, &document)?; + Ok(()) } -pub fn render_xml<W>(document: &Document, stream: W) -> Result<(), Error> where W: Write { - serde_xml_rs::to_writer(stream, &document).map_err(failure::SyncFailure::new)?; - Ok(()) +pub fn render_xml<W>(document: &Document, stream: W) -> Result<(), Error> +where + W: Write, +{ + serde_xml_rs::to_writer(stream, &document).map_err(failure::SyncFailure::new)?; + Ok(()) } pub use html::render_html; diff --git a/rst/Cargo.toml b/rst/Cargo.toml index a0a288e..310fab6 100644 --- a/rst/Cargo.toml +++ b/rst/Cargo.toml @@ -1,6 +1,6 @@ [package] name = 'rst' -version = '0.3.0' +version = '0.4.0' authors = ['Philipp A. <flying-sheep@web.de>'] edition = '2018' description = 'a reStructuredText parser and renderer for the command line' @@ -12,8 +12,8 @@ homepage = 'https://github.com/flying-sheep/rust-rst' repository = 'https://github.com/flying-sheep/rust-rst' [dependencies] -rst_renderer = { path = '../renderer', version = '0.3.0' } -rst_parser = { path = '../parser', version = '0.3.0' } +rst_renderer = { path = '../renderer', version = '0.4.0' } +rst_parser = { path = '../parser', version = '0.4.0' } quicli = '0.4.0' structopt = '0.2.15' diff --git a/rst/src/main.rs b/rst/src/main.rs index 1418a5e..6a7ed44 100644 --- a/rst/src/main.rs +++ b/rst/src/main.rs @@ -1,59 +1,59 @@ -use structopt::StructOpt; use clap::arg_enum; use quicli::{ - fs::read_file, - prelude::{CliResult,Verbosity}, + fs::read_file, + prelude::{CliResult, Verbosity}, }; +use structopt::StructOpt; use rst_parser::parse; -use rst_renderer::{ - render_json, - render_xml, - render_html, -}; +use rst_renderer::{render_html, render_json, render_xml}; use std::io::{self, Read}; arg_enum! { - #[derive(Debug)] - #[allow(non_camel_case_types)] - enum Format { json, xml, html } + #[derive(Debug)] + #[allow(non_camel_case_types)] + enum Format { json, xml, html } } #[derive(Debug, StructOpt)] #[structopt(raw(setting = "structopt::clap::AppSettings::ColoredHelp"))] struct Cli { - #[structopt( - long = "format", short = "f", default_value = "html", // xml is pretty defunct… - raw(possible_values = "&Format::variants()", case_insensitive = "true"), - )] - format: Format, - file: Option<String>, - #[structopt(flatten)] - verbosity: Verbosity, + #[structopt( + long = "format", short = "f", default_value = "html", // xml is pretty defunct… + raw(possible_values = "&Format::variants()", case_insensitive = "true"), + )] + format: Format, + file: Option<String>, + #[structopt(flatten)] + verbosity: Verbosity, } fn main() -> CliResult { - let args = Cli::from_args(); - args.verbosity.setup_env_logger("rst")?; - - let content = if let Some(file) = args.file { - read_file(file)? - } else { - let mut stdin = String::new(); - io::stdin().read_to_string(&mut stdin)?; - - stdin - }; - - // TODO: somehow make it work without replacing tabs - let content = content.replace('\t', " ".repeat(8).as_ref()); - let document = parse(&content)?; - let stdout = std::io::stdout(); - match args.format { - Format::json => render_json(&document, stdout)?, - Format::xml => render_xml (&document, stdout)?, - Format::html => render_html(&document, stdout, true)?, - } - Ok(()) + let args = Cli::from_args(); + args.verbosity.setup_env_logger("rst")?; + + let content = if let Some(file) = args.file { + read_file(file)? + } else { + let mut stdin = String::new(); + io::stdin().read_to_string(&mut stdin)?; + + stdin + }; + + // TODO: somehow make it work without replacing tabs + let mut content = read_file(args.file)?.replace('\t', " ".repeat(8).as_ref()); + // Allows for less complex grammar + if !content.ends_with('\n') { + content.push('\n'); + } + let document = parse(&content)?; + let stdout = std::io::stdout(); + match args.format { + Format::json => render_json(&document, stdout)?, + Format::xml => render_xml (&document, stdout)?, + Format::html => render_html(&document, stdout, true)?, + } + Ok(()) } diff --git a/rust-rst.svg b/rust-rst.svg index 894715d..6ff77a3 100644 --- a/rust-rst.svg +++ b/rust-rst.svg @@ -1,57 +1,57 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg xmlns="http://www.w3.org/2000/svg" height="104.49973" width="195.40294" version="1.1"> - <style> - /* colors */ - :root { --rust-rst-lite: #ccc; --rust-rst-dark: #333 } - .rust-rst-logo { fill: var(--rust-rst-dark); stroke: none; fill-rule: evenodd } - .rust-rst-logo .stroked { stroke: var(--rust-rst-dark) } - /* with a dark system theme, use: */ - @media (prefers-color-scheme: dark) { - .rust-rst-logo { fill: var(--rust-rst-lite) } - .rust-rst-logo .stroked { stroke: var(--rust-rst-lite) } - } - /* If this SVG is embedded in a document, use the text color */ - body .rust-rst-logo { fill: currentColor } - body .rust-rst-logo .stroked { stroke: currentColor } + <style> + /* colors */ + :root { --rust-rst-lite: #ccc; --rust-rst-dark: #333 } + .rust-rst-logo { fill: var(--rust-rst-dark); stroke: none; fill-rule: evenodd } + .rust-rst-logo .stroked { stroke: var(--rust-rst-dark) } + /* with a dark system theme, use: */ + @media (prefers-color-scheme: dark) { + .rust-rst-logo { fill: var(--rust-rst-lite) } + .rust-rst-logo .stroked { stroke: var(--rust-rst-lite) } + } + /* If this SVG is embedded in a document, use the text color */ + body .rust-rst-logo { fill: currentColor } + body .rust-rst-logo .stroked { stroke: currentColor } - /* parts that use stroke */ - .rust-rst-logo .stroked { - stroke-width: 3; - stroke-linecap: round; - stroke-linejoin: round; - } + /* parts that use stroke */ + .rust-rst-logo .stroked { + stroke-width: 3; + stroke-linecap: round; + stroke-linejoin: round; + } - /* can be used to regenerate/change the logo */ - .rust-rst-logo text { - display: none; - font-size: 90.66667175px; - line-height: 1.25; - font-family: "OCR-A", OCRA; - } - </style> - <g class="rust-rst-logo"> - <path - d="M 52.25,4.9804688 C 26.19,4.9804688 4.9804688,26.2 4.9804688,52.25 4.9804688,78.3 26.2,99.519531 52.25,99.519531 78.3,99.519531 99.519531,78.3 99.519531,52.25 99.519531,26.2 78.3,4.9804688 52.25,4.9804688 Z m -0.07031,4.1992187 c 1.68076,0.043529 3.020091,1.4226295 3.019531,3.1093745 0,4.146665 -6.21875,4.146665 -6.21875,0 -7.34e-4,-1.753375 1.446577,-3.1601154 3.199219,-3.1093745 z m -7.029297,5.1210935 4.789062,5.019531 c 1.08,1.13 2.87,1.179844 4,0.08984 l 5.361328,-5.109375 C 70.351003,16.370525 79.931432,23.192597 85.5,32.958984 l -3.669922,8.28125 c -0.63,1.43 0.01945,3.11 1.439453,3.75 l 7.060547,3.128907 c 1.806609,8.8722 -3.653366,24.198569 -10.800779,30.611328 L 72.958984,77.320312 C 71.428984,76.990313 69.919844,77.97 69.589844,79.5 l -1.560547,7.279297 C 57.878136,91.377741 46.226622,91.32258 36.119141,86.628906 l -1.558594,-7.279297 c -0.33,-1.53 -1.831328,-2.509687 -3.361328,-2.179687 l -6.429688,1.380859 C 17.284085,70.513922 13.167601,57.997225 14.199219,47.789062 l 6.701172,-2.978515 c 1.43,-0.64 2.079453,-2.310235 1.439453,-3.740235 L 20.958984,37.958984 18.81585,33.087893 C 23.531492,24.265903 34.944722,15.885966 45.150391,14.300781 Z M 15.099609,37.320312 c 1.682359,0.04871 3.020237,1.426312 3.019532,3.109376 0,4.146664 -6.21875,4.146665 -6.21875,0 -7.34e-4,-1.753377 1.446577,-3.160117 3.199218,-3.109376 z M 89.25,37.458984 c 1.682359,0.04871 3.020236,1.428264 3.019531,3.111328 0,4.146665 -6.21875,4.146666 -6.21875,0 -7.34e-4,-1.753376 1.446578,-3.162069 3.199219,-3.111328 z M 29.150391,81.109375 c 1.682359,0.04871 3.020236,1.428264 3.019531,3.111328 0,4.146665 -6.220703,4.146665 -6.220703,0 -7.34e-4,-1.753376 1.448531,-3.162069 3.201172,-3.111328 z M 75.199219,81.25 c 1.682359,0.04871 3.022189,1.426311 3.021484,3.109375 0,4.146665 -6.220703,4.146665 -6.220703,0 -7.34e-4,-1.753376 1.446578,-3.160116 3.199219,-3.109375 z" - /> - <path class="stroked" - d="m 96.879865,52.249865 a 44.63,44.63 0 0 1 -44.63,44.63 44.63,44.63 0 0 1 -44.6299999,-44.63 44.63,44.63 0 0 1 44.6299999,-44.6299999 44.63,44.63 0 0 1 44.63,44.6299999 z m -0.84,-4.31 6.960005,4.31 -6.960005,4.31 5.980005,5.59 -7.660005,2.87 4.78,6.65 -8.09,1.32 3.4,7.46 -8.19,-0.29 1.88,7.98 -7.98,-1.88 0.29,8.19 -7.46,-3.4 -1.32,8.09 -6.65,-4.78 -2.87,7.660005 -5.59,-5.980005 -4.31,6.960005 -4.31,-6.960005 -5.59,5.980005 -2.87,-7.660005 -6.65,4.78 -1.32,-8.09 -7.46,3.4 0.29,-8.19 -7.98,1.88 1.88,-7.98 -8.19,0.29 3.4,-7.46 -8.0899999,-1.32 4.7799999,-6.65 -7.6599999,-2.87 5.98,-5.59 -6.96,-4.31 6.96,-4.31 -5.98,-5.59 7.6599999,-2.87 -4.7799999,-6.65 8.0899999,-1.32 -3.4,-7.46 8.19,0.29 -1.88,-7.98 7.98,1.88 -0.29,-8.19 7.46,3.4 1.32,-8.0899999 6.65,4.7799999 2.87,-7.6599999 5.59,5.98 4.31,-6.96 4.31,6.96 5.59,-5.98 2.87,7.6599999 6.65,-4.7799999 1.32,8.0899999 7.46,-3.4 -0.29,8.19 7.98,-1.88 -1.88,7.98 8.19,-0.29 -3.4,7.46 8.09,1.32 -4.78,6.65 7.660005,2.87 z" - /> - <text x="21.311935" y="82.67424"> - <tspan dx="0 7.5999999 -15.63">RST</tspan> - </text> - <g> - <path - style="font-size:90.66667175px;font-family:OCRA;word-spacing:0px" - d="M 31.013269,47.132905 V 15.580903 H 46.51727 q 8.160001,0 11.877334,0.09067 3.717334,0 4.170667,0.09067 0.453333,0 1.36,0.272 3.898667,0.906667 6.618667,4.080001 2.72,3.082666 3.173334,7.162667 0.09067,0.544 0.09067,3.989333 -0.09067,3.626667 -0.453333,5.077334 -0.816,3.445333 -3.445334,5.984 -2.538666,2.538667 -5.984,3.445334 -1.36,0.362666 -5.077333,0.453333 h -3.173334 l 8.885334,15.141334 q 8.885334,15.232001 8.885334,15.413334 1.088,2.901334 -1.088,4.714667 -1.269334,1.269334 -3.082667,1.178667 -1.813334,-0.09067 -3.082667,-1.450667 -0.272,-0.272 -0.634667,-0.997333 L 55.402604,62.908906 45.610603,46.226238 h -2.992 -2.992 v 16.410668 q 0,16.501334 -0.09067,16.773334 -0.362666,0.997333 -0.816,1.632 -0.634666,0.816 -1.450666,1.178667 -1.813334,0.816 -3.445334,0.181333 -0.816,-0.272 -1.541333,-0.997333 -0.816,-0.906667 -0.997334,-1.541334 0,-0.09067 -0.09067,-0.09067 -0.181334,-0.544 -0.181334,-16.048001 0,-9.701334 0,-16.592001 z M 61.839938,24.37557 q -0.272,-0.09067 -11.242668,-0.09067 H 39.626603 v 6.618667 6.618667 H 50.59727 q 5.712001,0 8.341334,0 2.629334,-0.09067 2.901334,-0.09067 0.272,-0.09067 0.816,-0.362667 1.450666,-0.725333 1.994667,-2.085333 0.453333,-0.816 0.453333,-4.08 0,-3.264001 -0.453333,-4.170667 -0.816001,-1.632 -2.810667,-2.357334 z" - /> - <path - style="font-size:90.66667175px;font-family:OCRA;word-spacing:0px" - d="m 111.85777,15.67157 q 0.0907,0 0.816,0 0.72533,0 1.81333,0 1.17867,-0.09067 3.62667,-0.09067 2.53867,0 5.712,0 11.51467,0 12.05867,0.09067 4.08,0.544 6.98133,3.445333 2.81067,2.810667 3.264,6.981334 0.36267,2.72 -1.26933,4.261334 -1.26934,1.178666 -2.992,1.178666 -0.72534,0 -1.17867,-0.09067 -1.088,-0.362667 -1.904,-1.178667 -1.17867,-1.178667 -1.26933,-2.992 0,-0.544 -0.18134,-1.088 -0.45333,-1.269334 -1.81333,-1.722667 l -0.36267,-0.181333 h -11.152 -11.24267 l -0.18133,0.181333 q -0.72533,0.634667 -0.45333,1.36 0.0907,0.09067 16.13867,20.762668 16.13866,20.581334 16.32,20.944001 1.26933,1.994667 1.54133,4.170667 0.45333,3.626667 -1.632,6.709334 -2.08533,2.992 -5.62133,3.898666 -1.36,0.362667 -13.23734,0.362667 -11.424,0 -11.968,-0.09067 -4.08,-0.544 -6.98133,-3.445333 -2.81067,-2.992 -3.264,-6.981334 -0.272,-2.538667 1.17866,-4.08 0.816,-0.906667 1.99467,-1.178667 0.36267,-0.181333 1.088,-0.181333 1.81333,0 2.992,1.178667 1.26933,1.088 1.36,3.082666 l 0.18133,1.088001 q 0.544,1.269333 1.81334,1.722666 l 0.36267,0.181334 h 11.152 11.06133 l 0.272,-0.09067 q 1.088,-0.544 0.544,-1.36 0,-0.272 -16.13867,-20.853335 -16.048,-20.672001 -16.32,-20.944001 -1.54133,-2.448 -1.54133,-5.349333 0,-2.992001 1.632,-5.349334 2.448,-3.808 6.8,-4.352 z" - /> - <path - style="font-size:90.66667175px;font-family:OCRA;word-spacing:0px" - d="m 152.60827,21.564903 v -5.984 h 21.39734 21.39733 v 5.984 q 0,6.437334 -0.36267,7.253334 -0.544,1.722667 -2.35733,2.448 -0.63467,0.272 -1.632,0.272 -1.17867,0 -1.904,-0.362666 -1.45067,-0.816 -2.176,-2.357334 -0.272,-1.088 -0.272,-2.901333 v -1.632 h -4.17067 -4.26133 v 27.381334 q 0,27.472002 -0.0907,27.744002 -0.544,1.994667 -2.26666,2.810667 -1.81334,0.816 -3.44534,0.181333 -0.72533,-0.181333 -1.54133,-0.997333 -0.816,-0.906667 -0.99734,-1.541334 0,-0.09067 -0.0907,-0.09067 -0.18134,-0.544 -0.18134,-13.962668 0,-8.341334 0,-14.325334 V 24.284904 h -4.26133 -4.17067 v 1.632 q 0,2.176 -0.272,2.901333 -0.72533,1.722667 -2.448,2.448 -0.544,0.272 -1.632,0.272 -1.088,0 -1.81333,-0.362666 -1.632,-0.906667 -2.176,-2.357334 -0.272,-0.816 -0.272,-7.253334 z" - /> - </g> - </g> + /* can be used to regenerate/change the logo */ + .rust-rst-logo text { + display: none; + font-size: 90.66667175px; + line-height: 1.25; + font-family: "OCR-A", OCRA; + } + </style> + <g class="rust-rst-logo"> + <path + d="M 52.25,4.9804688 C 26.19,4.9804688 4.9804688,26.2 4.9804688,52.25 4.9804688,78.3 26.2,99.519531 52.25,99.519531 78.3,99.519531 99.519531,78.3 99.519531,52.25 99.519531,26.2 78.3,4.9804688 52.25,4.9804688 Z m -0.07031,4.1992187 c 1.68076,0.043529 3.020091,1.4226295 3.019531,3.1093745 0,4.146665 -6.21875,4.146665 -6.21875,0 -7.34e-4,-1.753375 1.446577,-3.1601154 3.199219,-3.1093745 z m -7.029297,5.1210935 4.789062,5.019531 c 1.08,1.13 2.87,1.179844 4,0.08984 l 5.361328,-5.109375 C 70.351003,16.370525 79.931432,23.192597 85.5,32.958984 l -3.669922,8.28125 c -0.63,1.43 0.01945,3.11 1.439453,3.75 l 7.060547,3.128907 c 1.806609,8.8722 -3.653366,24.198569 -10.800779,30.611328 L 72.958984,77.320312 C 71.428984,76.990313 69.919844,77.97 69.589844,79.5 l -1.560547,7.279297 C 57.878136,91.377741 46.226622,91.32258 36.119141,86.628906 l -1.558594,-7.279297 c -0.33,-1.53 -1.831328,-2.509687 -3.361328,-2.179687 l -6.429688,1.380859 C 17.284085,70.513922 13.167601,57.997225 14.199219,47.789062 l 6.701172,-2.978515 c 1.43,-0.64 2.079453,-2.310235 1.439453,-3.740235 L 20.958984,37.958984 18.81585,33.087893 C 23.531492,24.265903 34.944722,15.885966 45.150391,14.300781 Z M 15.099609,37.320312 c 1.682359,0.04871 3.020237,1.426312 3.019532,3.109376 0,4.146664 -6.21875,4.146665 -6.21875,0 -7.34e-4,-1.753377 1.446577,-3.160117 3.199218,-3.109376 z M 89.25,37.458984 c 1.682359,0.04871 3.020236,1.428264 3.019531,3.111328 0,4.146665 -6.21875,4.146666 -6.21875,0 -7.34e-4,-1.753376 1.446578,-3.162069 3.199219,-3.111328 z M 29.150391,81.109375 c 1.682359,0.04871 3.020236,1.428264 3.019531,3.111328 0,4.146665 -6.220703,4.146665 -6.220703,0 -7.34e-4,-1.753376 1.448531,-3.162069 3.201172,-3.111328 z M 75.199219,81.25 c 1.682359,0.04871 3.022189,1.426311 3.021484,3.109375 0,4.146665 -6.220703,4.146665 -6.220703,0 -7.34e-4,-1.753376 1.446578,-3.160116 3.199219,-3.109375 z" + /> + <path class="stroked" + d="m 96.879865,52.249865 a 44.63,44.63 0 0 1 -44.63,44.63 44.63,44.63 0 0 1 -44.6299999,-44.63 44.63,44.63 0 0 1 44.6299999,-44.6299999 44.63,44.63 0 0 1 44.63,44.6299999 z m -0.84,-4.31 6.960005,4.31 -6.960005,4.31 5.980005,5.59 -7.660005,2.87 4.78,6.65 -8.09,1.32 3.4,7.46 -8.19,-0.29 1.88,7.98 -7.98,-1.88 0.29,8.19 -7.46,-3.4 -1.32,8.09 -6.65,-4.78 -2.87,7.660005 -5.59,-5.980005 -4.31,6.960005 -4.31,-6.960005 -5.59,5.980005 -2.87,-7.660005 -6.65,4.78 -1.32,-8.09 -7.46,3.4 0.29,-8.19 -7.98,1.88 1.88,-7.98 -8.19,0.29 3.4,-7.46 -8.0899999,-1.32 4.7799999,-6.65 -7.6599999,-2.87 5.98,-5.59 -6.96,-4.31 6.96,-4.31 -5.98,-5.59 7.6599999,-2.87 -4.7799999,-6.65 8.0899999,-1.32 -3.4,-7.46 8.19,0.29 -1.88,-7.98 7.98,1.88 -0.29,-8.19 7.46,3.4 1.32,-8.0899999 6.65,4.7799999 2.87,-7.6599999 5.59,5.98 4.31,-6.96 4.31,6.96 5.59,-5.98 2.87,7.6599999 6.65,-4.7799999 1.32,8.0899999 7.46,-3.4 -0.29,8.19 7.98,-1.88 -1.88,7.98 8.19,-0.29 -3.4,7.46 8.09,1.32 -4.78,6.65 7.660005,2.87 z" + /> + <text x="21.311935" y="82.67424"> + <tspan dx="0 7.5999999 -15.63">RST</tspan> + </text> + <g> + <path + style="font-size:90.66667175px;font-family:OCRA;word-spacing:0px" + d="M 31.013269,47.132905 V 15.580903 H 46.51727 q 8.160001,0 11.877334,0.09067 3.717334,0 4.170667,0.09067 0.453333,0 1.36,0.272 3.898667,0.906667 6.618667,4.080001 2.72,3.082666 3.173334,7.162667 0.09067,0.544 0.09067,3.989333 -0.09067,3.626667 -0.453333,5.077334 -0.816,3.445333 -3.445334,5.984 -2.538666,2.538667 -5.984,3.445334 -1.36,0.362666 -5.077333,0.453333 h -3.173334 l 8.885334,15.141334 q 8.885334,15.232001 8.885334,15.413334 1.088,2.901334 -1.088,4.714667 -1.269334,1.269334 -3.082667,1.178667 -1.813334,-0.09067 -3.082667,-1.450667 -0.272,-0.272 -0.634667,-0.997333 L 55.402604,62.908906 45.610603,46.226238 h -2.992 -2.992 v 16.410668 q 0,16.501334 -0.09067,16.773334 -0.362666,0.997333 -0.816,1.632 -0.634666,0.816 -1.450666,1.178667 -1.813334,0.816 -3.445334,0.181333 -0.816,-0.272 -1.541333,-0.997333 -0.816,-0.906667 -0.997334,-1.541334 0,-0.09067 -0.09067,-0.09067 -0.181334,-0.544 -0.181334,-16.048001 0,-9.701334 0,-16.592001 z M 61.839938,24.37557 q -0.272,-0.09067 -11.242668,-0.09067 H 39.626603 v 6.618667 6.618667 H 50.59727 q 5.712001,0 8.341334,0 2.629334,-0.09067 2.901334,-0.09067 0.272,-0.09067 0.816,-0.362667 1.450666,-0.725333 1.994667,-2.085333 0.453333,-0.816 0.453333,-4.08 0,-3.264001 -0.453333,-4.170667 -0.816001,-1.632 -2.810667,-2.357334 z" + /> + <path + style="font-size:90.66667175px;font-family:OCRA;word-spacing:0px" + d="m 111.85777,15.67157 q 0.0907,0 0.816,0 0.72533,0 1.81333,0 1.17867,-0.09067 3.62667,-0.09067 2.53867,0 5.712,0 11.51467,0 12.05867,0.09067 4.08,0.544 6.98133,3.445333 2.81067,2.810667 3.264,6.981334 0.36267,2.72 -1.26933,4.261334 -1.26934,1.178666 -2.992,1.178666 -0.72534,0 -1.17867,-0.09067 -1.088,-0.362667 -1.904,-1.178667 -1.17867,-1.178667 -1.26933,-2.992 0,-0.544 -0.18134,-1.088 -0.45333,-1.269334 -1.81333,-1.722667 l -0.36267,-0.181333 h -11.152 -11.24267 l -0.18133,0.181333 q -0.72533,0.634667 -0.45333,1.36 0.0907,0.09067 16.13867,20.762668 16.13866,20.581334 16.32,20.944001 1.26933,1.994667 1.54133,4.170667 0.45333,3.626667 -1.632,6.709334 -2.08533,2.992 -5.62133,3.898666 -1.36,0.362667 -13.23734,0.362667 -11.424,0 -11.968,-0.09067 -4.08,-0.544 -6.98133,-3.445333 -2.81067,-2.992 -3.264,-6.981334 -0.272,-2.538667 1.17866,-4.08 0.816,-0.906667 1.99467,-1.178667 0.36267,-0.181333 1.088,-0.181333 1.81333,0 2.992,1.178667 1.26933,1.088 1.36,3.082666 l 0.18133,1.088001 q 0.544,1.269333 1.81334,1.722666 l 0.36267,0.181334 h 11.152 11.06133 l 0.272,-0.09067 q 1.088,-0.544 0.544,-1.36 0,-0.272 -16.13867,-20.853335 -16.048,-20.672001 -16.32,-20.944001 -1.54133,-2.448 -1.54133,-5.349333 0,-2.992001 1.632,-5.349334 2.448,-3.808 6.8,-4.352 z" + /> + <path + style="font-size:90.66667175px;font-family:OCRA;word-spacing:0px" + d="m 152.60827,21.564903 v -5.984 h 21.39734 21.39733 v 5.984 q 0,6.437334 -0.36267,7.253334 -0.544,1.722667 -2.35733,2.448 -0.63467,0.272 -1.632,0.272 -1.17867,0 -1.904,-0.362666 -1.45067,-0.816 -2.176,-2.357334 -0.272,-1.088 -0.272,-2.901333 v -1.632 h -4.17067 -4.26133 v 27.381334 q 0,27.472002 -0.0907,27.744002 -0.544,1.994667 -2.26666,2.810667 -1.81334,0.816 -3.44534,0.181333 -0.72533,-0.181333 -1.54133,-0.997333 -0.816,-0.906667 -0.99734,-1.541334 0,-0.09067 -0.0907,-0.09067 -0.18134,-0.544 -0.18134,-13.962668 0,-8.341334 0,-14.325334 V 24.284904 h -4.26133 -4.17067 v 1.632 q 0,2.176 -0.272,2.901333 -0.72533,1.722667 -2.448,2.448 -0.544,0.272 -1.632,0.272 -1.088,0 -1.81333,-0.362666 -1.632,-0.906667 -2.176,-2.357334 -0.272,-0.816 -0.272,-7.253334 z" + /> + </g> + </g> </svg> |
