Add filetime dependency and preserve modified time of exported files

This commit is contained in:
Davis Davalos-DeLosh 2023-12-28 23:27:02 -06:00
parent dba05d0e9c
commit b10698aa15
5 changed files with 184 additions and 13 deletions

101
Cargo.lock generated
View File

@ -131,7 +131,7 @@ checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd"
dependencies = [
"errno-dragonfly",
"libc",
"windows-sys",
"windows-sys 0.48.0",
]
[[package]]
@ -160,6 +160,18 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764"
[[package]]
name = "filetime"
version = "0.2.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"windows-sys 0.52.0",
]
[[package]]
name = "futures"
version = "0.3.28"
@ -407,6 +419,7 @@ name = "obsidian-export"
version = "23.12.0"
dependencies = [
"eyre",
"filetime",
"gumdrop",
"ignore",
"lazy_static",
@ -618,7 +631,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
"windows-sys 0.48.0",
]
[[package]]
@ -754,7 +767,7 @@ dependencies = [
"fastrand",
"redox_syscall",
"rustix",
"windows-sys",
"windows-sys 0.48.0",
]
[[package]]
@ -915,7 +928,16 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets",
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.0",
]
[[package]]
@ -924,13 +946,28 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]]
name = "windows-targets"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
dependencies = [
"windows_aarch64_gnullvm 0.52.0",
"windows_aarch64_msvc 0.52.0",
"windows_i686_gnu 0.52.0",
"windows_i686_msvc 0.52.0",
"windows_x86_64_gnu 0.52.0",
"windows_x86_64_gnullvm 0.52.0",
"windows_x86_64_msvc 0.52.0",
]
[[package]]
@ -939,42 +976,84 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]]
name = "yansi"
version = "0.5.1"

View File

@ -40,6 +40,7 @@ serde_yaml = "0.9.27"
slug = "0.1.5"
snafu = "0.7.5"
unicode-normalization = "0.1.22"
filetime = "0.2.23"
[dev-dependencies]
pretty_assertions = "1.4.0"

View File

@ -14,6 +14,7 @@ pub use context::Context;
pub use frontmatter::{Frontmatter, FrontmatterStrategy};
pub use walker::{vault_contents, WalkOptions};
use filetime::set_file_mtime;
use frontmatter::{frontmatter_from_str, frontmatter_to_str};
use pathdiff::diff_paths;
use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS};
@ -165,6 +166,20 @@ pub enum ExportError {
source: ignore::Error,
},
#[snafu(display("Failed to read the mtime of '{}'", path.display()))]
/// This occurs when a file's modified time cannot be read
ModTimeReadError {
path: PathBuf,
source: std::io::Error,
},
#[snafu(display("Failed to set the mtime of '{}'", path.display()))]
/// This occurs when a file's modified time cannot be set
ModTimeSetError {
path: PathBuf,
source: std::io::Error,
},
#[snafu(display("No such file or directory: {}", path.display()))]
/// This occurs when an operation is requested on a file or directory which does not exist.
PathDoesNotExist { path: PathBuf },
@ -231,6 +246,7 @@ pub struct Exporter<'a> {
vault_contents: Option<Vec<PathBuf>>,
walk_options: WalkOptions<'a>,
process_embeds_recursively: bool,
preserve_mtime: bool,
postprocessors: Vec<&'a Postprocessor<'a>>,
embed_postprocessors: Vec<&'a Postprocessor<'a>>,
}
@ -247,6 +263,7 @@ impl<'a> fmt::Debug for Exporter<'a> {
"process_embeds_recursively",
&self.process_embeds_recursively,
)
.field("preserve_mtime", &self.preserve_mtime)
.field(
"postprocessors",
&format!("<{} postprocessors active>", self.postprocessors.len()),
@ -273,6 +290,7 @@ impl<'a> Exporter<'a> {
frontmatter_strategy: FrontmatterStrategy::Auto,
walk_options: WalkOptions::default(),
process_embeds_recursively: true,
preserve_mtime: false,
vault_contents: None,
postprocessors: vec![],
embed_postprocessors: vec![],
@ -313,6 +331,15 @@ impl<'a> Exporter<'a> {
self
}
/// Set whether the modified time of exported files should be preserved.
///
/// When `preserve` is true, the modified time of exported files will be set to the modified
/// time of the source file.
pub fn preserve_mtime(&mut self, preserve: bool) -> &mut Exporter<'a> {
self.preserve_mtime = preserve;
self
}
/// 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 Exporter<'a> {
self.postprocessors.push(processor);
@ -388,11 +415,19 @@ impl<'a> Exporter<'a> {
}
fn export_note(&self, src: &Path, dest: &Path) -> Result<()> {
match is_markdown_file(src) {
let result = match is_markdown_file(src) {
true => self.parse_and_export_obsidian_note(src, dest),
false => copy_file(src, dest),
}
.context(FileExportSnafu { path: src })
.context(FileExportSnafu { path: src });
if self.preserve_mtime {
if let Ok(()) = result {
copy_mtime(src, dest)?;
}
}
result
}
fn parse_and_export_obsidian_note(&self, src: &Path, dest: &Path) -> Result<()> {
@ -762,6 +797,16 @@ fn create_file(dest: &Path) -> Result<File> {
Ok(file)
}
fn copy_mtime(src: &Path, dest: &Path) -> Result<()> {
let metadata = fs::metadata(src).context(ModTimeReadSnafu { path: src })?;
let modified_time = metadata
.modified()
.context(ModTimeReadSnafu { path: src })?;
set_file_mtime(dest, modified_time.into()).context(ModTimeSetSnafu { path: dest })?;
Ok(())
}
fn copy_file(src: &Path, dest: &Path) -> Result<()> {
std::fs::copy(src, dest)
.or_else(|err| {

View File

@ -54,6 +54,13 @@ struct Opts {
#[options(no_short, help = "Don't process embeds recursively", default = "false")]
no_recursive_embeds: bool,
#[options(
no_short,
help = "Preserve the mtime of exported files",
default = "false"
)]
preserve_mtime: bool,
#[options(
no_short,
help = "Convert soft line breaks to hard line breaks. This mimics Obsidian's 'Strict line breaks' setting",
@ -94,6 +101,7 @@ fn main() {
let mut exporter = Exporter::new(root, destination);
exporter.frontmatter_strategy(args.frontmatter_strategy);
exporter.process_embeds_recursively(!args.no_recursive_embeds);
exporter.preserve_mtime(args.preserve_mtime);
exporter.walk_options(walk_options);
if args.hard_linebreaks {

View File

@ -358,6 +358,44 @@ fn test_no_recursive_embeds() {
);
}
#[test]
fn test_preserve_mtime() {
let tmp_dir = TempDir::new().expect("failed to make tempdir");
let mut exporter = Exporter::new(
PathBuf::from("tests/testdata/input/main-samples/"),
tmp_dir.path().to_path_buf(),
);
exporter.preserve_mtime(true);
exporter.run().expect("exporter returned error");
let src = "tests/testdata/input/main-samples/obsidian-wikilinks.md";
let dest = tmp_dir.path().join(PathBuf::from("obsidian-wikilinks.md"));
let src_meta = std::fs::metadata(src).unwrap();
let dest_meta = std::fs::metadata(dest).unwrap();
assert_eq!(src_meta.modified().unwrap(), dest_meta.modified().unwrap());
}
#[test]
fn test_no_preserve_mtime() {
let tmp_dir = TempDir::new().expect("failed to make tempdir");
let mut exporter = Exporter::new(
PathBuf::from("tests/testdata/input/main-samples/"),
tmp_dir.path().to_path_buf(),
);
exporter.preserve_mtime(false);
exporter.run().expect("exporter returned error");
let src = "tests/testdata/input/main-samples/obsidian-wikilinks.md";
let dest = tmp_dir.path().join(PathBuf::from("obsidian-wikilinks.md"));
let src_meta = std::fs::metadata(src).unwrap();
let dest_meta = std::fs::metadata(dest).unwrap();
assert_ne!(src_meta.modified().unwrap(), dest_meta.modified().unwrap());
}
#[test]
fn test_non_ascii_filenames() {
let tmp_dir = TempDir::new().expect("failed to make tempdir");