diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 29a50e1..6dede6a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: strategy: matrix: job: - - cargo fmt --all -- --check + - rustup toolchain install nightly --profile minimal --component rustfmt && cargo +nightly fmt --all -- --check - cargo test --all-targets --all-features - cargo clippy --all-targets --all-features -- -D warning fail-fast: false @@ -54,7 +54,7 @@ jobs: needs: populate-rust-cache env: # These hooks are expensive and already run as dedicated jobs above - SKIP: "tests,clippy" + SKIP: "rustfmt,tests,clippy" steps: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a4e28c3..6b63919 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: hooks: - id: rustfmt name: Check formatting - entry: cargo fmt -- + entry: cargo +nightly fmt -- language: system files: \.rs$ - id: tests diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8b0b377..3c68cd0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,9 +35,11 @@ You can see some examples of this in: ## Conventions -Code is formatted with [rustfmt](https://github.com/rust-lang/rustfmt) using the default options. -In addition, all default [clippy](https://github.com/rust-lang/rust-clippy) checks on the latest stable Rust compiler must also pass. -Both of these are enforced through CI using GitHub actions. +Code is formatted with [rustfmt](https://github.com/rust-lang/rustfmt). +The nightly toolchain is used for this as things like sorting of imports is not yet available on stable yet. +If you don't have the nightly toolchain installed, run: `rustup toolchain install nightly --component rustfmt` + +In addition, [clippy](https://github.com/rust-lang/rust-clippy) is configured to be quite pedantic and all of its checks must also pass for CI builds to succeed. > **💡 Tip: install pre-commit hooks** > diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..5f59aa1 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,7 @@ +comment_width = 120 +format_code_in_doc_comments = true +group_imports = "StdExternalCrate" +imports_granularity = "Module" +imports_layout = "HorizontalVertical" +use_field_init_shorthand = true +wrap_comments = true diff --git a/src/context.rs b/src/context.rs index 64c4621..4773d44 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,6 +1,7 @@ -use crate::Frontmatter; use std::path::{Path, PathBuf}; +use crate::Frontmatter; + #[derive(Debug, Clone)] /// Context holds metadata about a note which is being parsed. /// @@ -36,10 +37,9 @@ pub struct Context { /// # let mut context = Context::new(PathBuf::from("source"), PathBuf::from("destination")); /// let key = Value::String("foo".to_string()); /// - /// context.frontmatter.insert( - /// key.clone(), - /// Value::String("bar".to_string()), - /// ); + /// context + /// .frontmatter + /// .insert(key.clone(), Value::String("bar".to_string())); /// ``` pub frontmatter: Frontmatter, } diff --git a/src/frontmatter.rs b/src/frontmatter.rs index e80e8bf..7e5e56c 100644 --- a/src/frontmatter.rs +++ b/src/frontmatter.rs @@ -2,8 +2,8 @@ use serde_yaml::Result; /// YAML front matter from an Obsidian note. /// -/// This is essentially an alias of [`serde_yaml::Mapping`] so all the methods available on that type -/// are available with `Frontmatter` as well. +/// This is essentially an alias of [`serde_yaml::Mapping`] so all the methods available on that +/// type are available with `Frontmatter` as well. /// /// # Examples /// @@ -14,10 +14,7 @@ use serde_yaml::Result; /// let mut frontmatter = Frontmatter::new(); /// let key = Value::String("foo".to_string()); /// -/// frontmatter.insert( -/// key.clone(), -/// Value::String("bar".to_string()), -/// ); +/// frontmatter.insert(key.clone(), Value::String("bar".to_string())); /// /// assert_eq!( /// frontmatter.get(&key), @@ -67,10 +64,11 @@ pub enum FrontmatterStrategy { #[cfg(test)] mod tests { - use super::*; use pretty_assertions::assert_eq; use serde_yaml::Value; + use super::*; + #[test] fn empty_string_should_yield_empty_frontmatter() { assert_eq!(frontmatter_from_str("").unwrap(), Frontmatter::new()); diff --git a/src/lib.rs b/src/lib.rs index 7cfc7c8..a8265e1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,4 @@ -pub use pulldown_cmark; -pub use serde_yaml; +pub use {pulldown_cmark, serde_yaml}; #[macro_use] extern crate lazy_static; @@ -10,11 +9,16 @@ pub mod postprocessors; mod references; mod walker; -pub use context::Context; -pub use frontmatter::{Frontmatter, FrontmatterStrategy}; -pub use walker::{vault_contents, WalkOptions}; +use std::ffi::OsString; +use std::fs::{self, File}; +use std::io::prelude::*; +use std::io::ErrorKind; +use std::path::{Path, PathBuf}; +use std::{fmt, str}; +pub use context::Context; use frontmatter::{frontmatter_from_str, frontmatter_to_str}; +pub use frontmatter::{Frontmatter, FrontmatterStrategy}; use pathdiff::diff_paths; use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS}; use pulldown_cmark::{CodeBlockKind, CowStr, Event, HeadingLevel, Options, Parser, Tag}; @@ -23,14 +27,8 @@ use rayon::prelude::*; use references::{ObsidianNoteReference, RefParser, RefParserState, RefType}; use slug::slugify; use snafu::{ResultExt, Snafu}; -use std::ffi::OsString; -use std::fmt; -use std::fs::{self, File}; -use std::io::prelude::*; -use std::io::ErrorKind; -use std::path::{Path, PathBuf}; -use std::str; use unicode_normalization::UnicodeNormalization; +pub use walker::{vault_contents, WalkOptions}; /// A series of markdown [Event]s that are generated while traversing an Obsidian markdown note. pub type MarkdownEvents<'a> = Vec>; @@ -38,11 +36,12 @@ pub type MarkdownEvents<'a> = Vec>; /// A post-processing function that is to be called after an Obsidian note has been fully parsed and /// converted to regular markdown syntax. /// -/// Postprocessors are called in the order they've been added through [`Exporter::add_postprocessor`] -/// just before notes are written out to their final destination. +/// Postprocessors are called in the order they've been added through +/// [`Exporter::add_postprocessor`] just before notes are written out to their final destination. /// They may be used to achieve the following: /// -/// 1. Modify a note's [Context], for example to change the destination filename or update its [Frontmatter] (see [`Context::frontmatter`]). +/// 1. Modify a note's [Context], for example to change the destination filename or update its +/// [Frontmatter] (see [`Context::frontmatter`]). /// 2. Change a note's contents by altering [`MarkdownEvents`]. /// 3. Prevent later postprocessors from running ([`PostprocessorResult::StopHere`]) or cause a note /// to be skipped entirely ([`PostprocessorResult::StopAndSkipNote`]). @@ -64,8 +63,8 @@ pub type MarkdownEvents<'a> = Vec>; /// replaced with a blank document) but doesn't affect the root note. /// /// It's possible to pass the same functions to [`Exporter::add_postprocessor`] and -/// [`Exporter::add_embed_postprocessor`]. The [`Context::note_depth`] method may be used to determine -/// whether a note is a root note or an embedded note in this situation. +/// [`Exporter::add_embed_postprocessor`]. The [`Context::note_depth`] method may be used to +/// determine whether a note is a root note or an embedded note in this situation. /// /// # Examples /// @@ -104,8 +103,8 @@ pub type MarkdownEvents<'a> = Vec>; /// /// ## Change note contents /// -/// In this example a note's markdown content is changed by iterating over the [`MarkdownEvents`] and -/// changing the text when we encounter a [text element][Event::Text]. +/// In this example a note's markdown content is changed by iterating over the [`MarkdownEvents`] +/// and changing the text when we encounter a [text element][Event::Text]. /// /// Instead of using a closure like above, this example shows how to use a separate function /// definition. @@ -282,8 +281,9 @@ impl<'a> Exporter<'a> { /// Set a custom starting point for the export. /// - /// Normally all notes under `root` (except for notes excluded by ignore rules) will be exported. - /// When `start_at` is set, only notes under this path will be exported to the target destination. + /// Normally all notes under `root` (except for notes excluded by ignore rules) will be + /// exported. When `start_at` is set, only notes under this path will be exported to the + /// target destination. pub fn start_at(&mut self, start_at: PathBuf) -> &mut Self { self.start_at = start_at; self @@ -305,7 +305,8 @@ impl<'a> Exporter<'a> { /// /// When `recursive` is true (the default), emdeds are always processed recursively. This may /// lead to infinite recursion when note A embeds B, but B also embeds A. - /// (When this happens, [`ExportError::RecursionLimitExceeded`] will be returned by [`Exporter::run`]). + /// (When this happens, [`ExportError::RecursionLimitExceeded`] will be returned by + /// [`Exporter::run`]). /// /// When `recursive` is false, if a note is encountered for a second time while processing the /// original note, instead of embedding it again a link to the note is inserted instead. @@ -314,7 +315,8 @@ impl<'a> Exporter<'a> { self } - /// Append a function to the chain of [postprocessors][Postprocessor] to run on exported Obsidian Markdown notes. + /// Append a function to the chain of [postprocessors][Postprocessor] to run on exported + /// Obsidian Markdown notes. pub fn add_postprocessor(&mut self, processor: &'a Postprocessor<'_>) -> &mut Self { self.postprocessors.push(processor); self @@ -718,8 +720,7 @@ impl<'a> Exporter<'a> { /// /// 1. Standard Obsidian note references not including a .md extension. /// 2. Case-insensitive matching -/// 3. Unicode normalization rules using normalization form C -/// () +/// 3. Unicode normalization rules using normalization form C () fn lookup_filename_in_vault<'a>( filename: &str, vault_contents: &'a [PathBuf], @@ -897,10 +898,11 @@ fn codeblock_kind_to_owned<'a>(codeblock_kind: CodeBlockKind<'_>) -> CodeBlockKi #[cfg(test)] mod tests { - use super::*; use pretty_assertions::assert_eq; use rstest::rstest; + use super::*; + lazy_static! { static ref VAULT: Vec = vec![ PathBuf::from("NoteA.md"), diff --git a/src/main.rs b/src/main.rs index 273af39..7c4ff19 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,10 @@ +use std::env; +use std::path::PathBuf; + use eyre::{eyre, Result}; use gumdrop::Options; use obsidian_export::postprocessors::{filter_by_tags, softbreaks_to_hardbreaks}; use obsidian_export::{ExportError, Exporter, FrontmatterStrategy, WalkOptions}; -use std::{env, path::PathBuf}; const VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/src/postprocessors.rs b/src/postprocessors.rs index 16b3786..966baca 100644 --- a/src/postprocessors.rs +++ b/src/postprocessors.rs @@ -1,9 +1,10 @@ //! A collection of officially maintained [postprocessors][crate::Postprocessor]. -use super::{Context, MarkdownEvents, PostprocessorResult}; use pulldown_cmark::Event; use serde_yaml::Value; +use super::{Context, MarkdownEvents, PostprocessorResult}; + /// This postprocessor converts all soft line breaks to hard line breaks. Enabling this mimics /// Obsidian's _'Strict line breaks'_ setting. pub fn softbreaks_to_hardbreaks( diff --git a/src/references.rs b/src/references.rs index f5b04ee..1d7e731 100644 --- a/src/references.rs +++ b/src/references.rs @@ -1,6 +1,7 @@ -use regex::Regex; use std::fmt; +use regex::Regex; + lazy_static! { static ref OBSIDIAN_NOTE_LINK_RE: Regex = Regex::new(r"^(?P[^#|]+)??(#(?P
.+?))??(\|(?P