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..eccef38 --- /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..f32687a --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,10 @@ +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 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", +} diff --git a/document_tree/src/attribute_types.rs b/document_tree/src/attribute_types.rs index 411b24d..ef00544 100644 --- a/document_tree/src/attribute_types.rs +++ b/document_tree/src/attribute_types.rs @@ -15,9 +15,13 @@ pub enum EnumeratedListType { 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 } @@ -32,12 +36,7 @@ impl Default for FixedSpace { fn default() -> FixedSpace { FixedSpace::Preserve // 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)] @@ -104,7 +103,7 @@ impl FromStr for Measure { #[cfg(test)] mod parse_tests { use super::*; - + #[test] fn measure() { let _a: Measure = "1.5em".parse().unwrap(); @@ -152,4 +151,3 @@ impl CanBeEmpty for bool { 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 index 24a0798..1f5fcde 100644 --- a/document_tree/src/element_categories.rs +++ b/document_tree/src/element_categories.rs @@ -25,9 +25,9 @@ macro_rules! impl_into { $( 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())) + impl From<$entry> for $supcat { + fn from(inner: $entry) -> Self { + $supcat::$subcat(Box::new(inner.into())) } } }; @@ -47,7 +47,7 @@ macro_rules! synonymous_enum { pub enum $name { $( $entry(Box<$entry>), )* } - + impl Debug for $name { fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> { match *self { @@ -55,10 +55,10 @@ macro_rules! synonymous_enum { } } } - - $( impl Into<$name> for $entry { - fn into(self) -> $name { - $name::$entry(Box::new(self)) + + $( impl From<$entry> for $name { + fn from(inner: $entry) -> Self { + $name::$entry(Box::new(inner)) } } )* }; @@ -106,22 +106,22 @@ synonymous_enum!(SubTableGroup { TableColspec, TableHead, TableBody }); 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(); diff --git a/document_tree/src/elements.rs b/document_tree/src/elements.rs index 1db0a24..e32c677 100644 --- a/document_tree/src/elements.rs +++ b/document_tree/src/elements.rs @@ -59,7 +59,7 @@ macro_rules! impl_element { ($name:ident) => ( 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 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 } } @@ -68,7 +68,7 @@ macro_rules! impl_children { ($name:ident, $childtype:ident) => ( 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: extra $($more)* } } + 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 } } @@ -82,7 +82,7 @@ impl HasExtraAndChildren for T where T: HasChildren + ExtraAtt #[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().extend(children.drain(..)); + r.children_mut().append(&mut children); r } } @@ -96,11 +96,11 @@ macro_rules! impl_new {( ) => ( $(#[$attr])* #[derive(Debug,PartialEq,Serialize,Clone)] - pub struct $name { $( + pub struct $name { $( $(#[$fattr])* $field: $typ, )* } impl $name { - pub fn new( $( $field: $typ, )* ) -> $name { $name { $( $field: $field, )* } } + pub fn new( $( $field: $typ, )* ) -> $name { $name { $( $field, )* } } } )} @@ -156,14 +156,14 @@ impl_elems!( (Section, StructuralSubElement) (Topic, SubTopic) (Sidebar, SubSidebar) - + //structural subelements (Title, TextOrInlineElement) (Subtitle, TextOrInlineElement) (Decoration, DecorationElement) (Docinfo, BibliographicElement) (Transition) - + //bibliographic elements (Author, TextOrInlineElement) (Authors, AuthorInfo) @@ -176,11 +176,11 @@ impl_elems!( (Date, TextOrInlineElement) (Copyright, TextOrInlineElement) (Field, SubField) - + //decoration elements (Header, BodyElement) (Footer, BodyElement) - + //simple body elements (Paragraph, TextOrInlineElement) (LiteralBlock, TextOrInlineElement; +) @@ -193,17 +193,17 @@ impl_elems!( (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) @@ -229,32 +229,32 @@ impl_elems!( (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) @@ -272,12 +272,12 @@ impl_elems!( (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 ); diff --git a/document_tree/src/lib.rs b/document_tree/src/lib.rs index 9154725..00d6cb2 100644 --- a/document_tree/src/lib.rs +++ b/document_tree/src/lib.rs @@ -1,7 +1,9 @@ #![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; diff --git a/document_tree/src/url.rs b/document_tree/src/url.rs index 31a0536..543f9e5 100644 --- a/document_tree/src/url.rs +++ b/document_tree/src/url.rs @@ -57,7 +57,7 @@ impl Url { impl From for Url { fn from(url: url::Url) -> Self { - Url(url.into_string()) + Url(url.into()) } } 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/src/conversion.rs b/parser/src/conversion.rs index de5f091..5e855ef 100644 --- a/parser/src/conversion.rs +++ b/parser/src/conversion.rs @@ -40,7 +40,7 @@ fn get_level<'tl>(toplevel: &'tl mut Vec, section_idxs: pub fn convert_document(pairs: Pairs) -> Result { use self::block::TitleOrSsubel::*; - + let mut toplevel: Vec = vec![]; // The kinds of section titles encountered. // `section_idx[x]` has the kind `kinds[x]`, but `kinds` can be longer @@ -49,7 +49,7 @@ pub fn convert_document(pairs: Pairs) -> Result { // `None`s indicate skipped section levels: // toplevel[section_idxs.flatten()[0]].children[section_idxs.flatten()[1]]... let mut section_idxs: Vec> = vec![]; - + for pair in pairs { if let Some(ssubel) = block::convert_ssubel(pair)? { match ssubel { Title(title, kind) => { @@ -83,7 +83,7 @@ pub fn convert_document(pairs: Pairs) -> Result { 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') + |ch: char| ch.is_whitespace() || ('\x1C'..='\x1F').contains(&ch) ).filter(|split| !split.is_empty()); let mut ret = String::new(); for split in split_iter { diff --git a/parser/src/conversion/block.rs b/parser/src/conversion/block.rs index 626bc20..a68dd17 100644 --- a/parser/src/conversion/block.rs +++ b/parser/src/conversion/block.rs @@ -37,8 +37,9 @@ pub(super) fn convert_ssubel(pair: Pair) -> Result, fn convert_substructure(pair: Pair) -> Result { + #[allow(clippy::match_single_binding)] Ok(match pair.as_rule() { - // todo: Topic, Sidebar, Transition + // TODO: Topic, Sidebar, Transition // no section here, as it’s constructed from titles _ => convert_body_elem(pair)?.into(), }) @@ -50,7 +51,7 @@ fn convert_body_elem(pair: Pair) -> Result { 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::admonition_gen => convert_admonition_gen(pair)?, Rule::image => convert_image::(pair)?.into(), Rule::bullet_list => convert_bullet_list(pair)?.into(), Rule::literal_block => convert_literal_block(pair).into(), @@ -84,7 +85,7 @@ fn convert_title(pair: Pair) -> Result<(e::Title, TitleKind), Error> { 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(" ", "-"); + let slug = title.to_lowercase().replace('\n', "").replace(' ', "-"); elem.names_mut().push(at::NameToken(slug)); } let title_kind = match kind { @@ -163,7 +164,7 @@ fn convert_image(pair: Pair) -> Result where I: Element + Ext } fn parse_scale(pair: &Pair) -> Result { - let input = if pair.as_str().chars().rev().next() == Some('%') { &pair.as_str()[..pair.as_str().len()-1] } else { pair.as_str() }; + 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 = ErrorVariant::CustomError { message: e.to_string() }; @@ -215,7 +216,7 @@ fn convert_literal_lines(pair: Pair) -> e::LiteralBlock { Rule::literal_line_blank => "\n", _ => unreachable!(), }.into()).collect(); - return e::LiteralBlock::with_children(children); + e::LiteralBlock::with_children(children) } fn convert_code_directive(pair: Pair) -> e::LiteralBlock { diff --git a/parser/src/conversion/tests.rs b/parser/src/conversion/tests.rs index 89b0a1c..e042d01 100644 --- a/parser/src/conversion/tests.rs +++ b/parser/src/conversion/tests.rs @@ -41,11 +41,11 @@ 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] @@ -55,7 +55,7 @@ fn convert_skipped_section() { 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] diff --git a/parser/src/lib.rs b/parser/src/lib.rs index 23e97c7..4f1b8dd 100644 --- a/parser/src/lib.rs +++ b/parser/src/lib.rs @@ -22,7 +22,7 @@ pub fn parse_only(source: &str) -> Result { 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 { parse_only(source).map(resolve_references) } diff --git a/parser/src/pair_ext_parse.rs b/parser/src/pair_ext_parse.rs index a04b3dd..fb5ba6b 100644 --- a/parser/src/pair_ext_parse.rs +++ b/parser/src/pair_ext_parse.rs @@ -6,16 +6,16 @@ use pest::error::{Error,ErrorVariant}; pub trait PairExt where R: pest::RuleType { - fn parse(&self) -> Result> where T: FromStr, E: ToString; + fn parse(&self) -> Result>> where T: FromStr, E: ToString; } impl<'l, R> PairExt for Pair<'l, R> where R: pest::RuleType { - fn parse(&self) -> Result> where T: FromStr, E: ToString { + fn parse(&self) -> Result>> where T: FromStr, E: ToString { self.as_str().parse().map_err(|e| to_parse_error(self.as_span(), &e)) } } -pub(crate) fn to_parse_error(span: Span, e: &E) -> Error where E: ToString, R: pest::RuleType { +pub(crate) fn to_parse_error(span: Span, e: &E) -> Box> where E: ToString, R: pest::RuleType { let var: ErrorVariant = ErrorVariant::CustomError { message: e.to_string() }; - Error::new_from_span(var, span) + Box::new(Error::new_from_span(var, span)) } diff --git a/parser/src/simplify.rs b/parser/src/simplify.rs index 4c254af..ab04964 100644 --- a/parser/src/simplify.rs +++ b/parser/src/simplify.rs @@ -33,6 +33,7 @@ use document_tree::{ #[derive(Debug)] +#[allow(dead_code)] enum NamedTargetType { NumberedFootnote(usize), LabeledFootnote(usize), @@ -43,11 +44,9 @@ enum NamedTargetType { SectionTitle, } impl NamedTargetType { + #[allow(dead_code)] fn is_implicit_target(&self) -> bool { - match self { - NamedTargetType::SectionTitle => true, - _ => false, - } + matches!(self, NamedTargetType::SectionTitle) } } @@ -55,7 +54,7 @@ impl NamedTargetType { struct Substitution { content: Vec, /// If true and the sibling before the reference is a text node, - /// the text node gets right-trimmed. + /// the text node gets right-trimmed. ltrim: bool, /// Same as `ltrim` with the sibling after the reference. rtrim: bool, @@ -79,7 +78,7 @@ impl TargetsCollected { _ => unimplemented!(), } } - + fn substitution<'t>(self: &'t TargetsCollected, refname: &[NameToken]) -> Option<&'t Substitution> { // TODO: Check if the substitution would expand circularly if refname.len() != 1 { @@ -378,7 +377,7 @@ impl ResolvableRefs for c::TextOrInlineElement { // 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 = Box::new(Default::default()); + let mut replacement: Box = Box::default(); replacement.children_mut().push( c::TextOrInlineElement::String(Box::new(format!("|{}|", e.extra().refname[0].0))) ); diff --git a/renderer/src/html/tests.rs b/renderer/src/html/tests.rs index a4ec633..c29e9d8 100644 --- a/renderer/src/html/tests.rs +++ b/renderer/src/html/tests.rs @@ -307,7 +307,7 @@ fn code() { def foo(): print('Hi!') - + # comment ", "\
def foo():
diff --git a/rst/src/main.rs b/rst/src/main.rs
index 318bcb6..cf16b16 100644
--- a/rst/src/main.rs
+++ b/rst/src/main.rs
@@ -34,7 +34,7 @@ struct Cli {
 fn main() -> CliResult {
 	let args = Cli::from_args();
 	args.verbosity.setup_env_logger("rst")?;
-	
+
 	// TODO: somehow make it work without replacing tabs
 	let content = read_file(args.file)?.replace('\t', " ".repeat(8).as_ref());
 	let document = parse(&content)?;
diff --git a/rust-rst.svg b/rust-rst.svg
index 894715d..a67350a 100644
--- a/rust-rst.svg
+++ b/rust-rst.svg
@@ -22,7 +22,7 @@
 	}
 
 	/* can be used to regenerate/change the logo */
-	.rust-rst-logo text { 
+	.rust-rst-logo text {
 		display: none;
 		font-size: 90.66667175px;
 		line-height: 1.25;