document_tree-0.4.2/.cargo_vcs_info.json0000644000000001530000000000100136730ustar { "git": { "sha1": "a59c330d2109e823b0c9ac791e5216951987711d" }, "path_in_vcs": "document_tree" }document_tree-0.4.2/CHANGELOG.md000064400000000000000000000025341046102023000143010ustar 00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ## [0.4.2](https://github.com/flying-sheep/rust-rst/compare/document_tree-v0.4.1...document_tree-v0.4.2) - 2025-04-13 ### Other - Footnote refs ([#67](https://github.com/flying-sheep/rust-rst/pull/67)) - Visitor ([#68](https://github.com/flying-sheep/rust-rst/pull/68)) - Footnotes ([#65](https://github.com/flying-sheep/rust-rst/pull/65)) - clippy ([#66](https://github.com/flying-sheep/rust-rst/pull/66)) - bump edition again ([#64](https://github.com/flying-sheep/rust-rst/pull/64)) - Clippy ([#62](https://github.com/flying-sheep/rust-rst/pull/62)) ## [0.4.1](https://github.com/flying-sheep/rust-rst/compare/document_tree-v0.4.0...document_tree-v0.4.1) - 2024-11-20 ### Other - Bump deps and refresh token ([#59](https://github.com/flying-sheep/rust-rst/pull/59)) - Bump serde-xml-rs to 0.5 ([#51](https://github.com/flying-sheep/rust-rst/pull/51)) - Migrate from quicli/failure to plain clap/anyhow ([#50](https://github.com/flying-sheep/rust-rst/pull/50)) - Format ([#38](https://github.com/flying-sheep/rust-rst/pull/38)) - Add CI ([#36](https://github.com/flying-sheep/rust-rst/pull/36)) document_tree-0.4.2/Cargo.lock0000644000000245350000000000100116600ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "anyhow" version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" [[package]] name = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "document_tree" version = "0.4.2" dependencies = [ "anyhow", "regex", "serde", "serde_derive", "url", ] [[package]] name = "form_urlencoded" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "icu_collections" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ "displaydoc", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_locid" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" dependencies = [ "displaydoc", "litemap", "tinystr", "writeable", "zerovec", ] [[package]] name = "icu_locid_transform" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" dependencies = [ "displaydoc", "icu_locid", "icu_locid_transform_data", "icu_provider", "tinystr", "zerovec", ] [[package]] name = "icu_locid_transform_data" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" [[package]] name = "icu_normalizer" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" dependencies = [ "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", "utf16_iter", "utf8_iter", "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" [[package]] name = "icu_properties" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" dependencies = [ "displaydoc", "icu_collections", "icu_locid_transform", "icu_properties_data", "icu_provider", "tinystr", "zerovec", ] [[package]] name = "icu_properties_data" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" [[package]] name = "icu_provider" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" dependencies = [ "displaydoc", "icu_locid", "icu_provider_macros", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_provider_macros" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "idna" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ "idna_adapter", "smallvec", "utf8_iter", ] [[package]] name = "idna_adapter" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" dependencies = [ "icu_normalizer", "icu_properties", ] [[package]] name = "litemap" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "proc-macro2" version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "regex" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "serde" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "smallvec" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "syn" version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "synstructure" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tinystr" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ "displaydoc", "zerovec", ] [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "url" version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] [[package]] name = "utf16_iter" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "write16" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" [[package]] name = "writeable" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "yoke" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ "serde", "stable_deref_trait", "yoke-derive", "zerofrom", ] [[package]] name = "yoke-derive" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "zerofrom" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "zerovec" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" dependencies = [ "yoke", "zerofrom", "zerovec-derive", ] [[package]] name = "zerovec-derive" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", "syn", ] document_tree-0.4.2/Cargo.toml0000644000000023260000000000100116750ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2024" name = "document_tree" version = "0.4.2" authors = ["Philipp A. "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "reStructuredText’s DocumentTree representation" homepage = "https://github.com/flying-sheep/rust-rst" documentation = "https://docs.rs/document_tree" readme = "README.md" license = "MIT OR Apache-2.0" repository = "https://github.com/flying-sheep/rust-rst" resolver = "2" [lib] name = "document_tree" path = "src/lib.rs" [dependencies.anyhow] version = "1.0.86" [dependencies.regex] version = "1.5.5" [dependencies.serde] version = "1.0.104" [dependencies.serde_derive] version = "1.0.104" [dependencies.url] version = "2.1.0" document_tree-0.4.2/Cargo.toml.orig000064400000000000000000000007611046102023000153570ustar 00000000000000[package] name = 'document_tree' version = "0.4.2" authors = ['Philipp A. '] edition = '2024' description = 'reStructuredText’s DocumentTree representation' license = 'MIT OR Apache-2.0' readme = 'README.md' documentation = 'https://docs.rs/document_tree' homepage = 'https://github.com/flying-sheep/rust-rst' repository = 'https://github.com/flying-sheep/rust-rst' [dependencies] anyhow = '1.0.86' regex = '1.5.5' url = '2.1.0' serde = '1.0.104' serde_derive = '1.0.104' document_tree-0.4.2/README.md000064400000000000000000000036021046102023000137440ustar 00000000000000`document_tree` =============== Part of the [`rst`][rst] crate family. This crate contains structs and traits mirroring [Docutils’ Document Tree][doctree] model. The basic structure is a tree of [elements][], some of which [have children][] and/or [extra attributes][]. ```rust use document_tree::*; use document_tree::{extra_attributes as a, element_categories as c, attribute_types as t}; #[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(a::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(a::ImageInline::new( "https://example.com/image.jpg".parse().unwrap() )).into(), ]).into() ]); println!("{:?}", doc); } ``` Check out the other crates in the family on how to create one from rST markup or render it! The advantages of this approach are that it’s convenient to have the children interface, as well as to trivially map elements to XML. The disadvantage is that a “vector of children” is not a well-defined model for the more structured elements like e.g. a section, which always contains a title followed by blocks. [rst]: https://github.com/flying-sheep/rust-rst/#readme [doctree]: https://docutils.sourceforge.io/docs/ref/doctree.html [elements]: https://docs.rs/document_tree/0/document_tree/elements/trait.Element.html [have children]: https://docs.rs/document_tree/0/document_tree/element_categories/trait.HasChildren.html [extra attributes]: https://docs.rs/document_tree/0/document_tree/extra_attributes/trait.ExtraAttributes.html document_tree-0.4.2/src/attribute_types.rs000064400000000000000000000114671046102023000170610ustar 00000000000000use std::str::FromStr; use anyhow::{Error, bail, format_err}; use regex::Regex; use serde_derive::Serialize; use crate::url::Url; #[derive(Debug, PartialEq, Eq, Hash, Serialize, Clone, Copy)] pub enum EnumeratedListType { Arabic, LowerAlpha, UpperAlpha, LowerRoman, UpperRoman, } #[derive(Debug, PartialEq, Eq, Hash, Serialize, Clone, Copy)] pub enum AutoFootnoteType { Number, Symbol, } impl TryFrom for AutoFootnoteType { type Error = (); fn try_from(c: char) -> Result { match c { '#' => Ok(AutoFootnoteType::Number), '*' => Ok(AutoFootnoteType::Symbol), _ => Err(()), } } } #[derive(Default, Debug, PartialEq, Eq, Hash, Serialize, Clone, Copy)] pub enum FixedSpace { Default, // yes, default really is not “Default” #[default] Preserve, } #[derive(Debug, PartialEq, Eq, Hash, Serialize, Clone, Copy)] pub enum AlignH { Left, Center, Right, } #[derive(Debug, PartialEq, Eq, Hash, Serialize, Clone, Copy)] pub enum AlignHV { Top, Middle, Bottom, Left, Center, Right, } #[derive(Debug, PartialEq, Eq, Hash, Serialize, Clone, Copy)] pub enum AlignV { Top, Middle, Bottom, } #[derive(Debug, PartialEq, Eq, Hash, Serialize, Clone, Copy)] pub enum TableAlignH { Left, Right, Center, Justify, Char, } #[derive(Debug, PartialEq, Eq, Hash, Serialize, Clone, Copy)] 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(Default, Debug, PartialEq, Eq, Hash, Serialize, Clone)] pub struct TableGroupCols(pub usize); // no eq for f64 #[derive(Debug, PartialEq, Serialize, Clone)] pub enum Measure { // https://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 { use self::AlignHV as A; Ok(match s { "top" => A::Top, "middle" => A::Middle, "bottom" => A::Bottom, "left" => A::Left, "center" => A::Center, "right" => A::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 { use self::Measure as M; let re = Regex::new(r"(?P\d+\.\d*|\.?\d+)\s*(?Pem|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" => M::Em(value), "ex" => M::Ex(value), "mm" => M::Mm(value), "cm" => M::Cm(value), "in" => M::In(value), "px" => M::Px(value), "pt" => M::Pt(value), "pc" => M::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 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 CanBeEmpty for Option { fn is_empty(&self) -> bool { self.is_none() } } impl CanBeEmpty for Vec { 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() } } document_tree-0.4.2/src/element_categories.rs000064400000000000000000000153021046102023000174600ustar 00000000000000use std::fmt::{self, Debug, Formatter}; use serde_derive::Serialize; #[allow(clippy::wildcard_imports)] use crate::elements::*; pub trait HasChildren { fn with_children(children: Vec) -> Self; fn children(&self) -> &Vec; fn children_mut(&mut self) -> &mut Vec; fn append_child>(&mut self, child: R) { self.children_mut().push(child.into()); } fn append_children + 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 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 From<$entry> for $name { fn from(inner: $entry) -> Self { $name::$entry(Box::new(inner)) } } )* }; } 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 }); impl<'a> TryFrom<&'a StructuralSubElement> for &'a BodyElement { type Error = (); fn try_from(value: &'a StructuralSubElement) -> Result { match value { StructuralSubElement::SubStructure(s) => s.as_ref().try_into(), _ => Err(()), } } } impl<'a> TryFrom<&'a SubStructure> for &'a BodyElement { type Error = (); fn try_from(value: &'a SubStructure) -> Result { match value { SubStructure::BodyElement(s) => Ok(s.as_ref()), _ => Err(()), } } } synonymous_enum!(BibliographicElement { Authors, // author info, contained in Authors above: Author, Organization, Address, Contact, // other: 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 }); // indirect conversions impl From for SubSidebar { fn from(inner: SubTopic) -> Self { match inner { SubTopic::Title(e) => (*e).into(), SubTopic::BodyElement(e) => (*e).into(), } } } impl From for StructuralSubElement { fn from(inner: SubTopic) -> Self { match inner { SubTopic::Title(e) => (*e).into(), SubTopic::BodyElement(e) => (*e).into(), } } } impl From for StructuralSubElement { fn from(inner: SubSidebar) -> Self { match inner { SubSidebar::Topic(e) => (*e).into(), SubSidebar::Title(e) => (*e).into(), SubSidebar::Subtitle(e) => (*e).into(), SubSidebar::BodyElement(e) => (*e).into(), } } } impl From for BibliographicElement { fn from(inner: AuthorInfo) -> Self { match inner { AuthorInfo::Author(e) => (*e).into(), AuthorInfo::Organization(e) => (*e).into(), AuthorInfo::Address(e) => (*e).into(), AuthorInfo::Contact(e) => (*e).into(), } } } #[cfg(test)] mod conversion_tests { 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(); } } document_tree-0.4.2/src/element_types.rs000064400000000000000000000056161046102023000165060ustar 00000000000000 // 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, anonymous: bool }, // Raw { space: FixedSpace, format: Vec }, // 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, auto: bool }, // Citation { backrefs: Vec }, // SystemMessage { backrefs: Vec, 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 }, // FootnoteReference { refid: ID, refname: Vec, auto: bool }, // CitationReference { refid: ID, refname: Vec }, // SubstitutionReference { refname: Vec }, // 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, anonymous: bool }, // RawInline { space: FixedSpace, format: Vec }, // ImageInline { // align: AlignHV, // uri: Url, // alt: String, // height: Measure, // width: Measure, // scale: f64, // }, // // //text element // TextElement, // } document_tree-0.4.2/src/elements.rs000064400000000000000000000273151046102023000154450ustar 00000000000000use serde_derive::Serialize; use std::path::PathBuf; use crate::attribute_types::{CanBeEmpty, ID, NameToken}; #[allow(clippy::wildcard_imports)] 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; fn ids_mut(&mut self) -> &mut Vec; /// 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; fn names_mut(&mut self) -> &mut Vec; fn source(&self) -> &Option; fn source_mut(&mut self) -> &mut Option; fn classes(&self) -> &Vec; fn classes_mut(&mut self) -> &mut Vec; } #[derive(Debug, Default, PartialEq, Serialize, Clone)] pub struct CommonAttributes { #[serde(skip_serializing_if = "CanBeEmpty::is_empty")] ids: Vec, #[serde(skip_serializing_if = "CanBeEmpty::is_empty")] names: Vec, #[serde(skip_serializing_if = "CanBeEmpty::is_empty")] source: Option, #[serde(skip_serializing_if = "CanBeEmpty::is_empty")] classes: Vec, //TODO: dupnames } //----\\ //impl\\ //----\\ macro_rules! impl_element { ($name:ident) => { impl Element for $name { fn ids(&self) -> &Vec { &self.common.ids } fn ids_mut(&mut self) -> &mut Vec { &mut self.common.ids } fn names(&self) -> &Vec { &self.common.names } fn names_mut(&mut self) -> &mut Vec { &mut self.common.names } fn source(&self) -> &Option { &self.common.source } fn source_mut(&mut self) -> &mut Option { &mut self.common.source } fn classes(&self) -> &Vec { &self.common.classes } fn classes_mut(&mut self) -> &mut Vec { &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, ..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 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 } } )} #[allow(dead_code)] trait HasExtraAndChildren { fn with_extra_and_children(extra: A, children: Vec) -> Self; } impl HasExtraAndChildren for T where T: HasChildren + ExtraAttributes, { #[allow(clippy::needless_update)] fn with_extra_and_children(extra: A, mut children: Vec) -> 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])* #[derive(Debug,PartialEq,Serialize,Clone)] pub struct $name { $( $(#[$fattr])* $field: $typ, )* } impl $name { #[must_use] 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); }; } macro_rules! impl_elems { ( $( ($($args:tt)*) )* ) => ( $( impl_elem!($($args)*); )* )} #[derive(Default, Debug, Serialize)] pub struct Document { children: Vec, } 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 ); impl<'a> From<&'a str> for TextOrInlineElement { fn from(s: &'a str) -> Self { s.to_owned().into() } } pub trait LabelledFootnote { /// Get the footnote’s/footnote reference’s label node, if available /// /// # Errors /// Returns an error if the footnote has no label fn get_label(&self) -> Result<&str, anyhow::Error>; } impl LabelledFootnote for Footnote { fn get_label(&self) -> Result<&str, anyhow::Error> { use anyhow::{Context, bail}; let SubFootnote::Label(e) = self .children() .first() .context("Footnote has no children")? else { bail!("Non-auto footnote has no label"); }; match e .children() .first() .context("Footnote label has no child")? { TextOrInlineElement::String(s) => Ok(s.as_ref()), _ => bail!("Footnote label is not a string"), } } } impl LabelledFootnote for FootnoteReference { fn get_label(&self) -> Result<&str, anyhow::Error> { use anyhow::{Context, bail}; match self .children() .first() .context("Footnote reference has no child")? { TextOrInlineElement::String(s) => Ok(s.as_ref()), _ => bail!("Footnote reference is not a string"), } } } document_tree-0.4.2/src/extra_attributes.rs000064400000000000000000000133631046102023000172200ustar 00000000000000use serde_derive::Serialize; use crate::attribute_types::{ AlignH, AlignHV, AlignV, AutoFootnoteType, CanBeEmpty, EnumeratedListType, FixedSpace, ID, Measure, NameToken, TableAlignH, TableBorder, TableGroupCols, }; use crate::elements as e; use crate::url::Url; pub trait ExtraAttributes { 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, /// References to ids attributes in other elements refid: Option, /// Internal reference to the names attribute of another element. May resolve to either an internal or external reference. refname: Vec, anonymous: bool, }); impl_extra!(Raw { space: FixedSpace, format: Vec }); impl_extra!(#[derive(Debug,PartialEq,Serialize,Clone)] Image { uri: Url, align: Option, alt: Option, height: Option, width: Option, scale: Option, target: Option, // 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 }); impl_extra!(EnumeratedList { enumtype: Option, prefix: Option, suffix: Option }); impl_extra!(Footnote { backrefs: Vec, auto: Option }); impl_extra!(Citation { backrefs: Vec }); impl_extra!(SystemMessage { backrefs: Vec, level: Option, line: Option, type_: Option }); impl_extra!(Figure { align: Option, width: Option }); impl_extra!(Table { frame: Option, colsep: Option, rowsep: Option, pgwide: Option }); impl_extra!(TableGroup { cols: TableGroupCols, colsep: Option, rowsep: Option, align: Option }); impl_extra!(TableHead { valign: Option }); impl_extra!(TableBody { valign: Option }); impl_extra!(TableRow { rowsep: Option, valign: Option }); impl_extra!(TableEntry { colname: Option, namest: Option, nameend: Option, morerows: Option, colsep: Option, rowsep: Option, align: Option, r#char: Option, charoff: Option, valign: Option, morecols: Option }); impl_extra!(TableColspec { colnum: Option, colname: Option, colwidth: Option, colsep: Option, rowsep: Option, align: Option, r#char: Option, charoff: Option, stub: Option }); impl_extra!(OptionArgument { delimiter: Option }); impl_extra!(Reference { name: Option, //TODO: is CDATA in the DTD, so maybe no nametoken? /// External reference to a URI/URL refuri: Option, /// References to ids attributes in other elements refid: Option, /// Internal reference to the names attribute of another element refname: Vec, }); impl_extra!(FootnoteReference { refid: Option, refname: Vec, auto: Option }); impl_extra!(CitationReference { refid: Option, refname: Vec }); impl_extra!(SubstitutionReference { refname: Vec }); impl_extra!(Problematic { refid: Option }); //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, /// References to ids attributes in other elements refid: Option, /// Internal reference to the names attribute of another element. May resolve to either an internal or external reference. refname: Vec, anonymous: bool, }); impl_extra!(RawInline { space: FixedSpace, format: Vec }); pub type ImageInline = Image; pub trait FootnoteType { /// Is this an auto-numbered footnote? fn is_auto(&self) -> bool; /// Is this a symbolic footnote and not a numeric one? fn is_symbol(&self) -> bool; } impl FootnoteType for Option { fn is_auto(&self) -> bool { self.is_some() } fn is_symbol(&self) -> bool { matches!(self, Some(AutoFootnoteType::Symbol)) } } impl FootnoteType for e::Footnote { fn is_auto(&self) -> bool { self.extra().auto.is_auto() } fn is_symbol(&self) -> bool { self.extra().auto.is_symbol() } } impl FootnoteType for e::FootnoteReference { fn is_auto(&self) -> bool { self.extra().auto.is_auto() } fn is_symbol(&self) -> bool { self.extra().auto.is_symbol() } } impl Image { #[must_use] pub fn new(uri: Url) -> Image { Image { uri, align: None, alt: None, height: None, width: None, scale: None, target: None, } } } document_tree-0.4.2/src/lib.rs000064400000000000000000000025771046102023000144020ustar 00000000000000#![warn(clippy::pedantic)] #![recursion_limit = "256"] //! See [doctree][] reference. //! Serves as AST. //! //! [doctree]: https://docutils.sourceforge.net/docs/ref/doctree.html #[macro_use] mod macro_util; pub mod attribute_types; pub mod element_categories; pub mod elements; pub mod extra_attributes; pub mod url; pub use self::element_categories::HasChildren; pub use self::elements::*; //Element,CommonAttributes,HasExtraAndChildren pub use self::extra_attributes::ExtraAttributes; #[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:?}"); } } document_tree-0.4.2/src/macro_util.rs000064400000000000000000000022711046102023000157610ustar 00000000000000macro_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))); } } document_tree-0.4.2/src/url.rs000064400000000000000000000050551046102023000144300ustar 00000000000000use std::fmt; use std::str::FromStr; 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 } /// 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 { /// Parse an absolute URL. /// /// # Errors /// Returns an error if the string is not a valid absolute URL. pub fn parse_absolute(input: &str) -> Result { Ok(url::Url::parse(input)?.into()) } /// Parse a relative path as URL. /// /// # Errors /// Returns an error if the string is not a relative path or can’t be converted to an url. #[allow(clippy::missing_panics_doc)] pub fn parse_relative(input: &str) -> Result { // 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) } } #[must_use] pub fn as_str(&self) -> &str { self.0.as_str() } } impl From for Url { 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()) } } impl FromStr for Url { type Err = ParseError; fn from_str(input: &str) -> Result { Url::parse_absolute(input).or_else(|_| Url::parse_relative(input)) } }