Use nightly rust for formatting to group import statements
This commit is contained in:
parent
bd1220028b
commit
da9238a78c
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@ -19,7 +19,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
job:
|
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 test --all-targets --all-features
|
||||||
- cargo clippy --all-targets --all-features -- -D warning
|
- cargo clippy --all-targets --all-features -- -D warning
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@ -54,7 +54,7 @@ jobs:
|
|||||||
needs: populate-rust-cache
|
needs: populate-rust-cache
|
||||||
env:
|
env:
|
||||||
# These hooks are expensive and already run as dedicated jobs above
|
# These hooks are expensive and already run as dedicated jobs above
|
||||||
SKIP: "tests,clippy"
|
SKIP: "rustfmt,tests,clippy"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ repos:
|
|||||||
hooks:
|
hooks:
|
||||||
- id: rustfmt
|
- id: rustfmt
|
||||||
name: Check formatting
|
name: Check formatting
|
||||||
entry: cargo fmt --
|
entry: cargo +nightly fmt --
|
||||||
language: system
|
language: system
|
||||||
files: \.rs$
|
files: \.rs$
|
||||||
- id: tests
|
- id: tests
|
||||||
|
@ -35,9 +35,11 @@ You can see some examples of this in:
|
|||||||
|
|
||||||
## Conventions
|
## Conventions
|
||||||
|
|
||||||
Code is formatted with [rustfmt](https://github.com/rust-lang/rustfmt) using the default options.
|
Code is formatted with [rustfmt](https://github.com/rust-lang/rustfmt).
|
||||||
In addition, all default [clippy](https://github.com/rust-lang/rust-clippy) checks on the latest stable Rust compiler must also pass.
|
The nightly toolchain is used for this as things like sorting of imports is not yet available on stable yet.
|
||||||
Both of these are enforced through CI using GitHub actions.
|
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**
|
> **💡 Tip: install pre-commit hooks**
|
||||||
>
|
>
|
||||||
|
7
rustfmt.toml
Normal file
7
rustfmt.toml
Normal file
@ -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
|
@ -1,6 +1,7 @@
|
|||||||
use crate::Frontmatter;
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use crate::Frontmatter;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
/// Context holds metadata about a note which is being parsed.
|
/// 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 mut context = Context::new(PathBuf::from("source"), PathBuf::from("destination"));
|
||||||
/// let key = Value::String("foo".to_string());
|
/// let key = Value::String("foo".to_string());
|
||||||
///
|
///
|
||||||
/// context.frontmatter.insert(
|
/// context
|
||||||
/// key.clone(),
|
/// .frontmatter
|
||||||
/// Value::String("bar".to_string()),
|
/// .insert(key.clone(), Value::String("bar".to_string()));
|
||||||
/// );
|
|
||||||
/// ```
|
/// ```
|
||||||
pub frontmatter: Frontmatter,
|
pub frontmatter: Frontmatter,
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,8 @@ use serde_yaml::Result;
|
|||||||
|
|
||||||
/// YAML front matter from an Obsidian note.
|
/// YAML front matter from an Obsidian note.
|
||||||
///
|
///
|
||||||
/// This is essentially an alias of [`serde_yaml::Mapping`] so all the methods available on that type
|
/// This is essentially an alias of [`serde_yaml::Mapping`] so all the methods available on that
|
||||||
/// are available with `Frontmatter` as well.
|
/// type are available with `Frontmatter` as well.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
@ -14,10 +14,7 @@ use serde_yaml::Result;
|
|||||||
/// let mut frontmatter = Frontmatter::new();
|
/// let mut frontmatter = Frontmatter::new();
|
||||||
/// let key = Value::String("foo".to_string());
|
/// let key = Value::String("foo".to_string());
|
||||||
///
|
///
|
||||||
/// frontmatter.insert(
|
/// frontmatter.insert(key.clone(), Value::String("bar".to_string()));
|
||||||
/// key.clone(),
|
|
||||||
/// Value::String("bar".to_string()),
|
|
||||||
/// );
|
|
||||||
///
|
///
|
||||||
/// assert_eq!(
|
/// assert_eq!(
|
||||||
/// frontmatter.get(&key),
|
/// frontmatter.get(&key),
|
||||||
@ -67,10 +64,11 @@ pub enum FrontmatterStrategy {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use serde_yaml::Value;
|
use serde_yaml::Value;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn empty_string_should_yield_empty_frontmatter() {
|
fn empty_string_should_yield_empty_frontmatter() {
|
||||||
assert_eq!(frontmatter_from_str("").unwrap(), Frontmatter::new());
|
assert_eq!(frontmatter_from_str("").unwrap(), Frontmatter::new());
|
||||||
|
54
src/lib.rs
54
src/lib.rs
@ -1,5 +1,4 @@
|
|||||||
pub use pulldown_cmark;
|
pub use {pulldown_cmark, serde_yaml};
|
||||||
pub use serde_yaml;
|
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
@ -10,11 +9,16 @@ pub mod postprocessors;
|
|||||||
mod references;
|
mod references;
|
||||||
mod walker;
|
mod walker;
|
||||||
|
|
||||||
pub use context::Context;
|
use std::ffi::OsString;
|
||||||
pub use frontmatter::{Frontmatter, FrontmatterStrategy};
|
use std::fs::{self, File};
|
||||||
pub use walker::{vault_contents, WalkOptions};
|
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};
|
use frontmatter::{frontmatter_from_str, frontmatter_to_str};
|
||||||
|
pub use frontmatter::{Frontmatter, FrontmatterStrategy};
|
||||||
use pathdiff::diff_paths;
|
use pathdiff::diff_paths;
|
||||||
use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS};
|
use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS};
|
||||||
use pulldown_cmark::{CodeBlockKind, CowStr, Event, HeadingLevel, Options, Parser, Tag};
|
use pulldown_cmark::{CodeBlockKind, CowStr, Event, HeadingLevel, Options, Parser, Tag};
|
||||||
@ -23,14 +27,8 @@ use rayon::prelude::*;
|
|||||||
use references::{ObsidianNoteReference, RefParser, RefParserState, RefType};
|
use references::{ObsidianNoteReference, RefParser, RefParserState, RefType};
|
||||||
use slug::slugify;
|
use slug::slugify;
|
||||||
use snafu::{ResultExt, Snafu};
|
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;
|
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.
|
/// A series of markdown [Event]s that are generated while traversing an Obsidian markdown note.
|
||||||
pub type MarkdownEvents<'a> = Vec<Event<'a>>;
|
pub type MarkdownEvents<'a> = Vec<Event<'a>>;
|
||||||
@ -38,11 +36,12 @@ pub type MarkdownEvents<'a> = Vec<Event<'a>>;
|
|||||||
/// A post-processing function that is to be called after an Obsidian note has been fully parsed and
|
/// A post-processing function that is to be called after an Obsidian note has been fully parsed and
|
||||||
/// converted to regular markdown syntax.
|
/// converted to regular markdown syntax.
|
||||||
///
|
///
|
||||||
/// Postprocessors are called in the order they've been added through [`Exporter::add_postprocessor`]
|
/// Postprocessors are called in the order they've been added through
|
||||||
/// just before notes are written out to their final destination.
|
/// [`Exporter::add_postprocessor`] just before notes are written out to their final destination.
|
||||||
/// They may be used to achieve the following:
|
/// 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`].
|
/// 2. Change a note's contents by altering [`MarkdownEvents`].
|
||||||
/// 3. Prevent later postprocessors from running ([`PostprocessorResult::StopHere`]) or cause a note
|
/// 3. Prevent later postprocessors from running ([`PostprocessorResult::StopHere`]) or cause a note
|
||||||
/// to be skipped entirely ([`PostprocessorResult::StopAndSkipNote`]).
|
/// to be skipped entirely ([`PostprocessorResult::StopAndSkipNote`]).
|
||||||
@ -64,8 +63,8 @@ pub type MarkdownEvents<'a> = Vec<Event<'a>>;
|
|||||||
/// replaced with a blank document) but doesn't affect the root note.
|
/// 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
|
/// 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
|
/// [`Exporter::add_embed_postprocessor`]. The [`Context::note_depth`] method may be used to
|
||||||
/// whether a note is a root note or an embedded note in this situation.
|
/// determine whether a note is a root note or an embedded note in this situation.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
@ -104,8 +103,8 @@ pub type MarkdownEvents<'a> = Vec<Event<'a>>;
|
|||||||
///
|
///
|
||||||
/// ## Change note contents
|
/// ## Change note contents
|
||||||
///
|
///
|
||||||
/// In this example a note's markdown content is changed by iterating over the [`MarkdownEvents`] and
|
/// In this example a note's markdown content is changed by iterating over the [`MarkdownEvents`]
|
||||||
/// changing the text when we encounter a [text element][Event::Text].
|
/// 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
|
/// Instead of using a closure like above, this example shows how to use a separate function
|
||||||
/// definition.
|
/// definition.
|
||||||
@ -282,8 +281,9 @@ impl<'a> Exporter<'a> {
|
|||||||
|
|
||||||
/// Set a custom starting point for the export.
|
/// Set a custom starting point for the export.
|
||||||
///
|
///
|
||||||
/// Normally all notes under `root` (except for notes excluded by ignore rules) will be exported.
|
/// Normally all notes under `root` (except for notes excluded by ignore rules) will be
|
||||||
/// When `start_at` is set, only notes under this path will be exported to the target destination.
|
/// 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 {
|
pub fn start_at(&mut self, start_at: PathBuf) -> &mut Self {
|
||||||
self.start_at = start_at;
|
self.start_at = start_at;
|
||||||
self
|
self
|
||||||
@ -305,7 +305,8 @@ impl<'a> Exporter<'a> {
|
|||||||
///
|
///
|
||||||
/// When `recursive` is true (the default), emdeds are always processed recursively. This may
|
/// 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.
|
/// 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
|
/// 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.
|
/// original note, instead of embedding it again a link to the note is inserted instead.
|
||||||
@ -314,7 +315,8 @@ impl<'a> Exporter<'a> {
|
|||||||
self
|
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 {
|
pub fn add_postprocessor(&mut self, processor: &'a Postprocessor<'_>) -> &mut Self {
|
||||||
self.postprocessors.push(processor);
|
self.postprocessors.push(processor);
|
||||||
self
|
self
|
||||||
@ -718,8 +720,7 @@ impl<'a> Exporter<'a> {
|
|||||||
///
|
///
|
||||||
/// 1. Standard Obsidian note references not including a .md extension.
|
/// 1. Standard Obsidian note references not including a .md extension.
|
||||||
/// 2. Case-insensitive matching
|
/// 2. Case-insensitive matching
|
||||||
/// 3. Unicode normalization rules using normalization form C
|
/// 3. Unicode normalization rules using normalization form C (<https://www.w3.org/TR/charmod-norm/#unicodeNormalization>)
|
||||||
/// (<https://www.w3.org/TR/charmod-norm/#unicodeNormalization>)
|
|
||||||
fn lookup_filename_in_vault<'a>(
|
fn lookup_filename_in_vault<'a>(
|
||||||
filename: &str,
|
filename: &str,
|
||||||
vault_contents: &'a [PathBuf],
|
vault_contents: &'a [PathBuf],
|
||||||
@ -897,10 +898,11 @@ fn codeblock_kind_to_owned<'a>(codeblock_kind: CodeBlockKind<'_>) -> CodeBlockKi
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use rstest::rstest;
|
use rstest::rstest;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref VAULT: Vec<PathBuf> = vec![
|
static ref VAULT: Vec<PathBuf> = vec![
|
||||||
PathBuf::from("NoteA.md"),
|
PathBuf::from("NoteA.md"),
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
|
use std::env;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use eyre::{eyre, Result};
|
use eyre::{eyre, Result};
|
||||||
use gumdrop::Options;
|
use gumdrop::Options;
|
||||||
use obsidian_export::postprocessors::{filter_by_tags, softbreaks_to_hardbreaks};
|
use obsidian_export::postprocessors::{filter_by_tags, softbreaks_to_hardbreaks};
|
||||||
use obsidian_export::{ExportError, Exporter, FrontmatterStrategy, WalkOptions};
|
use obsidian_export::{ExportError, Exporter, FrontmatterStrategy, WalkOptions};
|
||||||
use std::{env, path::PathBuf};
|
|
||||||
|
|
||||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
//! A collection of officially maintained [postprocessors][crate::Postprocessor].
|
//! A collection of officially maintained [postprocessors][crate::Postprocessor].
|
||||||
|
|
||||||
use super::{Context, MarkdownEvents, PostprocessorResult};
|
|
||||||
use pulldown_cmark::Event;
|
use pulldown_cmark::Event;
|
||||||
use serde_yaml::Value;
|
use serde_yaml::Value;
|
||||||
|
|
||||||
|
use super::{Context, MarkdownEvents, PostprocessorResult};
|
||||||
|
|
||||||
/// This postprocessor converts all soft line breaks to hard line breaks. Enabling this mimics
|
/// This postprocessor converts all soft line breaks to hard line breaks. Enabling this mimics
|
||||||
/// Obsidian's _'Strict line breaks'_ setting.
|
/// Obsidian's _'Strict line breaks'_ setting.
|
||||||
pub fn softbreaks_to_hardbreaks(
|
pub fn softbreaks_to_hardbreaks(
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use regex::Regex;
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref OBSIDIAN_NOTE_LINK_RE: Regex =
|
static ref OBSIDIAN_NOTE_LINK_RE: Regex =
|
||||||
Regex::new(r"^(?P<file>[^#|]+)??(#(?P<section>.+?))??(\|(?P<label>.+?))??$").unwrap();
|
Regex::new(r"^(?P<file>[^#|]+)??(#(?P<section>.+?))??(\|(?P<label>.+?))??$").unwrap();
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
use crate::{ExportError, WalkDirSnafu};
|
|
||||||
use ignore::{DirEntry, Walk, WalkBuilder};
|
|
||||||
use snafu::ResultExt;
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use ignore::{DirEntry, Walk, WalkBuilder};
|
||||||
|
use snafu::ResultExt;
|
||||||
|
|
||||||
|
use crate::{ExportError, WalkDirSnafu};
|
||||||
|
|
||||||
type Result<T, E = ExportError> = std::result::Result<T, E>;
|
type Result<T, E = ExportError> = std::result::Result<T, E>;
|
||||||
type FilterFn = dyn Fn(&DirEntry) -> bool + Send + Sync + 'static;
|
type FilterFn = dyn Fn(&DirEntry) -> bool + Send + Sync + 'static;
|
||||||
|
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
#![allow(clippy::shadow_unrelated)]
|
#![allow(clippy::shadow_unrelated)]
|
||||||
|
|
||||||
|
use std::fs::{create_dir, read_to_string, set_permissions, File, Permissions};
|
||||||
|
use std::io::prelude::*;
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use obsidian_export::{ExportError, Exporter, FrontmatterStrategy};
|
use obsidian_export::{ExportError, Exporter, FrontmatterStrategy};
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use std::fs::{create_dir, read_to_string, set_permissions, File, Permissions};
|
|
||||||
use std::io::prelude::*;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
use std::os::unix::fs::PermissionsExt;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_main_variants_with_default_options() {
|
fn test_main_variants_with_default_options() {
|
||||||
let tmp_dir = TempDir::new().expect("failed to make tempdir");
|
let tmp_dir = TempDir::new().expect("failed to make tempdir");
|
||||||
@ -262,9 +262,9 @@ fn test_not_existing_destination_with_source_dir() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
// This test ensures that when source is a file, but destination points to a regular file
|
// This test ensures that when source is a file, but destination points to a
|
||||||
// inside of a non-existent directory, an error is raised instead of that directory path being
|
// regular file inside of a non-existent directory, an error is raised instead
|
||||||
// created (like `mkdir -p`)
|
// of that directory path being created (like `mkdir -p`)
|
||||||
fn test_not_existing_destination_with_source_file() {
|
fn test_not_existing_destination_with_source_file() {
|
||||||
let tmp_dir = TempDir::new().expect("failed to make tempdir");
|
let tmp_dir = TempDir::new().expect("failed to make tempdir");
|
||||||
|
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
use std::fs::{read_to_string, remove_file};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use obsidian_export::postprocessors::{filter_by_tags, softbreaks_to_hardbreaks};
|
use obsidian_export::postprocessors::{filter_by_tags, softbreaks_to_hardbreaks};
|
||||||
use obsidian_export::{Context, Exporter, MarkdownEvents, PostprocessorResult};
|
use obsidian_export::{Context, Exporter, MarkdownEvents, PostprocessorResult};
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use pulldown_cmark::{CowStr, Event};
|
use pulldown_cmark::{CowStr, Event};
|
||||||
use serde_yaml::Value;
|
use serde_yaml::Value;
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::fs::{read_to_string, remove_file};
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::sync::Mutex;
|
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
/// This postprocessor replaces any instance of "foo" with "bar" in the note body.
|
/// This postprocessor replaces any instance of "foo" with "bar" in the note
|
||||||
|
/// body.
|
||||||
fn foo_to_bar(_ctx: &mut Context, events: &mut MarkdownEvents<'_>) -> PostprocessorResult {
|
fn foo_to_bar(_ctx: &mut Context, events: &mut MarkdownEvents<'_>) -> PostprocessorResult {
|
||||||
for event in events.iter_mut() {
|
for event in events.iter_mut() {
|
||||||
if let Event::Text(text) = event {
|
if let Event::Text(text) = event {
|
||||||
@ -27,9 +29,9 @@ fn append_frontmatter(ctx: &mut Context, _events: &mut MarkdownEvents<'_>) -> Po
|
|||||||
PostprocessorResult::Continue
|
PostprocessorResult::Continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// The purpose of this test to verify the `append_frontmatter` postprocessor is called to extend
|
// The purpose of this test to verify the `append_frontmatter` postprocessor is
|
||||||
// the frontmatter, and the `foo_to_bar` postprocessor is called to replace instances of "foo" with
|
// called to extend the frontmatter, and the `foo_to_bar` postprocessor is
|
||||||
// "bar" (only in the note body).
|
// called to replace instances of "foo" with "bar" (only in the note body).
|
||||||
#[test]
|
#[test]
|
||||||
fn test_postprocessors() {
|
fn test_postprocessors() {
|
||||||
let tmp_dir = TempDir::new().expect("failed to make tempdir");
|
let tmp_dir = TempDir::new().expect("failed to make tempdir");
|
||||||
@ -106,8 +108,8 @@ fn test_postprocessor_change_destination() {
|
|||||||
assert!(new_note_path.exists());
|
assert!(new_note_path.exists());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure postprocessor type definition has proper lifetimes to allow state (here: `parents`)
|
// Ensure postprocessor type definition has proper lifetimes to allow state
|
||||||
// to be passed in. Otherwise, this fails with an error like:
|
// (here: `parents`) to be passed in. Otherwise, this fails with an error like:
|
||||||
// error[E0597]: `parents` does not live long enough
|
// error[E0597]: `parents` does not live long enough
|
||||||
// cast requires that `parents` is borrowed for `'static`
|
// cast requires that `parents` is borrowed for `'static`
|
||||||
#[test]
|
#[test]
|
||||||
@ -139,9 +141,9 @@ fn test_postprocessor_stateful_callback() {
|
|||||||
assert!(parents.contains(expected));
|
assert!(parents.contains(expected));
|
||||||
}
|
}
|
||||||
|
|
||||||
// The purpose of this test to verify the `append_frontmatter` postprocessor is called to extend
|
// The purpose of this test to verify the `append_frontmatter` postprocessor is
|
||||||
// the frontmatter, and the `foo_to_bar` postprocessor is called to replace instances of "foo" with
|
// called to extend the frontmatter, and the `foo_to_bar` postprocessor is
|
||||||
// "bar" (only in the note body).
|
// called to replace instances of "foo" with "bar" (only in the note body).
|
||||||
#[test]
|
#[test]
|
||||||
fn test_embed_postprocessors() {
|
fn test_embed_postprocessors() {
|
||||||
let tmp_dir = TempDir::new().expect("failed to make tempdir");
|
let tmp_dir = TempDir::new().expect("failed to make tempdir");
|
||||||
@ -162,8 +164,8 @@ fn test_embed_postprocessors() {
|
|||||||
assert_eq!(expected, actual);
|
assert_eq!(expected, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
// When StopAndSkipNote is used with an embed_preprocessor, it should skip the embedded note but
|
// When StopAndSkipNote is used with an embed_preprocessor, it should skip the
|
||||||
// continue with the rest of the note.
|
// embedded note but continue with the rest of the note.
|
||||||
#[test]
|
#[test]
|
||||||
fn test_embed_postprocessors_stop_and_skip() {
|
fn test_embed_postprocessors_stop_and_skip() {
|
||||||
let tmp_dir = TempDir::new().expect("failed to make tempdir");
|
let tmp_dir = TempDir::new().expect("failed to make tempdir");
|
||||||
@ -182,9 +184,10 @@ fn test_embed_postprocessors_stop_and_skip() {
|
|||||||
assert_eq!(expected, actual);
|
assert_eq!(expected, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This test verifies that the context which is passed to an embed postprocessor is actually
|
// This test verifies that the context which is passed to an embed postprocessor
|
||||||
// correct. Primarily, this means the frontmatter should reflect that of the note being embedded as
|
// is actually correct. Primarily, this means the frontmatter should reflect
|
||||||
// opposed to the frontmatter of the root note.
|
// that of the note being embedded as opposed to the frontmatter of the root
|
||||||
|
// note.
|
||||||
#[test]
|
#[test]
|
||||||
#[allow(clippy::manual_assert)]
|
#[allow(clippy::manual_assert)]
|
||||||
fn test_embed_postprocessors_context() {
|
fn test_embed_postprocessors_context() {
|
||||||
@ -203,9 +206,10 @@ fn test_embed_postprocessors_context() {
|
|||||||
.get(&Value::String("is_root_note".into()))
|
.get(&Value::String("is_root_note".into()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
if is_root_note != &Value::Bool(true) {
|
if is_root_note != &Value::Bool(true) {
|
||||||
// NOTE: Test failure may not give output consistently because the test binary affects
|
// NOTE: Test failure may not give output consistently because the test binary
|
||||||
// how output is captured and printed in the thread running this postprocessor. Just
|
// affects how output is captured and printed in the thread running
|
||||||
// run the test a couple times until the error shows up.
|
// this postprocessor. Just run the test a couple times until the
|
||||||
|
// error shows up.
|
||||||
panic!(
|
panic!(
|
||||||
"postprocessor: expected is_root_note in {} to be true, got false",
|
"postprocessor: expected is_root_note in {} to be true, got false",
|
||||||
&ctx.current_file().display()
|
&ctx.current_file().display()
|
||||||
@ -219,9 +223,10 @@ fn test_embed_postprocessors_context() {
|
|||||||
.get(&Value::String("is_root_note".into()))
|
.get(&Value::String("is_root_note".into()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
if is_root_note == &Value::Bool(true) {
|
if is_root_note == &Value::Bool(true) {
|
||||||
// NOTE: Test failure may not give output consistently because the test binary affects
|
// NOTE: Test failure may not give output consistently because the test binary
|
||||||
// how output is captured and printed in the thread running this postprocessor. Just
|
// affects how output is captured and printed in the thread running
|
||||||
// run the test a couple times until the error shows up.
|
// this postprocessor. Just run the test a couple times until the
|
||||||
|
// error shows up.
|
||||||
panic!(
|
panic!(
|
||||||
"embed_postprocessor: expected is_root_note in {} to be false, got true",
|
"embed_postprocessor: expected is_root_note in {} to be false, got true",
|
||||||
&ctx.current_file().display()
|
&ctx.current_file().display()
|
||||||
|
Loading…
Reference in New Issue
Block a user