parent
8ace49ded3
commit
f47e86b859
|
@ -138,9 +138,12 @@ To completely remove any frontmatter from exported notes, use `--frontmatter=nev
|
|||
|
||||
## Ignoring files
|
||||
|
||||
By default, hidden files, patterns listed in `.export-ignore` as well as any files ignored by git (if your vault is part of a git repository) will be excluded from exports.
|
||||
The following files are not exported by default:
|
||||
- hidden files (can be adjusted with `--hidden`)
|
||||
- files mattching a pattern listed in `.export-ignore` (can be adjusted with `--ignore-file`)
|
||||
- any files that are ignored by git (can be adjusted with `--no-git`)
|
||||
- any files having `private: true` in their frontmatter (the keyword `private` can be changed with `--ignore-frontmatter-keyword`)
|
||||
|
||||
These options may be adjusted with `--hidden`, `--ignore-file` and `--no-git` if desired.
|
||||
(See `--help` for more information).
|
||||
|
||||
Notes linking to ignored notes will be unlinked (they'll only include the link text).
|
||||
|
|
68
src/lib.rs
68
src/lib.rs
|
@ -228,6 +228,7 @@ pub struct Exporter<'a> {
|
|||
destination: PathBuf,
|
||||
start_at: PathBuf,
|
||||
frontmatter_strategy: FrontmatterStrategy,
|
||||
ignore_frontmatter_keyword: &'a str,
|
||||
vault_contents: Option<Vec<PathBuf>>,
|
||||
walk_options: WalkOptions<'a>,
|
||||
process_embeds_recursively: bool,
|
||||
|
@ -241,6 +242,10 @@ impl<'a> fmt::Debug for Exporter<'a> {
|
|||
.field("root", &self.root)
|
||||
.field("destination", &self.destination)
|
||||
.field("frontmatter_strategy", &self.frontmatter_strategy)
|
||||
.field(
|
||||
"ignore_frontmatter_keyword",
|
||||
&self.ignore_frontmatter_keyword,
|
||||
)
|
||||
.field("vault_contents", &self.vault_contents)
|
||||
.field("walk_options", &self.walk_options)
|
||||
.field(
|
||||
|
@ -271,6 +276,7 @@ impl<'a> Exporter<'a> {
|
|||
root,
|
||||
destination,
|
||||
frontmatter_strategy: FrontmatterStrategy::Auto,
|
||||
ignore_frontmatter_keyword: "private",
|
||||
walk_options: WalkOptions::default(),
|
||||
process_embeds_recursively: true,
|
||||
vault_contents: None,
|
||||
|
@ -299,6 +305,11 @@ impl<'a> Exporter<'a> {
|
|||
self.frontmatter_strategy = strategy;
|
||||
self
|
||||
}
|
||||
/// Set the frontmatter keyword that excludes files from being exported.
|
||||
pub fn ignore_frontmatter_keyword(&mut self, keyword: &'a str) -> &mut Exporter<'a> {
|
||||
self.ignore_frontmatter_keyword = keyword;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the behavior when recursive embeds are encountered.
|
||||
///
|
||||
|
@ -399,34 +410,39 @@ impl<'a> Exporter<'a> {
|
|||
let mut context = Context::new(src.to_path_buf(), dest.to_path_buf());
|
||||
|
||||
let (frontmatter, mut markdown_events) = self.parse_obsidian_note(src, &context)?;
|
||||
context.frontmatter = frontmatter;
|
||||
for func in &self.postprocessors {
|
||||
match func(&mut context, &mut markdown_events) {
|
||||
PostprocessorResult::StopHere => break,
|
||||
PostprocessorResult::StopAndSkipNote => return Ok(()),
|
||||
PostprocessorResult::Continue => (),
|
||||
match frontmatter.get(self.ignore_frontmatter_keyword) {
|
||||
Some(serde_yaml::Value::Bool(true)) => Ok(()),
|
||||
_ => {
|
||||
context.frontmatter = frontmatter;
|
||||
for func in &self.postprocessors {
|
||||
match func(&mut context, &mut markdown_events) {
|
||||
PostprocessorResult::StopHere => break,
|
||||
PostprocessorResult::StopAndSkipNote => return Ok(()),
|
||||
PostprocessorResult::Continue => (),
|
||||
}
|
||||
}
|
||||
|
||||
let dest = context.destination;
|
||||
let mut outfile = create_file(&dest)?;
|
||||
let write_frontmatter = match self.frontmatter_strategy {
|
||||
FrontmatterStrategy::Always => true,
|
||||
FrontmatterStrategy::Never => false,
|
||||
FrontmatterStrategy::Auto => !context.frontmatter.is_empty(),
|
||||
};
|
||||
if write_frontmatter {
|
||||
let mut frontmatter_str = frontmatter_to_str(context.frontmatter)
|
||||
.context(FrontMatterEncodeSnafu { path: src })?;
|
||||
frontmatter_str.push('\n');
|
||||
outfile
|
||||
.write_all(frontmatter_str.as_bytes())
|
||||
.context(WriteSnafu { path: &dest })?;
|
||||
}
|
||||
outfile
|
||||
.write_all(render_mdevents_to_mdtext(markdown_events).as_bytes())
|
||||
.context(WriteSnafu { path: &dest })?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
let dest = context.destination;
|
||||
let mut outfile = create_file(&dest)?;
|
||||
let write_frontmatter = match self.frontmatter_strategy {
|
||||
FrontmatterStrategy::Always => true,
|
||||
FrontmatterStrategy::Never => false,
|
||||
FrontmatterStrategy::Auto => !context.frontmatter.is_empty(),
|
||||
};
|
||||
if write_frontmatter {
|
||||
let mut frontmatter_str = frontmatter_to_str(context.frontmatter)
|
||||
.context(FrontMatterEncodeSnafu { path: src })?;
|
||||
frontmatter_str.push('\n');
|
||||
outfile
|
||||
.write_all(frontmatter_str.as_bytes())
|
||||
.context(WriteSnafu { path: &dest })?;
|
||||
}
|
||||
outfile
|
||||
.write_all(render_mdevents_to_mdtext(markdown_events).as_bytes())
|
||||
.context(WriteSnafu { path: &dest })?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_obsidian_note<'b>(
|
||||
|
|
|
@ -39,6 +39,13 @@ struct Opts {
|
|||
)]
|
||||
ignore_file: String,
|
||||
|
||||
#[options(
|
||||
no_short,
|
||||
help = "Exclude files with this keyword in the frontmatter from export",
|
||||
default = "private"
|
||||
)]
|
||||
ignore_frontmatter_keyword: String,
|
||||
|
||||
#[options(no_short, help = "Export hidden files", default = "false")]
|
||||
hidden: bool,
|
||||
|
||||
|
|
|
@ -425,3 +425,78 @@ fn test_same_filename_different_directories() {
|
|||
let actual = read_to_string(tmp_dir.path().clone().join(PathBuf::from("Note.md"))).unwrap();
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ignore_frontmatter_default_keyword() {
|
||||
let tmp_dir = TempDir::new().expect("failed to make tempdir");
|
||||
let mut exporter = Exporter::new(
|
||||
PathBuf::from("tests/testdata/input/ignore-keyword/"),
|
||||
tmp_dir.path().to_path_buf(),
|
||||
);
|
||||
exporter.run().expect("exporter returned error");
|
||||
|
||||
let walker = WalkDir::new("tests/testdata/expected/ignore-default-keyword/")
|
||||
// Without sorting here, different test runs may trigger the first assertion failure in
|
||||
// unpredictable order.
|
||||
.sort_by(|a, b| a.file_name().cmp(b.file_name()))
|
||||
.into_iter();
|
||||
for entry in walker {
|
||||
let entry = entry.unwrap();
|
||||
if entry.metadata().unwrap().is_dir() {
|
||||
continue;
|
||||
};
|
||||
let filename = entry.file_name().to_string_lossy().into_owned();
|
||||
let expected = read_to_string(entry.path()).unwrap_or_else(|_| {
|
||||
panic!(
|
||||
"failed to read {} from testdata/expected/ignore-default-keyword/",
|
||||
entry.path().display()
|
||||
)
|
||||
});
|
||||
let actual = read_to_string(tmp_dir.path().clone().join(PathBuf::from(&filename)))
|
||||
.unwrap_or_else(|_| panic!("failed to read {} from temporary exportdir", filename));
|
||||
|
||||
assert_eq!(
|
||||
expected, actual,
|
||||
"{} does not have expected content",
|
||||
filename
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ignore_frontmatter_specific_keyword() {
|
||||
let tmp_dir = TempDir::new().expect("failed to make tempdir");
|
||||
let mut exporter = Exporter::new(
|
||||
PathBuf::from("tests/testdata/input/ignore-keyword/"),
|
||||
tmp_dir.path().to_path_buf(),
|
||||
);
|
||||
exporter.ignore_frontmatter_keyword("no-expört");
|
||||
exporter.run().expect("exporter returned error");
|
||||
|
||||
let walker = WalkDir::new("tests/testdata/expected/ignore-specific-keyword/")
|
||||
// Without sorting here, different test runs may trigger the first assertion failure in
|
||||
// unpredictable order.
|
||||
.sort_by(|a, b| a.file_name().cmp(b.file_name()))
|
||||
.into_iter();
|
||||
for entry in walker {
|
||||
let entry = entry.unwrap();
|
||||
if entry.metadata().unwrap().is_dir() {
|
||||
continue;
|
||||
};
|
||||
let filename = entry.file_name().to_string_lossy().into_owned();
|
||||
let expected = read_to_string(entry.path()).unwrap_or_else(|_| {
|
||||
panic!(
|
||||
"failed to read {} from testdata/expected/ignore-specific-keyword/",
|
||||
entry.path().display()
|
||||
)
|
||||
});
|
||||
let actual = read_to_string(tmp_dir.path().clone().join(PathBuf::from(&filename)))
|
||||
.unwrap_or_else(|_| panic!("failed to read {} from temporary exportdir", filename));
|
||||
|
||||
assert_eq!(
|
||||
expected, actual,
|
||||
"{} does not have expected content",
|
||||
filename
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
no-expört: false
|
||||
---
|
||||
|
||||
A note with negated special ignore keyword
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
no-expört: true
|
||||
---
|
||||
|
||||
A note with a special ignore keyword
|
|
@ -0,0 +1 @@
|
|||
A note without frontmatter should be exported.
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
private: false
|
||||
---
|
||||
|
||||
A public note.
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
no-expört: false
|
||||
---
|
||||
|
||||
A note with negated special ignore keyword
|
|
@ -0,0 +1 @@
|
|||
A note without frontmatter should be exported.
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
private: true
|
||||
---
|
||||
|
||||
A private note.
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
private: false
|
||||
---
|
||||
|
||||
A public note.
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
no-expört: false
|
||||
---
|
||||
A note with negated special ignore keyword
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
no-expört: true
|
||||
---
|
||||
A note with a special ignore keyword
|
|
@ -0,0 +1 @@
|
|||
A note without frontmatter should be exported.
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
private: true
|
||||
---
|
||||
A private note.
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
private: false
|
||||
---
|
||||
A public note.
|
Loading…
Reference in New Issue