diff options
| author | Philipp A | 2019-12-26 23:01:00 +0100 | 
|---|---|---|
| committer | Philipp A | 2019-12-26 23:36:48 +0100 | 
| commit | a0e3c53758d526bb418c068bce1c99fa5a597ed3 (patch) | |
| tree | e640238b011a9ea7806ccccaf1a435e4b371a376 /document_tree | |
| parent | 7018f5d3c42f18b6c83f398db9f1915361a7c679 (diff) | |
| download | rust-rst-a0e3c53758d526bb418c068bce1c99fa5a597ed3.tar.bz2 | |
Split into smaller crates
Diffstat (limited to 'document_tree')
| -rw-r--r-- | document_tree/Cargo.toml | 19 | ||||
| -rw-r--r-- | document_tree/src/attribute_types.rs | 155 | ||||
| -rw-r--r-- | document_tree/src/element_categories.rs | 130 | ||||
| -rw-r--r-- | document_tree/src/element_types.rs | 96 | ||||
| -rw-r--r-- | document_tree/src/elements.rs | 288 | ||||
| -rw-r--r-- | document_tree/src/extra_attributes.rs | 120 | ||||
| -rw-r--r-- | document_tree/src/lib.rs | 43 | ||||
| -rw-r--r-- | document_tree/src/macro_util.rs | 42 | ||||
| -rw-r--r-- | document_tree/src/url.rs | 78 | 
9 files changed, 971 insertions, 0 deletions
| diff --git a/document_tree/Cargo.toml b/document_tree/Cargo.toml new file mode 100644 index 0000000..09e827e --- /dev/null +++ b/document_tree/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = 'document_tree' +version = '0.2.0' +authors = ['Philipp A. <flying-sheep@web.de>'] +description = 'reStructuredText’s DocumenTree representation' +license = 'MIT OR Apache-2.0' + +documentation = 'https://flying-sheep.github.io/rust-rst' +homepage = 'https://github.com/flying-sheep/rust-rst' +repository = 'https://github.com/flying-sheep/rust-rst.git' + +edition = '2018' + +[dependencies] +failure = '0.1.6' +regex = '1.3.1' +url = '2.1.0' +serde = '1.0.104' +serde_derive = '1.0.104' diff --git a/document_tree/src/attribute_types.rs b/document_tree/src/attribute_types.rs new file mode 100644 index 0000000..411b24d --- /dev/null +++ b/document_tree/src/attribute_types.rs @@ -0,0 +1,155 @@ +use std::str::FromStr; + +use failure::{Error,bail,format_err}; +use serde_derive::Serialize; +use regex::Regex; + +use crate::url::Url; + +#[derive(Debug,PartialEq,Eq,Hash,Serialize,Clone)] +pub enum EnumeratedListType { +	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(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 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) +	} +} + +// 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), +} + +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), +		}) +	} +} + +impl From<&str> for ID { +	fn from(s: &str) -> Self { +		ID(s.to_owned().replace(' ', "-")) +	} +} + +impl From<&str> for NameToken { +	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!(), +		}) +	} +} + +#[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(); +	} +} + +pub(crate) trait CanBeEmpty { +	fn is_empty(&self) -> bool; +} + +/* Specialization necessary +impl<T> CanBeEmpty for T { +	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),*); +	}; +} +impl_cannot_be_empty!(Url); +impl_cannot_be_empty!(TableGroupCols); + +impl<T> CanBeEmpty for Option<T> { +	fn is_empty(&self) -> bool { self.is_none() } +} + +impl<T> CanBeEmpty for Vec<T> { +	fn is_empty(&self) -> bool { self.is_empty() } +} + +impl CanBeEmpty for bool { +	fn is_empty(&self) -> bool { !self } +} + +impl CanBeEmpty for FixedSpace { +	fn is_empty(&self) -> bool { self == &FixedSpace::default() } +} + diff --git a/document_tree/src/element_categories.rs b/document_tree/src/element_categories.rs new file mode 100644 index 0000000..24a0798 --- /dev/null +++ b/document_tree/src/element_categories.rs @@ -0,0 +1,130 @@ +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()); +		} +	} +} + +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())) +			} +		} +	}; +} + +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)) +			} +		} )* +	}; +} + +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 +}); + +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 +}); + +//--------------\\ +//Content Models\\ +//--------------\\ + +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!(SubLineBlock { LineBlock, Line }); +synonymous_enum!(SubBlockQuote { Attribution, BodyElement }); +synonymous_enum!(SubFootnote { Label, BodyElement }); +synonymous_enum!(SubFigure { Caption, Legend, BodyElement }); +synonymous_enum!(SubTable { Title, TableGroup }); +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(); +	} +} diff --git a/document_tree/src/element_types.rs b/document_tree/src/element_types.rs new file mode 100644 index 0000000..429573e --- /dev/null +++ b/document_tree/src/element_types.rs @@ -0,0 +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, +// } diff --git a/document_tree/src/elements.rs b/document_tree/src/elements.rs new file mode 100644 index 0000000..26bccf6 --- /dev/null +++ b/document_tree/src/elements.rs @@ -0,0 +1,288 @@ +use std::path::PathBuf; +use serde_derive::Serialize; + +use crate::attribute_types::{CanBeEmpty,ID,NameToken}; +use crate::extra_attributes::{self,ExtraAttributes}; +use crate::element_categories::*; + + +//-----------------\\ +//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>; +} + +#[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 +} + +//----\\ +//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_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_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 } +	} +)} + +trait HasExtraAndChildren<C, A> { +	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 +	} +} + +macro_rules! impl_new {( +	$(#[$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, )* } } +	} +)} + +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); +	}; +} + +macro_rules! impl_elems { ( $( ($($args:tt)*) )* ) => ( +	$( impl_elem!($($args)*); )* +)} + + +#[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,               TextOrInlineElement) +	(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() +	} +} diff --git a/document_tree/src/extra_attributes.rs b/document_tree/src/extra_attributes.rs new file mode 100644 index 0000000..45fcf32 --- /dev/null +++ b/document_tree/src/extra_attributes.rs @@ -0,0 +1,120 @@ +use serde_derive::Serialize; + +use crate::url::Url; +use crate::attribute_types::{ +	CanBeEmpty, +	FixedSpace, +	ID,NameToken, +	AlignHV,AlignH,AlignV, +	TableAlignH,TableBorder,TableGroupCols, +	Measure, +	EnumeratedListType, +}; + +pub trait ExtraAttributes<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, +		)* } +	); +} + +impl_extra!(Address { space: FixedSpace }); +impl_extra!(LiteralBlock { space: FixedSpace }); +impl_extra!(DoctestBlock { space: FixedSpace }); +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, +}); +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 +}); + +//bools usually are XML yesorno. “auto” however either exists and is set to something random like “1” or doesn’t exist +//does auto actually mean the numbering prefix? + +impl_extra!(BulletList { bullet: Option<String> }); +impl_extra!(EnumeratedList { enumtype: Option<EnumeratedListType>, prefix: Option<String>, suffix: Option<String> }); + +impl_extra!(Footnote { backrefs: Vec<ID>, auto: bool }); +impl_extra!(Citation { backrefs: Vec<ID> }); +impl_extra!(SystemMessage { backrefs: Vec<ID>, level: Option<usize>, line: Option<usize>, type_: Option<NameToken> }); +impl_extra!(Figure { align: Option<AlignH>, width: Option<usize> }); +impl_extra!(Table { frame: Option<TableBorder>, colsep: Option<bool>, rowsep: Option<bool>, pgwide: Option<bool> }); + +impl_extra!(TableGroup { cols: TableGroupCols, colsep: Option<bool>, rowsep: Option<bool>, align: Option<TableAlignH> }); +impl_extra!(TableHead { valign: Option<AlignV> }); +impl_extra!(TableBody { valign: Option<AlignV> }); +impl_extra!(TableRow { rowsep: Option<bool>, valign: Option<AlignV> }); +impl_extra!(TableEntry { colname: Option<NameToken>, namest: Option<NameToken>, nameend: Option<NameToken>, morerows: Option<usize>, colsep: Option<bool>, rowsep: Option<bool>, align: Option<TableAlignH>, r#char: Option<char>, charoff: Option<usize>, valign: Option<AlignV>, morecols: Option<usize> }); +impl_extra!(TableColspec { colnum: Option<usize>, colname: Option<NameToken>, colwidth: Option<String>, colsep: Option<bool>, rowsep: Option<bool>, align: Option<TableAlignH>, r#char: Option<char>, charoff: Option<usize>, stub: Option<bool> }); + +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>, +}); +impl_extra!(FootnoteReference { refid: Option<ID>, refname: Vec<NameToken>, auto: bool }); +impl_extra!(CitationReference { refid: Option<ID>, refname: Vec<NameToken> }); +impl_extra!(SubstitutionReference { refname: Vec<NameToken> }); +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, +}); +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, +	} } +} diff --git a/document_tree/src/lib.rs b/document_tree/src/lib.rs new file mode 100644 index 0000000..324fc44 --- /dev/null +++ b/document_tree/src/lib.rs @@ -0,0 +1,43 @@ +#![recursion_limit="256"] + +///http://docutils.sourceforge.net/docs/ref/doctree.html +///serves as AST + +#[macro_use] +mod macro_util; + +pub mod url; +pub mod elements; +pub mod element_categories; +pub mod extra_attributes; +pub mod attribute_types; + +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::*; + +	#[test] +	fn imperative() { +		let mut doc = Document::default(); +		let mut title = Title::default(); +		title.append_child("Hi"); +		doc.append_child(title); + +		println!("{:?}", doc); +	} + +	#[test] +	fn descriptive() { +		let doc = Document::with_children(vec![ +			Title::with_children(vec![ +				"Hi".into() +			]).into() +		]); + +		println!("{:?}", doc); +	} +} diff --git a/document_tree/src/macro_util.rs b/document_tree/src/macro_util.rs new file mode 100644 index 0000000..dcf3725 --- /dev/null +++ b/document_tree/src/macro_util.rs @@ -0,0 +1,42 @@ +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} +	}; +} + +macro_rules! cartesian { +	( $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(()) } +				} +			} +		}; +	} + +	#[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 new file mode 100644 index 0000000..31a0536 --- /dev/null +++ b/document_tree/src/url.rs @@ -0,0 +1,78 @@ +use std::fmt; +use std::str::FromStr; + +use url::{self,ParseError}; +use serde_derive::Serialize; + + +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 +} + +/// 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)] +#[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() +	} +} + +impl From<url::Url> for Url { +	fn from(url: url::Url) -> Self { +		Url(url.into_string()) +	} +} + + +impl fmt::Display for Url { +	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)) +	} +} | 
