Compare commits

...

79 Commits

Author SHA1 Message Date
dependabot[bot] a7517106e8 Bump actions/setup-python from 4 to 5
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4 to 5.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-03 23:36:36 +01:00
Nick Groenen dba05d0e9c
Fix license specifier in Cargo metadata 2023-12-03 17:44:41 +01:00
Nick Groenen 82b6e597c6
Release v23.12.0 2023-12-03 17:38:24 +01:00
Nick Groenen 90d7dc3cf4
Update make-new-release.sh 2023-12-03 17:35:56 +01:00
Nick Groenen 7bbd211732
Remove leftover `--ignore-frontmatter-keyword` docs reference
An earlier iteration of #163 included this flag, but the final version
only uses `--skip-tags` and `--only-tags`.
2023-12-03 17:21:44 +01:00
Nick Groenen d6f8b4e692
Use cargo-dist to create release artifacts
This will create binaries for more platforms (including ARM builds for
MacOS) and installer scripts in addition to just the binaries themselves.
2023-12-03 17:06:31 +01:00
Nick Groenen ab3fe66d5c
Replace gitchangelog with git-cliff
gitchangelog doesn't look very maintained anymore. Git cliff makes for a
more modern replacement that is a little easier to install as well.
2023-12-03 13:26:35 +01:00
Nick Groenen 151679788a
Fix: trim filenames while resolving wikilinks
Obsidian trims the filename part in a [[WikiLink|label]], so each of
these are equivalent:

[[wikilink]]
[[ wikilink ]]
[[ wikilink |wikilink]]

Obsidian-export now behaves similarly.

Fixes #188
2023-12-02 12:19:16 +01:00
dependabot[bot] 4b88ad2b51 Bump serde_yaml from 0.9.25 to 0.9.27
Bumps [serde_yaml](https://github.com/dtolnay/serde-yaml) from 0.9.25 to 0.9.27.
- [Release notes](https://github.com/dtolnay/serde-yaml/releases)
- [Commits](https://github.com/dtolnay/serde-yaml/compare/0.9.25...0.9.27)

---
updated-dependencies:
- dependency-name: serde_yaml
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-02 11:42:43 +01:00
dependabot[bot] c7e0196fbe Bump eyre from 0.6.8 to 0.6.9
Bumps [eyre](https://github.com/eyre-rs/eyre) from 0.6.8 to 0.6.9.
- [Commits](https://github.com/eyre-rs/eyre/compare/v0.6.8...eyre@0.6.9)

---
updated-dependencies:
- dependency-name: eyre
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-02 11:42:31 +01:00
dependabot[bot] ede7a06c24 Bump tempfile from 3.8.0 to 3.8.1
Bumps [tempfile](https://github.com/Stebalien/tempfile) from 3.8.0 to 3.8.1.
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/commits)

---
updated-dependencies:
- dependency-name: tempfile
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-02 11:31:39 +01:00
dependabot[bot] f3d063aab4 Bump slug from 0.1.4 to 0.1.5
Bumps [slug](https://github.com/Stebalien/slug-rs) from 0.1.4 to 0.1.5.
- [Commits](https://github.com/Stebalien/slug-rs/compare/v0.1.4...v0.1.5)

---
updated-dependencies:
- dependency-name: slug
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-02 11:31:24 +01:00
dependabot[bot] 06b2a3d6a4 Bump ignore from 0.4.20 to 0.4.21
Bumps [ignore](https://github.com/BurntSushi/ripgrep) from 0.4.20 to 0.4.21.
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/commits)

---
updated-dependencies:
- dependency-name: ignore
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-02 11:30:54 +01:00
dependabot[bot] 0606eca6de Bump pulldown-cmark-to-cmark from 11.0.0 to 11.0.2
Bumps [pulldown-cmark-to-cmark](https://github.com/Byron/pulldown-cmark-to-cmark) from 11.0.0 to 11.0.2.
- [Release notes](https://github.com/Byron/pulldown-cmark-to-cmark/releases)
- [Changelog](https://github.com/Byron/pulldown-cmark-to-cmark/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Byron/pulldown-cmark-to-cmark/compare/v11.0.0...v11.0.2)

---
updated-dependencies:
- dependency-name: pulldown-cmark-to-cmark
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-02 11:30:44 +01:00
dependabot[bot] d3b1bd412e Bump percent-encoding from 2.3.0 to 2.3.1
Bumps [percent-encoding](https://github.com/servo/rust-url) from 2.3.0 to 2.3.1.
- [Release notes](https://github.com/servo/rust-url/releases)
- [Commits](https://github.com/servo/rust-url/compare/v2.3.0...v2.3.1)

---
updated-dependencies:
- dependency-name: percent-encoding
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-02 11:30:31 +01:00
Martin Heuschober 018c9606a6
Implement frontmatter based filtering (#163)
This allows limiting the notes that will be exported using `--skip-tags` and `--only-tags`

---------

Co-authored-by: Martin Heuschober <martin.heuschober@posteo.net>
Co-authored-by: Nick Groenen <nick@groenen.me>
Co-authored-by: Martin Heuschober <martin_heuschober@trimble.com>
2023-12-02 11:29:29 +01:00
dependabot[bot] eb4c009207 Bump rustix from 0.38.14 to 0.38.19
Bumps [rustix](https://github.com/bytecodealliance/rustix) from 0.38.14 to 0.38.19.
- [Release notes](https://github.com/bytecodealliance/rustix/releases)
- [Commits](https://github.com/bytecodealliance/rustix/compare/v0.38.14...v0.38.19)

---
updated-dependencies:
- dependency-name: rustix
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-20 15:53:51 +02:00
dependabot[bot] 4e99f03a1f Bump regex from 1.9.6 to 1.10.2
Bumps [regex](https://github.com/rust-lang/regex) from 1.9.6 to 1.10.2.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.9.6...1.10.2)

---
updated-dependencies:
- dependency-name: regex
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-17 09:47:38 +02:00
dependabot[bot] a791273d12 Bump actions/setup-python from 3 to 4
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 3 to 4.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-06 16:49:13 +02:00
dependabot[bot] 97958f81c5 Bump regex from 1.9.5 to 1.9.6
Bumps [regex](https://github.com/rust-lang/regex) from 1.9.5 to 1.9.6.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.9.5...1.9.6)

---
updated-dependencies:
- dependency-name: regex
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-06 16:48:57 +02:00
Nick Groenen 8ace49ded3
Merge branch 'pre-commit' 2023-09-26 09:13:16 +02:00
Nick Groenen 4c74371b9e
Use /bin/bash when generating README
set -o pipefail is a bashism that not all shells support. Notably, this
fails with the default /bin/sh on GitHub Actions Linux runners
2023-09-26 09:08:56 +02:00
Nick Groenen 5985ad70d6
Run pre-commit on GitHub Actions 2023-09-26 09:06:19 +02:00
Nick Groenen 3d98d65403
Add additional common pre-commit-hooks
I have these hooks enabled on many of my projects, but for some reason
forgot them here which led to the issue described in
https://github.com/zoni/obsidian-export/pull/181

This should help prevent that from happening again.
2023-09-26 08:48:06 +02:00
Nick Groenen 06191eb66e
Regenerate README 2023-09-26 08:45:14 +02:00
Robert Sesek 43d90d7879 Rename docs/contibuting.md to docs/contribute.md
On case-insensitive filesystems, the part file conflicts with the
docs/CONTRIBUTING.md symlink.
2023-09-26 08:42:16 +02:00
Robert Sesek cd5dbf6c3b Add a lifetime annotation to the Postprocesor type
This lets the compiler reason about the lifetimes of objects used by the
postprocessor, if the callback captures variables.

See zoni/obsidian-export#175
2023-09-25 21:50:34 +02:00
Nick Groenen c27d7b96b6
Optimize GitHub Actions workflows
Most notably:

- Stop using unmaintained setup-rust action
- Cache cargo and target directories
- Use sccache (https://github.com/mozilla/sccache/)
2023-09-24 12:15:18 +02:00
Nick Groenen b38e4d53b5
Remove changelog from main README file 2023-09-24 12:14:47 +02:00
Nick Groenen b28e4913ee
Relicense to BSD-2-Clause Plus Patent License
This license achieves everything that dual-licensing under MIT + Apache
aims for, but without the weirdness of being under two licenses.

Having checked external contributions, I feel pretty confident that I
can unilaterally make this license change, as people have only
contributed a handful of one-line changes of no significance towards
copyrighted work up to this point.
2023-09-24 12:05:15 +02:00
Nick Groenen f72ef651c0
Update transient dependencies
crates.io index
    crossbeam-deque v0.8.1 -> v0.8.3
    crossbeam-epoch v0.9.5 -> v0.9.15
    crossbeam-utils v0.8.12 -> v0.8.16
    deunicode v0.4.3 -> v0.4.4
    diff v0.1.12 -> v0.1.13
    either v1.6.1 -> v1.9.0
    futures v0.3.25 -> v0.3.28
    futures-channel v0.3.25 -> v0.3.28
    futures-core v0.3.25 -> v0.3.28
    futures-executor v0.3.25 -> v0.3.28
    futures-io v0.3.25 -> v0.3.28
    futures-macro v0.3.25 -> v0.3.28
    futures-sink v0.3.25 -> v0.3.28
    futures-task v0.3.25 -> v0.3.28
    futures-util v0.3.25 -> v0.3.28
    heck v0.4.0 -> v0.4.1
    itoa v1.0.4 -> v1.0.9
    log v0.4.14 -> v0.4.20
    memoffset v0.6.5 -> v0.9.0
    once_cell v1.9.0 -> v1.18.0
    pin-project-lite v0.2.9 -> v0.2.13
    ryu v1.0.11 -> v1.0.15
    scopeguard v1.1.0 -> v1.2.0
    semver v1.0.14 -> v1.0.19
    serde v1.0.147 -> v1.0.188
    serde_derive v1.0.188
    slab v0.4.7 -> v0.4.9
    syn v1.0.104 -> v1.0.109
    thread_local v1.1.4 -> v1.1.7
    tinyvec_macros v0.1.0 -> v0.1.1
    unicase v2.6.0 -> v2.7.0
    unicode-ident v1.0.5 -> v1.0.12
    unicode-width v0.1.10 -> v0.1.11
    winapi-util v0.1.5 -> v0.1.6
2023-09-23 10:42:07 +02:00
dependabot[bot] 143f4fe1cd
Bump rayon from 1.6.0 to 1.8.0 (#174) 2023-09-23 08:23:09 +00:00
dependabot[bot] 33707d67a5
Bump walkdir from 2.3.2 to 2.4.0 (#173) 2023-09-23 08:22:38 +00:00
dependabot[bot] 88b2378862
Bump regex from 1.9.4 to 1.9.5 (#178)
Bumps [regex](https://github.com/rust-lang/regex) from 1.9.4 to 1.9.5.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.9.4...1.9.5)

---
updated-dependencies:
- dependency-name: regex
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-23 09:39:47 +02:00
dependabot[bot] 52d400ff01
Bump rstest from 0.17.0 to 0.18.2 (#179) 2023-09-23 07:39:37 +00:00
dependabot[bot] b67aef7cdc
Bump actions/checkout from 3 to 4 (#177) 2023-09-23 07:38:42 +00:00
dependabot[bot] d76bbdb3b7
Bump ignore from 0.4.18 to 0.4.20 (#145) 2023-09-22 09:59:06 +00:00
dependabot[bot] 0dd235279e
Bump pulldown-cmark from 0.9.2 to 0.9.3 (#165) 2023-09-22 09:46:48 +00:00
dependabot[bot] e197ac3408
Bump tempfile from 3.3.0 to 3.8.0 (#172) 2023-09-22 09:43:56 +00:00
dependabot[bot] 33c57f2322
Bump snafu from 0.7.3 to 0.7.5 (#164) 2023-09-22 09:43:30 +00:00
dependabot[bot] ae87431847
Bump pulldown-cmark-to-cmark from 10.0.4 to 11.0.0 (#168) 2023-09-22 09:42:21 +00:00
dependabot[bot] 942b954a48
Bump serde_yaml from 0.9.14 to 0.9.25 (#167) 2023-09-22 09:39:01 +00:00
dependabot[bot] 277e057191
Bump rstest from 0.16.0 to 0.17.0 (#166) 2023-09-22 09:35:03 +00:00
dependabot[bot] 90573cab3e
Bump regex from 1.7.0 to 1.9.4 (#170) 2023-09-22 09:32:44 +00:00
dependabot[bot] 29772f8f5c
Bump percent-encoding from 2.2.0 to 2.3.0 (#171) 2023-09-22 09:32:25 +00:00
dependabot[bot] 303e2053be
Bump pretty_assertions from 1.3.0 to 1.4.0 (#169) 2023-09-22 09:31:11 +00:00
Nick Groenen cb6abedcad
Fix code coverage CI failures 2023-09-22 11:20:32 +02:00
Nick Groenen 4b636c4402
Fix 4 new clippy lints 2023-09-22 11:16:29 +02:00
dependabot[bot] 80130260e9
Bump rstest from 0.15.0 to 0.16.0 (#135)
Bumps [rstest](https://github.com/la10736/rstest) from 0.15.0 to 0.16.0.
- [Release notes](https://github.com/la10736/rstest/releases)
- [Changelog](https://github.com/la10736/rstest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/la10736/rstest/compare/0.15.0...0.16.0)

---
updated-dependencies:
- dependency-name: rstest
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-29 10:54:47 +01:00
dependabot[bot] 500f0fb86b
Bump rayon from 1.5.3 to 1.6.0 (#134)
Bumps [rayon](https://github.com/rayon-rs/rayon) from 1.5.3 to 1.6.0.
- [Release notes](https://github.com/rayon-rs/rayon/releases)
- [Changelog](https://github.com/rayon-rs/rayon/blob/master/RELEASES.md)
- [Commits](https://github.com/rayon-rs/rayon/compare/v1.5.3...rayon-core-v1.6.0)

---
updated-dependencies:
- dependency-name: rayon
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-29 10:54:28 +01:00
Nick Groenen 83ab69aedd
Release v22.11.0 2022-11-19 17:12:13 +01:00
Nick Groenen b5b2ea2c3b
New: apply unicode normalization while resolving notes
The unicode standard allows for certain (visually) identical characters to
be represented in different ways.

For example the character ä may be represented as a single combined
codepoint "Latin Small Letter A with Diaeresis" (U+00E4) or by the
combination of "Latin Small Letter A" (U+0061) followed by "Combining
Diaeresis" (U+0308).

When encoded with UTF-8, these are represented as respectively the two
bytes 0xC3 0xA4, and the three bytes 0x61 0xCC 0x88.

A user linking to notes with these characters in their titles would
expect these two variants to link to the same file, given they are
visually identical and have the exact same semantic meaning.

The unicode standard defines a method to deconstruct and normalize these
forms, so that a byte comparison on the normalized forms of these
variants ends up comparing the same thing. This is called Unicode
Normalization, defined in Unicode® Standard Annex #15
(http://www.unicode.org/reports/tr15/).

The W3C Working Group has written an excellent explanation of the
problems regarding string matching, and how unicode normalization helps
with this process: https://www.w3.org/TR/charmod-norm/#unicodeNormalization

With this change, obsidian-export will perform unicode normalization
(specifically the C (or NFC) normalization form) on all note titles
while looking up link references, ensuring visually identical links are
treated as being similar, even if they were encoded as different
variants.

A special thanks to Hans Raaf (@oderwat) for reporting and helping track
down this issue.

---

Closes #126
2022-11-19 16:58:48 +01:00
Chang-Yen Tseng c5ba5b7aef
Use path.Join to construct hugo links (#92)
Use path.Join so that it will render correctly on Windows
(path.Join will convert Windows backslash to forward slash)
2022-11-15 18:07:18 +01:00
dependabot[bot] 9bce284697 Bump crossbeam-utils from 0.8.5 to 0.8.12
Bumps [crossbeam-utils](https://github.com/crossbeam-rs/crossbeam) from 0.8.5 to 0.8.12.
- [Release notes](https://github.com/crossbeam-rs/crossbeam/releases)
- [Changelog](https://github.com/crossbeam-rs/crossbeam/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crossbeam-rs/crossbeam/compare/crossbeam-utils-0.8.5...crossbeam-utils-0.8.12)

---
updated-dependencies:
- dependency-name: crossbeam-utils
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-15 17:55:54 +01:00
dependabot[bot] f2a0d1c041 Bump regex from 1.6.0 to 1.7.0
Bumps [regex](https://github.com/rust-lang/regex) from 1.6.0 to 1.7.0.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.6.0...1.7.0)

---
updated-dependencies:
- dependency-name: regex
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-15 17:55:38 +01:00
dependabot[bot] 0659924635 Bump actions/checkout from 2 to 3
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-05 15:41:22 +01:00
dependabot[bot] 7a3f278e4b Bump actions/upload-artifact from 2 to 3
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 2 to 3.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-05 15:41:06 +01:00
dependabot[bot] 89ad2d0e66 Bump thread_local from 1.1.3 to 1.1.4
Bumps [thread_local](https://github.com/Amanieu/thread_local-rs) from 1.1.3 to 1.1.4.
- [Release notes](https://github.com/Amanieu/thread_local-rs/releases)
- [Commits](https://github.com/Amanieu/thread_local-rs/compare/v1.1.3...1.1.4)

---
updated-dependencies:
- dependency-name: thread_local
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-05 15:39:37 +01:00
Nick Groenen be5cf58c1a
Remove needless borrows 2022-11-05 15:37:20 +01:00
Nick Groenen 6af4c9140c
Upgrade snafu to 0.7.x 2022-11-05 14:38:02 +01:00
Nick Groenen 17d0e3df7e
Upgrade pulldown-cmark-to-cmark to 10.0.x 2022-11-05 14:38:02 +01:00
Nick Groenen 262f22ba70
Upgrade serde_yaml to 0.9.x 2022-11-05 14:38:02 +01:00
Nick Groenen 0535de53a9
Upgrade minor dependencies 2022-11-05 14:37:56 +01:00
Nick Groenen 868f1132bc
Fix new clippy lints 2022-11-05 14:18:53 +01:00
Nick Groenen 586530cac8 Add a contributor guide 2022-11-05 14:15:18 +01:00
Nick Groenen 081eb6c9ab
Simplify pre-commit setup
No need to depend on a third-party hook repository when each of these
checks is easily defined and run through system commands.

This also allows us to actually run tests, which is current unsupported
(https://github.com/doublify/pre-commit-rust/pull/19)
2022-01-16 17:06:52 +01:00
Nick Groenen d25c6d80c6 Chg: Pass context and events as mutable references to postprocessors
Instead of passing clones of context and the markdown tree to
postprocessors, pass them a mutable reference which may be modified
in-place.

This is a breaking change to the postprocessor implementation, changing
both the input arguments as well as the return value:

```diff
-    dyn Fn(Context, MarkdownEvents) -> (Context, MarkdownEvents, PostprocessorResult) + Send + Sync;
+    dyn Fn(&mut Context, &mut MarkdownEvents) -> PostprocessorResult + Send + Sync;
```

With this change the postprocessor API becomes a little more ergonomic
to use however, especially making the intent around return statements more clear.
2022-01-16 11:53:15 +01:00
dependabot[bot] 85adc314b6 Bump tempfile from 3.2.0 to 3.3.0
Bumps [tempfile](https://github.com/Stebalien/tempfile) from 3.2.0 to 3.3.0.
- [Release notes](https://github.com/Stebalien/tempfile/releases)
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/NEWS)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.2.0...v3.3.0)

---
updated-dependencies:
- dependency-name: tempfile
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-10 20:30:36 +01:00
Nick Groenen 86af6bbf37
Release v22.1.0 2022-01-02 12:28:25 +01:00
Nick Groenen 67cd5ac738
Give release binaries file extensions
This may make it more clear to users that these are precompiled, binary
files. This is especially relevant on Windows, where the convention is
that executable files have a .exe` extension, as seen in #49.
2022-01-02 12:08:50 +01:00
Nick Groenen 84308c9f1f
New: support Obsidian's "Strict line breaks" setting
This change introduces a new `--hard-linebreaks` CLI argument. When
used, this converts soft line breaks to hard line breaks, mimicking
Obsidian's "Strict line breaks" setting.

Implementation detail: I considered naming this flag
`--strict-line-breaks` to be consistent with Obsidian itself, however I
feel the name is somewhat misleading and ill-chosen.
2022-01-02 00:42:51 +01:00
Nick Groenen 838881fea0
Upgrade dependencies
This commit upgrades all dependencies to their current latest versions. Most
notably, this includes upgrades to the following most critical libraries:

    pulldown-cmark v0.8.0 -> v0.9.0
    pulldown-cmark-to-cmark v7.1.1 -> v9.0.0

In total, these dependencies were upgraded:

    bstr v0.2.16 -> v0.2.17
    ignore v0.4.17 -> v0.4.18
    libc v0.2.101 -> v0.2.112
    memoffset v0.6.4 -> v0.6.5
    num_cpus v1.13.0 -> v1.13.1
    once_cell v1.8.0 -> v1.9.0
    ppv-lite86 v0.2.10 -> v0.2.16
    proc-macro2 v1.0.29 -> v1.0.36
    pulldown-cmark v0.8.0 -> v0.9.0
    pulldown-cmark-to-cmark v7.1.1 -> v9.0.0
    quote v1.0.9 -> v1.0.14
    rayon v1.5.0 -> v1.5.1
    regex v1.5.3 -> v1.5.4
    serde v1.0.130 -> v1.0.132
    syn v1.0.75 -> v1.0.84
    unicode-width v0.1.8 -> v0.1.9
    version_check v0.9.3 -> v0.9.4
2022-01-01 23:34:46 +01:00
dependabot[bot] c96acc1d6d
Bump serde_yaml from 0.8.21 to 0.8.23 (#52)
Bumps [serde_yaml](https://github.com/dtolnay/serde-yaml) from 0.8.21 to 0.8.23.
- [Release notes](https://github.com/dtolnay/serde-yaml/releases)
- [Commits](https://github.com/dtolnay/serde-yaml/compare/0.8.21...0.8.23)

---
updated-dependencies:
- dependency-name: serde_yaml
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-12-14 16:49:32 +01:00
dependabot[bot] 7026b0f684
Bump pulldown-cmark-to-cmark from 7.1.0 to 7.1.1 (#51)
Bumps [pulldown-cmark-to-cmark](https://github.com/Byron/pulldown-cmark-to-cmark) from 7.1.0 to 7.1.1.
- [Release notes](https://github.com/Byron/pulldown-cmark-to-cmark/releases)
- [Changelog](https://github.com/Byron/pulldown-cmark-to-cmark/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Byron/pulldown-cmark-to-cmark/compare/v7.1.0...v7.1.1)

---
updated-dependencies:
- dependency-name: pulldown-cmark-to-cmark
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-12-06 23:31:49 +01:00
dependabot[bot] fe896ddd47
Bump pulldown-cmark-to-cmark from 7.0.0 to 7.1.0 (#48)
Bumps [pulldown-cmark-to-cmark](https://github.com/Byron/pulldown-cmark-to-cmark) from 7.0.0 to 7.1.0.
- [Release notes](https://github.com/Byron/pulldown-cmark-to-cmark/releases)
- [Changelog](https://github.com/Byron/pulldown-cmark-to-cmark/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Byron/pulldown-cmark-to-cmark/compare/v7.0.0...v7.1.0)

---
updated-dependencies:
- dependency-name: pulldown-cmark-to-cmark
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-30 18:37:41 +01:00
dependabot[bot] fd346baee9
Bump pulldown-cmark-to-cmark from 6.0.4 to 7.0.0 (#47)
Bumps [pulldown-cmark-to-cmark](https://github.com/Byron/pulldown-cmark-to-cmark) from 6.0.4 to 7.0.0.
- [Release notes](https://github.com/Byron/pulldown-cmark-to-cmark/releases)
- [Changelog](https://github.com/Byron/pulldown-cmark-to-cmark/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Byron/pulldown-cmark-to-cmark/compare/v6.0.4...v7.0.0)

---
updated-dependencies:
- dependency-name: pulldown-cmark-to-cmark
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-22 22:46:50 +01:00
dependabot[bot] 740346fa4c
Bump pathdiff from 0.2.0 to 0.2.1 (#46)
Bumps [pathdiff](https://github.com/Manishearth/pathdiff) from 0.2.0 to 0.2.1.
- [Release notes](https://github.com/Manishearth/pathdiff/releases)
- [Commits](https://github.com/Manishearth/pathdiff/commits)

---
updated-dependencies:
- dependency-name: pathdiff
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-06 19:38:25 +02:00
dependabot[bot] e78f19a2fa
Bump pulldown-cmark-to-cmark from 6.0.3 to 6.0.4 (#44)
Bumps [pulldown-cmark-to-cmark](https://github.com/Byron/pulldown-cmark-to-cmark) from 6.0.3 to 6.0.4.
- [Release notes](https://github.com/Byron/pulldown-cmark-to-cmark/releases)
- [Changelog](https://github.com/Byron/pulldown-cmark-to-cmark/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Byron/pulldown-cmark-to-cmark/compare/v6.0.3...v6.0.4)

---
updated-dependencies:
- dependency-name: pulldown-cmark-to-cmark
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-06 19:38:18 +02:00
dependabot[bot] 9a3ace0070
Bump pretty_assertions from 0.7.2 to 1.0.0 (#45)
Bumps [pretty_assertions](https://github.com/colin-kiegel/rust-pretty-assertions) from 0.7.2 to 1.0.0.
- [Release notes](https://github.com/colin-kiegel/rust-pretty-assertions/releases)
- [Changelog](https://github.com/colin-kiegel/rust-pretty-assertions/blob/main/CHANGELOG.md)
- [Commits](https://github.com/colin-kiegel/rust-pretty-assertions/compare/v0.7.2...v1.0.0)

---
updated-dependencies:
- dependency-name: pretty_assertions
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-06 19:38:01 +02:00
55 changed files with 2174 additions and 1598 deletions

View File

@ -1,312 +0,0 @@
# vim: set ft=python
##
## Format
##
## ACTION: [AUDIENCE:] COMMIT_MSG [!TAG ...]
##
## Description
##
## ACTION is one of 'chg', 'fix', 'new'
##
## Is WHAT the change is about.
##
## 'chg' is for refactor, small improvement, cosmetic changes...
## 'fix' is for bug fixes
## 'new' is for new features, big improvement
##
## AUDIENCE is optional and one of 'dev', 'usr', 'pkg', 'test', 'doc'
##
## Is WHO is concerned by the change.
##
## 'dev' is for developpers (API changes, refactors...)
## 'usr' is for final users (UI changes)
## 'pkg' is for packagers (packaging changes)
## 'test' is for testers (test only related changes)
## 'doc' is for doc guys (doc only changes)
##
## COMMIT_MSG is ... well ... the commit message itself.
##
## TAGs are additionnal adjective as 'refactor' 'minor' 'cosmetic'
##
## They are preceded with a '!' or a '@' (prefer the former, as the
## latter is wrongly interpreted in github.) Commonly used tags are:
##
## 'refactor' is obviously for refactoring code only
## 'minor' is for a very meaningless change (a typo, adding a comment)
## 'cosmetic' is for cosmetic driven change (re-indentation, 80-col...)
## 'wip' is for partial functionality but complete subfunctionality.
##
## Example:
##
## new: usr: support of bazaar implemented
## chg: re-indentend some lines !cosmetic
## new: dev: updated code to be compatible with last version of killer lib.
## fix: pkg: updated year of licence coverage.
## new: test: added a bunch of test around user usability of feature X.
## fix: typo in spelling my name in comment. !minor
##
## Please note that multi-line commit message are supported, and only the
## first line will be considered as the "summary" of the commit message. So
## tags, and other rules only applies to the summary. The body of the commit
## message will be displayed in the changelog without reformatting.
##
## ``ignore_regexps`` is a line of regexps
##
## Any commit having its full commit message matching any regexp listed here
## will be ignored and won't be reported in the changelog.
##
ignore_regexps = [
r'!skip_changelog',
r'^Release v[0-9]+\.[0-9]+\.[0-9]+$',
r'^(.{3,3}\s*:)?\s*[Ii]nitial commit.?\s*$',
]
## ``section_regexps`` is a list of 2-tuples associating a string label and a
## list of regexp
##
## Commit messages will be classified in sections thanks to this. Section
## titles are the label, and a commit is classified under this section if any
## of the regexps associated is matching.
##
## Please note that ``section_regexps`` will only classify commits and won't
## make any changes to the contents. So you'll probably want to go check
## ``subject_process`` (or ``body_process``) to do some changes to the subject,
## whenever you are tweaking this variable.
##
section_regexps = [
('New', [
r'^[nN]ew\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$',
]),
('Changes', [
r'^[cC]hg\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$',
]),
('Fixes', [
r'^[fF]ix\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$',
]),
('Other', None ## Match all lines
),
]
## ``body_process`` is a callable
##
## This callable will be given the original body and result will
## be used in the changelog.
##
## Available constructs are:
##
## - any python callable that take one txt argument and return txt argument.
##
## - ReSub(pattern, replacement): will apply regexp substitution.
##
## - Indent(chars=" "): will indent the text with the prefix
## Please remember that template engines gets also to modify the text and
## will usually indent themselves the text if needed.
##
## - Wrap(regexp=r"\n\n"): re-wrap text in separate paragraph to fill 80-Columns
##
## - noop: do nothing
##
## - ucfirst: ensure the first letter is uppercase.
## (usually used in the ``subject_process`` pipeline)
##
## - final_dot: ensure text finishes with a dot
## (usually used in the ``subject_process`` pipeline)
##
## - strip: remove any spaces before or after the content of the string
##
## - SetIfEmpty(msg="No commit message."): will set the text to
## whatever given ``msg`` if the current text is empty.
##
## Additionally, you can `pipe` the provided filters, for instance:
#body_process = Wrap(regexp=r'\n(?=\w+\s*:)') | Indent(chars=" ")
#body_process = Wrap(regexp=r'\n(?=\w+\s*:)')
#body_process = noop
body_process = ReSub(r'((^|\n)[A-Z]\w+(-\w+)*: .*(\n\s+.*)*)+$', r'') | strip
## ``subject_process`` is a callable
##
## This callable will be given the original subject and result will
## be used in the changelog.
##
## Available constructs are those listed in ``body_process`` doc.
subject_process = (strip |
ReSub(r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n@]*)(@[a-z]+\s+)*$', r'\4') |
SetIfEmpty("No commit message.") | ucfirst | final_dot)
## ``tag_filter_regexp`` is a regexp
##
## Tags that will be used for the changelog must match this regexp.
##
tag_filter_regexp = r'^v[0-9]+\.[0-9]+(\.[0-9]+)?$'
## ``unreleased_version_label`` is a string or a callable that outputs a string
##
## This label will be used as the changelog Title of the last set of changes
## between last valid tag and HEAD if any.
unreleased_version_label = "(unreleased)"
## ``output_engine`` is a callable
##
## This will change the output format of the generated changelog file
##
## Available choices are:
##
## - rest_py
##
## Legacy pure python engine, outputs ReSTructured text.
## This is the default.
##
## - mustache(<template_name>)
##
## Template name could be any of the available templates in
## ``templates/mustache/*.tpl``.
## Requires python package ``pystache``.
## Examples:
## - mustache("markdown")
## - mustache("restructuredtext")
##
## - makotemplate(<template_name>)
##
## Template name could be any of the available templates in
## ``templates/mako/*.tpl``.
## Requires python package ``mako``.
## Examples:
## - makotemplate("restructuredtext")
##
# output_engine = rest_py
#output_engine = mustache("restructuredtext")
output_engine = mustache("markdown")
#output_engine = makotemplate("restructuredtext")
## ``include_merge`` is a boolean
##
## This option tells git-log whether to include merge commits in the log.
## The default is to include them.
include_merge = False
## ``log_encoding`` is a string identifier
##
## This option tells gitchangelog what encoding is outputed by ``git log``.
## The default is to be clever about it: it checks ``git config`` for
## ``i18n.logOutputEncoding``, and if not found will default to git's own
## default: ``utf-8``.
log_encoding = 'utf-8'
## ``publish`` is a callable
##
## Sets what ``gitchangelog`` should do with the output generated by
## the output engine. ``publish`` is a callable taking one argument
## that is an interator on lines from the output engine.
##
## Some helper callable are provided:
##
## Available choices are:
##
## - stdout
##
## Outputs directly to standard output
## (This is the default)
##
## - FileInsertAtFirstRegexMatch(file, pattern, idx=lamda m: m.start())
##
## Creates a callable that will parse given file for the given
## regex pattern and will insert the output in the file.
## ``idx`` is a callable that receive the matching object and
## must return a integer index point where to insert the
## the output in the file. Default is to return the position of
## the start of the matched string.
##
## - FileRegexSubst(file, pattern, replace, flags)
##
## Apply a replace inplace in the given file. Your regex pattern must
## take care of everything and might be more complex. Check the README
## for a complete copy-pastable example.
##
# publish = FileInsertIntoFirstRegexMatch(
# "CHANGELOG.rst",
# r'/(?P<rev>[0-9]+\.[0-9]+(\.[0-9]+)?)\s+\([0-9]+-[0-9]{2}-[0-9]{2}\)\n--+\n/',
# idx=lambda m: m.start(1)
# )
#publish = stdout
OUTPUT_FILE = "CHANGES.md"
INSERT_POINT_REGEX = r'''(?isxu)
^
(
\s*\#\s+Changelog\s*(\n|\r\n|\r) ## ``Changelog`` line
)
( ## Match all between changelog and release rev
(
(?!
(?<=(\n|\r)) ## look back for newline
\#\#\s+%(rev)s ## revision
\s+
\([0-9]+-[0-9]{2}-[0-9]{2}\)(\n|\r\n|\r) ## date
)
.
)*
)
(?P<tail>\#\#\s+(?P<rev>%(rev)s))
''' % {'rev': r"v[0-9]+\.[0-9]+(\.[0-9]+)?"}
revs = [
Caret(FileFirstRegexMatch(OUTPUT_FILE, INSERT_POINT_REGEX)),
"HEAD"
]
publish = FileRegexSubst(OUTPUT_FILE, INSERT_POINT_REGEX, r"\1\o\n\g<tail>")
## ``revs`` is a list of callable or a list of string
##
## callable will be called to resolve as strings and allow dynamical
## computation of these. The result will be used as revisions for
## gitchangelog (as if directly stated on the command line). This allows
## to filter exaclty which commits will be read by gitchangelog.
##
## To get a full documentation on the format of these strings, please
## refer to the ``git rev-list`` arguments. There are many examples.
##
## Using callables is especially useful, for instance, if you
## are using gitchangelog to generate incrementally your changelog.
##
## Some helpers are provided, you can use them::
##
## - FileFirstRegexMatch(file, pattern): will return a callable that will
## return the first string match for the given pattern in the given file.
## If you use named sub-patterns in your regex pattern, it'll output only
## the string matching the regex pattern named "rev".
##
## - Caret(rev): will return the rev prefixed by a "^", which is a
## way to remove the given revision and all its ancestor.
##
## Please note that if you provide a rev-list on the command line, it'll
## replace this value (which will then be ignored).
##
## If empty, then ``gitchangelog`` will act as it had to generate a full
## changelog.
##
## The default is to use all commits to make the changelog.
#revs = ["^1.0.3", ]
#revs = [
# Caret(
# FileFirstRegexMatch(
# "CHANGELOG.rst",
# r"(?P<rev>[0-9]+\.[0-9]+(\.[0-9]+)?)\s+\([0-9]+-[0-9]{2}-[0-9]{2}\)\n--+\n")),
# "HEAD"
#]
# revs = []

19
.github/workflows/publish-crate.yml vendored Normal file
View File

@ -0,0 +1,19 @@
name: Publish to crates.io
on:
workflow_call:
inputs:
plan:
required: true
type: string
jobs:
publish:
runs-on: ubuntu-latest
env:
PLAN: ${{ inputs.plan }}
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: cargo publish

View File

@ -1,125 +1,211 @@
# Copyright 2022-2023, axodotdev
# SPDX-License-Identifier: MIT or Apache-2.0
#
# CI that:
#
# * checks for a Git Tag that looks like a release
# * builds artifacts with cargo-dist (archives, installers, hashes)
# * uploads those artifacts to temporary workflow zip
# * on success, uploads the artifacts to a Github Release™
#
# Note that the Github Release™ will be created with a generated
# title/body based on your changelogs.
name: Release
permissions:
contents: write
# This task will run whenever you push a git tag that looks like a version
# like "1.0.0", "v0.1.0-prerelease.1", "my-app/0.1.0", "releases/v1.0.0", etc.
# Various formats will be parsed into a VERSION and an optional PACKAGE_NAME, where
# PACKAGE_NAME must be the name of a Cargo package in your workspace, and VERSION
# must be a Cargo-style SemVer Version (must have at least major.minor.patch).
#
# If PACKAGE_NAME is specified, then the release will be for that
# package (erroring out if it doesn't have the given version or isn't cargo-dist-able).
#
# If PACKAGE_NAME isn't specified, then the release will be for all
# (cargo-dist-able) packages in the workspace with that version (this mode is
# intended for workspaces with only one dist-able package, or with all dist-able
# packages versioned/released in lockstep).
#
# If you push multiple tags at once, separate instances of this workflow will
# spin up, creating an independent Github Release™ for each one. However Github
# will hard limit this to 3 tags per commit, as it will assume more tags is a
# mistake.
#
# If there's a prerelease-style suffix to the version, then the Github Release™
# will be marked as a prerelease.
on:
push:
tags:
- "v*"
name: Create release
- '**[0-9]+.[0-9]+.[0-9]+*'
pull_request:
jobs:
create-release:
name: Create release
# Run 'cargo dist plan' to determine what tasks we need to do
plan:
runs-on: ubuntu-latest
outputs:
upload_url: "${{ steps.create_release.outputs.upload_url }}"
val: ${{ steps.plan.outputs.manifest }}
tag: ${{ !github.event.pull_request && github.ref_name || '' }}
tag-flag: ${{ !github.event.pull_request && format('--tag={0}', github.ref_name) || '' }}
publishing: ${{ !github.event.pull_request }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/checkout@v4
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
draft: true
prerelease: false
submodules: recursive
- name: Install cargo-dist
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.4.3/cargo-dist-installer.sh | sh"
- id: plan
run: |
cargo dist plan ${{ !github.event.pull_request && format('--tag={0}', github.ref_name) || '' }} --output-format=json > dist-manifest.json
echo "cargo dist plan ran successfully"
cat dist-manifest.json
echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
- name: "Upload dist-manifest.json"
uses: actions/upload-artifact@v3
with:
name: artifacts
path: dist-manifest.json
build-linux:
name: Linux binary
needs: create-release
# Build and packages all the platform-specific things
upload-local-artifacts:
# Let the initial task tell us to not run (currently very blunt)
needs: plan
if: ${{ fromJson(needs.plan.outputs.val).releases != null && (needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload') }}
strategy:
fail-fast: false
# Target platforms/runners are computed by cargo-dist in create-release.
# Each member of the matrix has the following arguments:
#
# - runner: the github runner
# - dist-args: cli flags to pass to cargo dist
# - install-dist: expression to run to install cargo-dist on the runner
#
# Typically there will be:
# - 1 "global" task that builds universal installers
# - N "local" tasks that build each platform's binaries and platform-specific installers
matrix: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix }}
runs-on: ${{ matrix.runner }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: swatinem/rust-cache@v2
- name: Install cargo-dist
run: ${{ matrix.install_dist }}
- name: Install dependencies
run: |
${{ matrix.packages_install }}
- name: Build artifacts
run: |
# Actually do builds and make zips and whatnot
cargo dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json
echo "cargo dist ran successfully"
- id: cargo-dist
name: Post-build
# We force bash here just because github makes it really hard to get values up
# to "real" actions without writing to env-vars, and writing to env-vars has
# inconsistent syntax between shell and powershell.
shell: bash
run: |
# Parse out what we just built and upload it to the Github Release™
echo "paths<<EOF" >> "$GITHUB_OUTPUT"
jq --raw-output ".artifacts[]?.path | select( . != null )" dist-manifest.json >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
- name: "Upload artifacts"
uses: actions/upload-artifact@v3
with:
name: artifacts
path: |
${{ steps.cargo-dist.outputs.paths }}
${{ env.BUILD_MANIFEST_NAME }}
# Build and package all the platform-agnostic(ish) things
upload-global-artifacts:
needs: [plan, upload-local-artifacts]
runs-on: "ubuntu-20.04"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install cargo-dist
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.4.3/cargo-dist-installer.sh | sh"
# Get all the local artifacts for the global tasks to use (for e.g. checksums)
- name: Fetch local artifacts
uses: actions/download-artifact@v3
with:
name: artifacts
path: target/distrib/
- id: cargo-dist
shell: bash
run: |
cargo dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json
echo "cargo dist ran successfully"
# Parse out what we just built and upload it to the Github Release™
echo "paths<<EOF" >> "$GITHUB_OUTPUT"
jq --raw-output ".artifacts[]?.path | select( . != null )" dist-manifest.json >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
- name: "Upload artifacts"
uses: actions/upload-artifact@v3
with:
name: artifacts
path: ${{ steps.cargo-dist.outputs.paths }}
should-publish:
needs:
- plan
- upload-local-artifacts
- upload-global-artifacts
if: ${{ needs.plan.outputs.publishing == 'true' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: print tag
run: echo "ok we're publishing!"
- uses: actions-rs/cargo@v1
with:
command: build
args: --release --locked
custom-publish-crate:
needs: [plan, should-publish]
if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }}
uses: ./.github/workflows/publish-crate.yml
with:
plan: ${{ needs.plan.outputs.val }}
secrets: inherit
- run: strip target/release/obsidian-export
- uses: actions/upload-artifact@v2
with:
name: Linux binary
path: target/release/obsidian-export
retention-days: 7
- uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-release.outputs.upload_url }}
asset_path: target/release/obsidian-export
asset_name: obsidian-export_Linux-x86_64
asset_content_type: application/octet-stream
build-windows:
name: Windows binary
needs: create-release
runs-on: windows-latest
# Create a Github Release with all the results once everything is done
publish-release:
needs: [plan, should-publish]
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
- uses: actions/checkout@v4
with:
profile: minimal
toolchain: stable
override: true
- uses: actions-rs/cargo@v1
submodules: recursive
- name: "Download artifacts"
uses: actions/download-artifact@v3
with:
command: build
args: --release --locked
- run: strip target/release/obsidian-export.exe
- uses: actions/upload-artifact@v2
name: artifacts
path: artifacts
- name: Cleanup
run: |
# Remove the granular manifests
rm artifacts/*-dist-manifest.json
- name: Create Release
uses: ncipollo/release-action@v1
with:
name: Windows binary
path: target/release/obsidian-export.exe
retention-days: 7
- uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-release.outputs.upload_url }}
asset_path: target/release/obsidian-export.exe
asset_name: obsidian-export_Windows-x64_64
asset_content_type: application/octet-stream
build-macos:
name: Mac OS binary
needs: create-release
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- uses: actions-rs/cargo@v1
with:
command: build
args: --release --locked
- run: strip target/release/obsidian-export
- uses: actions/upload-artifact@v2
with:
name: MacOS binary
path: target/release/obsidian-export
retention-days: 7
- uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-release.outputs.upload_url }}
asset_path: target/release/obsidian-export
asset_name: obsidian-export_MacOS-x86_64
asset_content_type: application/octet-stream
tag: ${{ needs.plan.outputs.tag }}
name: ${{ fromJson(needs.plan.outputs.val).announcement_title }}
body: ${{ fromJson(needs.plan.outputs.val).announcement_github_body }}
prerelease: ${{ fromJson(needs.plan.outputs.val).announcement_is_prerelease }}
artifacts: "artifacts/*"

View File

@ -1,80 +1,184 @@
name: CI tests
on: [push, pull_request]
name: CI tests
env:
SCCACHE_GHA_ENABLED: "true"
RUSTC_WRAPPER: "sccache"
jobs:
linting:
build:
name: Build project
runs-on: ubuntu-latest
outputs:
rustc_cache_key: ${{ steps.setup_rust.outputs.cachekey }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
id: setup_rust
with:
components: "rustfmt, clippy"
- uses: actions/cache@v3
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: "cargo-base-${{ steps.setup_rust.outputs.cachekey }}-${{ hashFiles('**/Cargo.lock') }}"
restore-keys: |
cargo-base-${{ env.RUSTC_CACHEKEY }}
- name: Run sccache-cache
uses: mozilla-actions/sccache-action@v0.0.3
- run: cargo build --locked --all-targets
lint:
name: Run lints
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
- uses: actions/checkout@v4
- uses: actions/cache@v3
with:
profile: minimal
toolchain: stable
override: true
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: "cargo-lint-${{ needs.build.outputs.rustc_cache_key }}-${{ hashFiles('**/Cargo.lock') }}"
restore-keys: |
cargo-lint-${{ env.RUSTC_CACHEKEY }}
cargo-base-${{ env.RUSTC_CACHEKEY }}
fail-on-cache-miss: true
- name: Run sccache-cache
uses: mozilla-actions/sccache-action@v0.0.3
- run: rustup component add rustfmt
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- run: cargo fmt --all -- --check
- run: cargo check
- run: cargo clippy -- -D warnings
- uses: actions-rs/cargo@v1
with:
command: check
- run: rustup component add clippy
- uses: actions-rs/cargo@v1
pre-commit:
name: Run pre-commit
runs-on: ubuntu-latest
needs: build
env:
# These hooks are expensive and already run as dedicated jobs above
SKIP: "tests,clippy"
steps:
- uses: actions/checkout@v4
- uses: actions/cache@v3
with:
command: clippy
args: -- -D warnings
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: "cargo-lint-${{ needs.build.outputs.rustc_cache_key }}-${{ hashFiles('**/Cargo.lock') }}"
restore-keys: |
cargo-lint-${{ env.RUSTC_CACHEKEY }}
cargo-base-${{ env.RUSTC_CACHEKEY }}
fail-on-cache-miss: true
- name: Run sccache-cache
uses: mozilla-actions/sccache-action@v0.0.3
- uses: actions/setup-python@v5
- name: set PYVERSION
run: echo "PYVERSION=$(python --version | tr ' ' '-')" >> $GITHUB_ENV
- uses: actions/cache@v3
with:
path: ~/.cache/pre-commit
# Changes to pre-commit-config.yaml may require the installation of
# new binaries/scripts. When a cache hit occurs, changes to the cache
# aren't persisted at the end of the run, so making the key dependent
# on the configuration file ensures we always persist a complete cache.
key: pre-commit-${{ env.PYVERSION }}-${{ hashFiles('.pre-commit-config.yaml') }}
- run: pip install pre-commit
- run: pre-commit run --all --color=always --show-diff-on-failure
test-linux:
name: Test on Linux
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
- uses: actions/checkout@v4
- uses: actions/cache@v3
with:
profile: minimal
toolchain: stable
override: true
- uses: actions-rs/cargo@v1
with:
command: test
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: "cargo-test-${{ needs.build.outputs.rustc_cache_key }}-${{ hashFiles('**/Cargo.lock') }}"
restore-keys: |
cargo-test-${{ env.RUSTC_CACHEKEY }}
cargo-base-${{ env.RUSTC_CACHEKEY }}
fail-on-cache-miss: true
- name: Run sccache-cache
uses: mozilla-actions/sccache-action@v0.0.3
- run: cargo test
test-windows:
name: Test on Windows
runs-on: windows-latest
needs: build
steps:
- run: git config --system core.autocrlf false && git config --system core.eol lf
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
id: setup_rust
- uses: actions/cache@v3
with:
profile: minimal
toolchain: stable
override: true
- uses: actions-rs/cargo@v1
with:
command: test
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: "cargo-windows-${{ needs.build.outputs.rustc_cache_key }}-${{ hashFiles('**/Cargo.lock') }}"
restore-keys: |
cargo-windows-${{ env.RUSTC_CACHEKEY }}
cargo-base-${{ env.RUSTC_CACHEKEY }}
fail-on-cache-miss: true
- name: Run sccache-cache
uses: mozilla-actions/sccache-action@v0.0.3
- run: cargo test
coverage:
name: Code coverage
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
- uses: actions/checkout@v4
- uses: actions/cache@v3
with:
profile: minimal
toolchain: stable
override: true
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: "cargo-coverage-${{ needs.build.outputs.rustc_cache_key }}-${{ hashFiles('**/Cargo.lock') }}"
restore-keys: |
cargo-coverage-${{ env.RUSTC_CACHEKEY }}
cargo-base-${{ env.RUSTC_CACHEKEY }}
fail-on-cache-miss: true
- name: Run sccache-cache
uses: mozilla-actions/sccache-action@v0.0.3
- uses: actions-rs/tarpaulin@v0.1
with:
version: "latest"
# Constrained by https://github.com/actions-rs/tarpaulin/pull/23
version: "0.22.0"
args: "--ignore-tests"
out-type: "Html"
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v3
with:
name: tarpaulin-report
path: tarpaulin-report.html

View File

@ -2,18 +2,34 @@
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: 9136088a246768144165fcc3ecc3d31bb686920a # frozen: v3.3.0
rev: 38b88246ccc552bffaaf54259d064beeee434539 # frozen: v4.0.1
hooks:
- id: check-case-conflict
- id: check-symlinks
- id: check-yaml
- repo: https://github.com/doublify/pre-commit-rust
rev: eeee35a89e69d5772bdee97db1a6a898467b686e # frozen: v1.0
hooks:
- id: fmt
- id: cargo-check
- id: clippy
args: ["--", "-D", "warnings"]
- id: end-of-file-fixer
- id: mixed-line-ending
- id: trailing-whitespace
exclude: '^(README.md|tests/testdata/expected/.*)$'
- repo: local
hooks:
- id: rustfmt
name: Check formatting
entry: cargo fmt --
language: system
files: \.rs$
- id: tests
name: Run tests
entry: cargo test
language: system
files: \.rs$
pass_filenames: false
- id: clippy
name: Check clippy lints
entry: cargo clippy -- -D warnings
language: system
files: \.rs$
pass_filenames: false
- id: README
name: Render README.md
entry: docs/generate.sh

View File

@ -1,5 +1,375 @@
# Changelog
## v23.12.0 (2023-12-03)
### New
- Implement frontmatter based filtering (#163) [Martin Heuschober]
This allows limiting the notes that will be exported using `--skip-tags` and `--only-tags`:
- using `--skip-tags foo --skip-tags bar` will skip any files that have the tags `foo` or `bar` in their frontmatter
- using `--only-tags foo --only-tags bar` will skip any files that **don't** have the tags `foo` or `bar` in their frontmatter
### Fixes
- Trim filenames while resolving wikilinks [Nick Groenen]
Obsidian trims the filename part in a [[WikiLink|label]], so each of
these are equivalent:
```
[[wikilink]]
[[ wikilink ]]
[[ wikilink |wikilink]]
```
Obsidian-export now behaves similarly.
Fixes #188
### Other
- Relicense to BSD-2-Clause Plus Patent License [Nick Groenen]
This license achieves everything that dual-licensing under MIT + Apache
aims for, but without the weirdness of being under two licenses.
Having checked external contributions, I feel pretty confident that I
can unilaterally make this license change, as people have only
contributed a handful of one-line changes of no significance towards
copyrighted work up to this point.
- Add a lifetime annotation to the Postprocesor type [Robert Sesek]
This lets the compiler reason about the lifetimes of objects used by the
postprocessor, if the callback captures variables.
See zoni/obsidian-export#175
- Use cargo-dist to create release artifacts [Nick Groenen]
This will create binaries for more platforms (including ARM builds for
MacOS) and installer scripts in addition to just the binaries themselves.
## v22.11.0 (2022-11-19)
### New
* Apply unicode normalization while resolving notes. [Nick Groenen]
The unicode standard allows for certain (visually) identical characters to
be represented in different ways.
For example the character ä may be represented as a single combined
codepoint "Latin Small Letter A with Diaeresis" (U+00E4) or by the
combination of "Latin Small Letter A" (U+0061) followed by "Combining
Diaeresis" (U+0308).
When encoded with UTF-8, these are represented as respectively the two
bytes 0xC3 0xA4, and the three bytes 0x61 0xCC 0x88.
A user linking to notes with these characters in their titles would
expect these two variants to link to the same file, given they are
visually identical and have the exact same semantic meaning.
The unicode standard defines a method to deconstruct and normalize these
forms, so that a byte comparison on the normalized forms of these
variants ends up comparing the same thing. This is called Unicode
Normalization, defined in Unicode® Standard Annex #15
(http://www.unicode.org/reports/tr15/).
The W3C Working Group has written an excellent explanation of the
problems regarding string matching, and how unicode normalization helps
with this process: https://www.w3.org/TR/charmod-norm/#unicodeNormalization
With this change, obsidian-export will perform unicode normalization
(specifically the C (or NFC) normalization form) on all note titles
while looking up link references, ensuring visually identical links are
treated as being similar, even if they were encoded as different
variants.
A special thanks to Hans Raaf (@oderwat) for reporting and helping track
down this issue.
### Breaking Changes (affects library API only)
* Pass context and events as mutable references to postprocessors. [Nick Groenen]
Instead of passing clones of context and the markdown tree to
postprocessors, pass them a mutable reference which may be modified
in-place.
This is a breaking change to the postprocessor implementation, changing
both the input arguments as well as the return value:
```diff
- dyn Fn(Context, MarkdownEvents) -> (Context, MarkdownEvents, PostprocessorResult) + Send + Sync;
+ dyn Fn(&mut Context, &mut MarkdownEvents) -> PostprocessorResult + Send + Sync;
```
With this change the postprocessor API becomes a little more ergonomic
to use however, especially making the intent around return statements more clear.
### Other
* Use path.Join to construct hugo links (#92) [Chang-Yen Tseng]
Use path.Join so that it will render correctly on Windows
(path.Join will convert Windows backslash to forward slash)
* Bump crossbeam-utils from 0.8.5 to 0.8.12. [dependabot[bot]]
Bumps [crossbeam-utils](https://github.com/crossbeam-rs/crossbeam) from 0.8.5 to 0.8.12.
- [Release notes](https://github.com/crossbeam-rs/crossbeam/releases)
- [Changelog](https://github.com/crossbeam-rs/crossbeam/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crossbeam-rs/crossbeam/compare/crossbeam-utils-0.8.5...crossbeam-utils-0.8.12)
---
updated-dependencies:
- dependency-name: crossbeam-utils
dependency-type: indirect
...
* Bump regex from 1.6.0 to 1.7.0. [dependabot[bot]]
Bumps [regex](https://github.com/rust-lang/regex) from 1.6.0 to 1.7.0.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.6.0...1.7.0)
---
updated-dependencies:
- dependency-name: regex
dependency-type: direct:production
update-type: version-update:semver-minor
...
* Bump actions/checkout from 2 to 3. [dependabot[bot]]
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)
---
updated-dependencies:
- dependency-name: actions/checkout
dependency-type: direct:production
update-type: version-update:semver-major
...
* Bump actions/upload-artifact from 2 to 3. [dependabot[bot]]
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 2 to 3.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v2...v3)
---
updated-dependencies:
- dependency-name: actions/upload-artifact
dependency-type: direct:production
update-type: version-update:semver-major
...
* Bump thread_local from 1.1.3 to 1.1.4. [dependabot[bot]]
Bumps [thread_local](https://github.com/Amanieu/thread_local-rs) from 1.1.3 to 1.1.4.
- [Release notes](https://github.com/Amanieu/thread_local-rs/releases)
- [Commits](https://github.com/Amanieu/thread_local-rs/compare/v1.1.3...1.1.4)
---
updated-dependencies:
- dependency-name: thread_local
dependency-type: indirect
...
* Remove needless borrows. [Nick Groenen]
* Upgrade snafu to 0.7.x. [Nick Groenen]
* Upgrade pulldown-cmark-to-cmark to 10.0.x. [Nick Groenen]
* Upgrade serde_yaml to 0.9.x. [Nick Groenen]
* Upgrade minor dependencies. [Nick Groenen]
* Fix new clippy lints. [Nick Groenen]
* Add a contributor guide. [Nick Groenen]
* Simplify pre-commit setup. [Nick Groenen]
No need to depend on a third-party hook repository when each of these
checks is easily defined and run through system commands.
This also allows us to actually run tests, which is current unsupported
(https://github.com/doublify/pre-commit-rust/pull/19)
* Bump tempfile from 3.2.0 to 3.3.0. [dependabot[bot]]
Bumps [tempfile](https://github.com/Stebalien/tempfile) from 3.2.0 to 3.3.0.
- [Release notes](https://github.com/Stebalien/tempfile/releases)
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/NEWS)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.2.0...v3.3.0)
---
updated-dependencies:
- dependency-name: tempfile
dependency-type: direct:production
update-type: version-update:semver-minor
...
## v22.1.0 (2022-01-02)
Happy new year! On this second day of 2022 comes a fresh release with one
notable new feature.
### New
* Support Obsidian's "Strict line breaks" setting. [Nick Groenen]
This change introduces a new `--hard-linebreaks` CLI argument. When
used, this converts soft line breaks to hard line breaks, mimicking
Obsidian's "Strict line breaks" setting.
> Implementation detail: I considered naming this flag
> `--strict-line-breaks` to be consistent with Obsidian itself, however I
> feel the name is somewhat misleading and ill-chosen.
### Other
* Give release binaries file extensions. [Nick Groenen]
This may make it more clear to users that these are precompiled, binary
files. This is especially relevant on Windows, where the convention is
that executable files have a `.exe` extension, as seen in #49.
* Upgrade dependencies. [Nick Groenen]
This commit upgrades all dependencies to their current latest versions. Most
notably, this includes upgrades to the following most critical libraries:
pulldown-cmark v0.8.0 -> v0.9.0
pulldown-cmark-to-cmark v7.1.1 -> v9.0.0
In total, these dependencies were upgraded:
bstr v0.2.16 -> v0.2.17
ignore v0.4.17 -> v0.4.18
libc v0.2.101 -> v0.2.112
memoffset v0.6.4 -> v0.6.5
num_cpus v1.13.0 -> v1.13.1
once_cell v1.8.0 -> v1.9.0
ppv-lite86 v0.2.10 -> v0.2.16
proc-macro2 v1.0.29 -> v1.0.36
pulldown-cmark v0.8.0 -> v0.9.0
pulldown-cmark-to-cmark v7.1.1 -> v9.0.0
quote v1.0.9 -> v1.0.14
rayon v1.5.0 -> v1.5.1
regex v1.5.3 -> v1.5.4
serde v1.0.130 -> v1.0.132
syn v1.0.75 -> v1.0.84
unicode-width v0.1.8 -> v0.1.9
version_check v0.9.3 -> v0.9.4
* Bump serde_yaml from 0.8.21 to 0.8.23 (#52) [dependabot[bot]]
Bumps [serde_yaml](https://github.com/dtolnay/serde-yaml) from 0.8.21 to 0.8.23.
- [Release notes](https://github.com/dtolnay/serde-yaml/releases)
- [Commits](https://github.com/dtolnay/serde-yaml/compare/0.8.21...0.8.23)
---
updated-dependencies:
- dependency-name: serde_yaml
dependency-type: direct:production
update-type: version-update:semver-patch
...
* Bump pulldown-cmark-to-cmark from 7.1.0 to 7.1.1 (#51) [dependabot[bot]]
Bumps [pulldown-cmark-to-cmark](https://github.com/Byron/pulldown-cmark-to-cmark) from 7.1.0 to 7.1.1.
- [Release notes](https://github.com/Byron/pulldown-cmark-to-cmark/releases)
- [Changelog](https://github.com/Byron/pulldown-cmark-to-cmark/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Byron/pulldown-cmark-to-cmark/compare/v7.1.0...v7.1.1)
---
updated-dependencies:
- dependency-name: pulldown-cmark-to-cmark
dependency-type: direct:production
update-type: version-update:semver-patch
...
* Bump pulldown-cmark-to-cmark from 7.0.0 to 7.1.0 (#48) [dependabot[bot]]
Bumps [pulldown-cmark-to-cmark](https://github.com/Byron/pulldown-cmark-to-cmark) from 7.0.0 to 7.1.0.
- [Release notes](https://github.com/Byron/pulldown-cmark-to-cmark/releases)
- [Changelog](https://github.com/Byron/pulldown-cmark-to-cmark/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Byron/pulldown-cmark-to-cmark/compare/v7.0.0...v7.1.0)
---
updated-dependencies:
- dependency-name: pulldown-cmark-to-cmark
dependency-type: direct:production
update-type: version-update:semver-minor
...
* Bump pulldown-cmark-to-cmark from 6.0.4 to 7.0.0 (#47) [dependabot[bot]]
Bumps [pulldown-cmark-to-cmark](https://github.com/Byron/pulldown-cmark-to-cmark) from 6.0.4 to 7.0.0.
- [Release notes](https://github.com/Byron/pulldown-cmark-to-cmark/releases)
- [Changelog](https://github.com/Byron/pulldown-cmark-to-cmark/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Byron/pulldown-cmark-to-cmark/compare/v6.0.4...v7.0.0)
---
updated-dependencies:
- dependency-name: pulldown-cmark-to-cmark
dependency-type: direct:production
update-type: version-update:semver-major
...
* Bump pathdiff from 0.2.0 to 0.2.1 (#46) [dependabot[bot]]
Bumps [pathdiff](https://github.com/Manishearth/pathdiff) from 0.2.0 to 0.2.1.
- [Release notes](https://github.com/Manishearth/pathdiff/releases)
- [Commits](https://github.com/Manishearth/pathdiff/commits)
---
updated-dependencies:
- dependency-name: pathdiff
dependency-type: direct:production
update-type: version-update:semver-patch
...
* Bump pulldown-cmark-to-cmark from 6.0.3 to 6.0.4 (#44) [dependabot[bot]]
Bumps [pulldown-cmark-to-cmark](https://github.com/Byron/pulldown-cmark-to-cmark) from 6.0.3 to 6.0.4.
- [Release notes](https://github.com/Byron/pulldown-cmark-to-cmark/releases)
- [Changelog](https://github.com/Byron/pulldown-cmark-to-cmark/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Byron/pulldown-cmark-to-cmark/compare/v6.0.3...v6.0.4)
---
updated-dependencies:
- dependency-name: pulldown-cmark-to-cmark
dependency-type: direct:production
update-type: version-update:semver-patch
...
* Bump pretty_assertions from 0.7.2 to 1.0.0 (#45) [dependabot[bot]]
Bumps [pretty_assertions](https://github.com/colin-kiegel/rust-pretty-assertions) from 0.7.2 to 1.0.0.
- [Release notes](https://github.com/colin-kiegel/rust-pretty-assertions/releases)
- [Changelog](https://github.com/colin-kiegel/rust-pretty-assertions/blob/main/CHANGELOG.md)
- [Commits](https://github.com/colin-kiegel/rust-pretty-assertions/compare/v0.7.2...v1.0.0)
---
updated-dependencies:
- dependency-name: pretty_assertions
dependency-type: direct:production
update-type: version-update:semver-major
...
## v21.9.1 (2021-09-24)
### Changes

75
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,75 @@
# Contributing to Obsidian Export
Hi there!
Thank you so much for wanting to contribute to this project.
I greatly appreciate any efforts people like you put into making obsidian-export better!
Managing an open-source project can take a lot of time and effort however.
As this is a passion project which I maintain alongside my regular daytime job, I need to take some measures to safeguard my mental health and the enjoyment of this project.
This document aims to provide guidance which makes contributions easier by:
1. Defining the expectations I have of submissions to the codebase and the pull request process.
2. Helping you get set up for development on the code.
3. Providing pointers to some areas of the codebase, as well as some design considerations to take into account when making changes.
## Working with Rust
Obsidian-export is written in [Rust](https://www.rust-lang.org/), which is not the easiest of languages to master.
If you'd like to contribute but you don't know Rust, check out [Learn Rust](https://www.rust-lang.org/learn) for some suggestions of how to get started with the language.
In general, I will do my best to support you and help you out, but understand my time for mentoring is highly limited.
To work on the codebase, you'll also need the Rust toolchain, including cargo, rustfmt and clippy.
The easiest way is to [install Rust using rustup](https://www.rust-lang.org/tools/install), which lets you install rustfmt and clippy using `rustup component add rustfmt` and `rustup component add clippy` respectively.
## Design principles
My intention is to keep the core of `obsidian-export` as limited and small as possible, avoiding changes to the core [`Exporter`](https://docs.rs/obsidian-export/latest/obsidian_export/struct.Exporter.html) struct or any of its methods whenever possible.
This improves long-term maintainability and makes investigation of bugs simpler.
To keep the core of obsidian-export small while still supporting a wide range of use-cases, additional functionality should be pushed down into [postprocessors](https://docs.rs/obsidian-export/latest/obsidian_export/type.Postprocessor.html) as much as possible.
You can see some examples of this in:
- [Support Obsidian's "Strict line breaks" setting (#57)](https://github.com/zoni/obsidian-export/pull/57)
- [Frontmatter based filtering (#67)](https://github.com/zoni/obsidian-export/pull/67)
## 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.
> **💡 Tip: install pre-commit hooks**
>
> This codebase is set up with the [pre-commit framework](https://pre-commit.com/) to automatically run the appropriate checks locally whenever you commit.
> Assuming you [have pre-commit installed](https://pre-commit.com/#install), all you need to do is run `pre-commit install` once to get this set up.
Following my advice on [creating high-quality commits](https://nick.groenen.me/notes/high-quality-commits/) will make it easier for me to review changes.
I don't insist on this, but pull requests which fail to adhere to these conventions are at risk of being squashed and having their commit messages rewritten when they are accepted.
## Tests
In order to have confidence that your changes work as intended, as well as to avoid regressions when making changes in the future, I would like to see code accompanied by test cases.
At the moment, the test framework primary relies on high-level integration tests, all of which are defined in the [tests](tests/) directory.
These rely on comparing Markdown notes [before](tests/testdata/input) and [after](tests/testdata/expected) running an export.
By studying some of the existing tests, you should be able to copy and adapt these for your own changes.
For an example of doing low-level unit tests, you can look at the end of [frontmatter.rs](src/frontmatter.rs).
## Documentation
I place a lot of value on good documentation and would encourage you to include updates to the docs with your changes.
Changes or additions to public methods and attributes **must** come with proper documentation for a PR to be accepted.
Advice on writing Rust documentation can be found in:
- [The rustdoc book: How to write documentation](https://doc.rust-lang.org/rustdoc/how-to-write-documentation.html)
- [Rust by example: Documentation](https://doc.rust-lang.org/rust-by-example/meta/doc.html)
Updates to the user guide/README instructions are also preferred, but optional.
If you don't feel comfortable writing user documentation, I will be happy to guide you or do it for you.
> **⚠ Warning**
>
> If you update the README file, take note that you must edit the fragments in the [docs](docs/) directory as opposed to the README in the root of the repository, which is auto-generated.

804
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,9 @@
[package]
name = "obsidian-export"
version = "21.9.1"
version = "23.12.0"
authors = ["Nick Groenen <nick@groenen.me>"]
edition = "2018"
license = "MIT OR Apache-2.0"
license = "BSD-2-Clause-Patent"
readme = "README.md"
repository = "https://github.com/zoni/obsidian-export"
documentation = "https://docs.rs/obsidian-export"
@ -25,22 +25,54 @@ doc = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
eyre = "0.6.5"
gumdrop = "0.8.0"
ignore = "0.4.17"
eyre = "0.6.9"
gumdrop = "0.8.1"
ignore = "0.4.21"
lazy_static = "1.4.0"
matter = "0.1.0-alpha4"
pathdiff = "0.2.0"
percent-encoding = "2.1.0"
pulldown-cmark = "0.8.0"
pulldown-cmark-to-cmark = "6.0.3"
rayon = "1.5.0"
regex = "1.5.3"
serde_yaml = "0.8.21"
slug = "0.1.4"
snafu = "0.6.10"
pathdiff = "0.2.1"
percent-encoding = "2.3.1"
pulldown-cmark = "0.9.3"
pulldown-cmark-to-cmark = "11.0.2"
rayon = "1.8.0"
regex = "1.10.2"
serde_yaml = "0.9.27"
slug = "0.1.5"
snafu = "0.7.5"
unicode-normalization = "0.1.22"
[dev-dependencies]
pretty_assertions = "0.7.2"
tempfile = "3.2.0"
walkdir = "2.3.2"
pretty_assertions = "1.4.0"
rstest = "0.18.2"
tempfile = "3.8.1"
walkdir = "2.4.0"
# The profile that 'cargo dist' will build with
[profile.dist]
inherits = "release"
lto = "thin"
# Config for 'cargo dist'
[workspace.metadata.dist]
# The preferred cargo-dist version to use in CI (Cargo.toml SemVer syntax)
cargo-dist-version = "0.4.3"
# CI backends to support
ci = ["github"]
# The installers to generate for each app
installers = ["shell", "powershell"]
# Target platforms to build apps for (Rust target-triple syntax)
targets = [
#"aarch64-unknown-linux-gnu", # Not yet supported (2023-12-03)
"x86_64-unknown-linux-gnu",
#"x86_64-unknown-linux-musl",
"aarch64-apple-darwin",
"x86_64-apple-darwin",
# "aarch64-pc-windows-msvc",, # Not yet supported (2023-12-03)
"x86_64-pc-windows-msvc",
]
unix-archive = ".tar.xz"
windows-archive = ".zip"
# Publish jobs to run in CI
pr-run-mode = "plan"
# Publish jobs to run in CI
publish-jobs = ["./publish-crate"]

45
LICENSE Normal file
View File

@ -0,0 +1,45 @@
Copyright (c) Nick Groenen <nick@groenen.me>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
Subject to the terms and conditions of this license, each copyright holder and
contributor hereby grants to those receiving rights under this license a
perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except for failure to satisfy the conditions of this license) patent license
to make, have made, use, offer to sell, sell, import, and otherwise transfer
this software, where such license applies only to those patent claims, already
acquired or hereafter acquired, licensable by such copyright holder or
contributor that are necessarily infringed by:
(a) their Contribution(s) (the licensed copyrights of copyright holders and
non-copyrightable additions of contributors, in source or binary form)
alone; or
(b) combination of their Contribution(s) with the work of authorship to
which such Contribution(s) was added by such copyright holder or
contributor, if, at the time the Contribution is added, such addition
causes such combination to be necessarily infringed. The patent license
shall not apply to any other combinations which include the Contribution.
Except as expressly stated above, no rights or licenses from any copyright
holder or contributor is granted under this license, whether expressly, by
implication, estoppel or otherwise.
DISCLAIMER
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,25 +0,0 @@
Copyright (c) 2020 Nick Groenen
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

487
README.md
View File

@ -6,20 +6,19 @@ WARNING:
the docs directory.
Instead of editing README.md, edit the corresponding Markdown files in the
docs directory and run generate.sh.
docs directory and run generate.sh.
To add new sections, create new files under docs and add these to _combined.md
-->
# Obsidian Export
*Obsidian Export is a CLI program and a Rust library to export an [Obsidian](https://obsidian.md/) vault to regular Markdown.*
*Obsidian Export is a CLI program and a Rust library to export an [Obsidian] vault to regular Markdown.*
* Recursively export Obsidian Markdown files to [CommonMark](https://commonmark.org/).
* Recursively export Obsidian Markdown files to [CommonMark].
* Supports `[[note]]`-style references as well as `![[note]]` file includes.
* Support for [gitignore](https://git-scm.com/docs/gitignore)-style exclude patterns (default: `.export-ignore`).
* Support for [gitignore]-style exclude patterns (default: `.export-ignore`).
* Automatically excludes files that are ignored by Git when the vault is located in a Git repository.
* Runs on all major platforms: Windows, Mac, Linux, BSDs.
@ -31,21 +30,20 @@ It supports most but not all of Obsidian's Markdown flavor.
## Pre-built binaries
Binary releases for x86-64 processors are provided for Windows, Linux and Mac operating systems on a best-effort basis.
They are built with GitHub runners as part of the release workflow defined in `.github/workflows/release.yml`.
Pre-compiled binaries for all major platforms are available at <https://github.com/zoni/obsidian-export/releases>
The resulting binaries can be downloaded from [https://github.com/zoni/obsidian-export/releases](https://github.com/zoni/obsidian-export/releases)
In addition to the installation scripts provided, these releases are also suitable for [installation with cargo-binstall](https://github.com/cargo-bins/cargo-binstall#readme).
## Building from source
When binary releases are unavailable for your platform, or you do not trust the pre-built binaries, then *obsidian-export* can be compiled from source with relatively little effort.
This is done through [Cargo](https://doc.rust-lang.org/cargo/), the official package manager for Rust, with the following steps:
This is done through [Cargo], the official package manager for Rust, with the following steps:
1. Install the Rust toolchain from [https://www.rust-lang.org/tools/install](https://www.rust-lang.org/tools/install)
1. Install the Rust toolchain from <https://www.rust-lang.org/tools/install>
1. Run: `cargo install obsidian-export`
>
> It is expected that you successfully configured the PATH variable correctly while installing the Rust toolchain, as described under *"Configuring the PATH environment variable"* on [https://www.rust-lang.org/tools/install](https://www.rust-lang.org/tools/install).
> It is expected that you successfully configured the PATH variable correctly while installing the Rust toolchain, as described under *"Configuring the PATH environment variable"* on <https://www.rust-lang.org/tools/install>.
## Upgrading from earlier versions
@ -59,7 +57,7 @@ If you built from source, upgrade by running `cargo install obsidian-export` aga
The main interface of *obsidian-export* is the `obsidian-export` CLI command.
As a text interface, this must be run from a terminal or Windows PowerShell.
It is assumed that you have basic familiarity with command-line interfaces and that you set up your `PATH` correctly if you installed with `cargo`.
It is assumed that you have basic familiarity with command-line interfaces and that you set up your `PATH` correctly if you installed with `cargo`.
Running `obsidian-export --version` should print a version number rather than giving some kind of error.
>
@ -102,7 +100,7 @@ Using the `--start-at` argument, you can export just a subset of your vault.
Given the following vault structure:
````
my-obsidian-vault
my-obsidian-vault
├── Notes/
├── Books/
└── People/
@ -139,9 +137,14 @@ 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 matching 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`)
* using `--skip-tags foo --skip-tags bar` will skip any files that have the tags `foo` or `bar` in their frontmatter
* using `--only-tags foo --only-tags bar` will skip any files that **don't** have the tags `foo` or `bar` in their frontmatter
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).
@ -149,7 +152,7 @@ Embeds of ignored notes will be skipped entirely.
### Ignorefile syntax
The syntax for `.export-ignore` files is identical to that of [gitignore](https://git-scm.com/docs/gitignore) files.
The syntax for `.export-ignore` files is identical to that of [gitignore] files.
Here's an example:
````
@ -163,7 +166,7 @@ test
!special.pdf
````
For more comprehensive documentation and examples, see the [gitignore](https://git-scm.com/docs/gitignore) manpage.
For more comprehensive documentation and examples, see the [gitignore] manpage.
## Recursive embeds
@ -177,12 +180,12 @@ Using this mode, if a note is encountered for a second time while processing the
## Relative links with Hugo
The [Hugo](https://gohugo.io) static site generator [does not support relative links to files](https://notes.nick.groenen.me/notes/relative-linking-in-hugo/).
Instead, it expects you to link to other pages using the [`ref` and `relref` shortcodes](https://gohugo.io/content-management/cross-references/).
The [Hugo] static site generator [does not support relative links to files](https://notes.nick.groenen.me/notes/relative-linking-in-hugo/).
Instead, it expects you to link to other pages using the [`ref` and `relref` shortcodes].
As a result of this, notes that have been exported from Obsidian using obsidian-export do not work out of the box because Hugo doesn't resolve these links correctly.
[Markdown Render Hooks](https://gohugo.io/getting-started/configuration-markup#markdown-render-hooks) (only supported using the default `goldmark` renderer) allow you to work around this issue however, making exported notes work with Hugo after a bit of one-time setup work.
[Markdown Render Hooks] (only supported using the default `goldmark` renderer) allow you to work around this issue however, making exported notes work with Hugo after a bit of one-time setup work.
Create the file `layouts/_default/_markup/render-link.html` with the following contents:
@ -244,434 +247,30 @@ All of the functionality exposed by the `obsidian-export` CLI command is also ac
To get started, visit the library documentation on [obsidian_export](https://docs.rs/obsidian-export/latest/obsidian_export/) and [obsidian_export::Exporter](https://docs.rs/obsidian-export/latest/obsidian_export/struct.Exporter.html).
# Contributing
I will happily accept bug fixes as well as enhancements, as long as they align with the overall scope and vision of the project.
Please see [CONTRIBUTING](CONTRIBUTING.md) for more information.
# License
Obsidian-export is dual-licensed under the [Apache 2.0](https://github.com/zoni/obsidian-export/blob/master/LICENSE-APACHE) and the [MIT](https://github.com/zoni/obsidian-export/blob/master/LICENSE-MIT) licenses.
Obsidian-export is open-source software released under the [BSD-2-Clause Plus Patent License].
This license is designed to provide: a) a simple permissive license; b) that is compatible with the GNU General Public License (GPL), version 2; and c) which also has an express patent grant included.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
Please review the [LICENSE] file for the full text of the license.
# Changelog
## v21.9.1 (2021-09-24)
### Changes
* Treat SVG files as embeddable images. \[Narayan Sainaney\]
This will ensure SVG files are included as an image when using `![[foo.svg]]` syntax, as opposed to only being linked to.
### Other
* Bump pulldown-cmark-to-cmark from 6.0.2 to 6.0.3. \[dependabot\[bot\]\]
Bumps [pulldown-cmark-to-cmark](https://github.com/Byron/pulldown-cmark-to-cmark) from 6.0.2 to 6.0.3.
* [Release notes](https://github.com/Byron/pulldown-cmark-to-cmark/releases)
* [Changelog](https://github.com/Byron/pulldown-cmark-to-cmark/blob/main/CHANGELOG.md)
* [Commits](https://github.com/Byron/pulldown-cmark-to-cmark/compare/v6.0.2...v6.0.3)
---
updated-dependencies:
* dependency-name: pulldown-cmark-to-cmark
dependency-type: direct:production
update-type: version-update:semver-patch
...
* Bump serde_yaml from 0.8.20 to 0.8.21. \[dependabot\[bot\]\]
Bumps [serde_yaml](https://github.com/dtolnay/serde-yaml) from 0.8.20 to 0.8.21.
* [Release notes](https://github.com/dtolnay/serde-yaml/releases)
* [Commits](https://github.com/dtolnay/serde-yaml/compare/0.8.20...0.8.21)
---
updated-dependencies:
* dependency-name: serde_yaml
dependency-type: direct:production
update-type: version-update:semver-patch
...
## v21.9.0 (2021-09-12)
>
> This release switches to a [calendar versioning scheme](https://calver.org/overview.html).
> Details on this decision can be read in [switching obsidian-export to CalVer](https://nick.groenen.me/posts/switching-obsidian-export-to-calver/).
### New
* Support postprocessors running on embedded notes. \[Nick Groenen\]
This introduces support for postprocessors that are run on the result of
a note that is being embedded into another note. This differs from the
existing postprocessors (which remain unchanged) that run once all
embeds have been processed and merged with the final note.
These "embed postprocessors" may be set through the new
`Exporter::add_embed_postprocessor` method.
* Add start_at option to export a partial vault. \[Nick Groenen\]
This introduces a new `--start-at` CLI argument and corresponding
`start_at()` method on the Exporter type that allows exporting of only a
given subdirectory within a vault.
See the updated README file for more details on when and how this may be
used.
### Other
* Don't build docs for the bin target. \[Nick Groenen\]
The library contains documentation covering both CLI and library usage,
there's no separate documentation for just the binary target.
* Move postprocessor tests into their own file for clarity. \[Nick Groenen\]
* Update indirect dependencies. \[Nick Groenen\]
* Bump serde_yaml from 0.8.19 to 0.8.20. \[dependabot\[bot\]\]
Bumps [serde_yaml](https://github.com/dtolnay/serde-yaml) from 0.8.19 to 0.8.20.
* [Release notes](https://github.com/dtolnay/serde-yaml/releases)
* [Commits](https://github.com/dtolnay/serde-yaml/compare/0.8.19...0.8.20)
---
updated-dependencies:
* dependency-name: serde_yaml
dependency-type: direct:production
update-type: version-update:semver-patch
...
* Don't borrow references that are immediately dereferenced. \[Nick Groenen\]
This was caught by a recently introduced clippy rule
* Bump serde_yaml from 0.8.17 to 0.8.19. \[dependabot\[bot\]\]
Bumps [serde_yaml](https://github.com/dtolnay/serde-yaml) from 0.8.17 to 0.8.19.
* [Release notes](https://github.com/dtolnay/serde-yaml/releases)
* [Commits](https://github.com/dtolnay/serde-yaml/compare/0.8.17...0.8.19)
---
updated-dependencies:
* dependency-name: serde_yaml
dependency-type: direct:production
update-type: version-update:semver-patch
...
* Update dependencies. \[Nick Groenen\]
* Fix 4 new clippy lints. \[Nick Groenen\]
* Bump regex from 1.4.6 to 1.5.3. \[dependabot\[bot\]\]
Bumps [regex](https://github.com/rust-lang/regex) from 1.4.6 to 1.5.3.
* [Release notes](https://github.com/rust-lang/regex/releases)
* [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
* [Commits](https://github.com/rust-lang/regex/compare/1.4.6...1.5.3)
* Bump pretty_assertions from 0.7.1 to 0.7.2. \[dependabot\[bot\]\]
Bumps [pretty_assertions](https://github.com/colin-kiegel/rust-pretty-assertions) from 0.7.1 to 0.7.2.
* [Release notes](https://github.com/colin-kiegel/rust-pretty-assertions/releases)
* [Changelog](https://github.com/colin-kiegel/rust-pretty-assertions/blob/main/CHANGELOG.md)
* [Commits](https://github.com/colin-kiegel/rust-pretty-assertions/compare/v0.7.1...v0.7.2)
* Bump regex from 1.4.5 to 1.4.6. \[dependabot\[bot\]\]
Bumps [regex](https://github.com/rust-lang/regex) from 1.4.5 to 1.4.6.
* [Release notes](https://github.com/rust-lang/regex/releases)
* [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
* [Commits](https://github.com/rust-lang/regex/compare/1.4.5...1.4.6)
## v0.7.0 (2021-04-11)
### New
* Postprocessing support. \[Nick Groenen\]
Add support for postprocessing of Markdown prior to writing converted
notes to disk.
Postprocessors may be used when making use of Obsidian export as a Rust
library to do the following:
1. Modify a note's `Context`, for example to change the destination
filename or update its Frontmatter.
1. Change a note's contents by altering `MarkdownEvents`.
1. Prevent later postprocessors from running or cause a note to be
skipped entirely.
Future releases of Obsidian export may come with built-in postprocessors
for users of the command-line tool to use, if general use-cases can be
identified.
For example, a future release might include functionality to make notes
more suitable for the Hugo static site generator. This functionality
would be implemented as a postprocessor that could be enabled through
command-line flags.
### Fixes
* Also percent-encode `?` in filenames. \[Nick Groenen\]
A recent Obsidian update expanded the list of allowed characters in
filenames, which now includes `?` as well. This needs to be
percent-encoded for proper links in static site generators like Hugo.
### Other
* Bump pretty_assertions from 0.6.1 to 0.7.1. \[dependabot\[bot\]\]
Bumps [pretty_assertions](https://github.com/colin-kiegel/rust-pretty-assertions) from 0.6.1 to 0.7.1.
* [Release notes](https://github.com/colin-kiegel/rust-pretty-assertions/releases)
* [Changelog](https://github.com/colin-kiegel/rust-pretty-assertions/blob/main/CHANGELOG.md)
* [Commits](https://github.com/colin-kiegel/rust-pretty-assertions/compare/v0.6.1...v0.7.1)
* Bump walkdir from 2.3.1 to 2.3.2. \[dependabot\[bot\]\]
Bumps [walkdir](https://github.com/BurntSushi/walkdir) from 2.3.1 to 2.3.2.
* [Release notes](https://github.com/BurntSushi/walkdir/releases)
* [Commits](https://github.com/BurntSushi/walkdir/compare/2.3.1...2.3.2)
* Bump regex from 1.4.3 to 1.4.5. \[dependabot\[bot\]\]
Bumps [regex](https://github.com/rust-lang/regex) from 1.4.3 to 1.4.5.
* [Release notes](https://github.com/rust-lang/regex/releases)
* [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
* [Commits](https://github.com/rust-lang/regex/compare/1.4.3...1.4.5)
## v0.6.0 (2021-02-15)
### New
* Add `--version` flag. \[Nick Groenen\]
### Changes
* Don't Box FilterFn in WalkOptions. \[Nick Groenen\]
Previously, `filter_fn` on the `WalkOptions` struct looked like:
````
pub filter_fn: Option<Box<&'static FilterFn>>,
````
This boxing was unneccesary and has been changed to:
````
pub filter_fn: Option<&'static FilterFn>,
````
This will only affect people who use obsidian-export as a library in
other Rust programs, not users of the CLI.
For those library users, they no longer need to supply `FilterFn`
wrapped in a Box.
### Fixes
* Recognize notes beginning with underscores. \[Nick Groenen\]
Notes with an underscore would fail to be recognized within Obsidian
`[[_WikiLinks]]` due to the assumption that the underlying Markdown
parser (pulldown_cmark) would emit the text between `[[` and `]]` as
a single event.
The note parser has now been rewritten to use a more reliable state
machine which correctly recognizes this corner-case (and likely some
others).
* Support self-references. \[Joshua Coles\]
This ensures links to headings within the same note (`[[#Heading]]`)
resolve correctly.
### Other
* Avoid redundant "Release" in GitHub release titles. \[Nick Groenen\]
* Add failing testcase for files with underscores. \[Nick Groenen\]
* Add unit tests for display of ObsidianNoteReference. \[Nick Groenen\]
* Add some unit tests for ObsidianNoteReference::from_str. \[Nick Groenen\]
* Also run tests on pull requests. \[Nick Groenen\]
* Apply clippy suggestions following rust 1.50.0. \[Nick Groenen\]
* Fix infinite recursion bug with references to current file. \[Joshua Coles\]
* Add tests for self-references. \[Joshua Coles\]
Note as there is no support for block references at the moment, the generated link goes nowhere, however it is to a reasonable ID
* Bump tempfile from 3.1.0 to 3.2.0. \[dependabot\[bot\]\]
Bumps [tempfile](https://github.com/Stebalien/tempfile) from 3.1.0 to 3.2.0.
* [Release notes](https://github.com/Stebalien/tempfile/releases)
* [Changelog](https://github.com/Stebalien/tempfile/blob/master/NEWS)
* [Commits](https://github.com/Stebalien/tempfile/commits)
* Bump eyre from 0.6.3 to 0.6.5. \[dependabot\[bot\]\]
Bumps [eyre](https://github.com/yaahc/eyre) from 0.6.3 to 0.6.5.
* [Release notes](https://github.com/yaahc/eyre/releases)
* [Changelog](https://github.com/yaahc/eyre/blob/v0.6.5/CHANGELOG.md)
* [Commits](https://github.com/yaahc/eyre/compare/v0.6.3...v0.6.5)
* Bump regex from 1.4.2 to 1.4.3. \[dependabot\[bot\]\]
Bumps [regex](https://github.com/rust-lang/regex) from 1.4.2 to 1.4.3.
* [Release notes](https://github.com/rust-lang/regex/releases)
* [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
* [Commits](https://github.com/rust-lang/regex/compare/1.4.2...1.4.3)
## v0.5.1 (2021-01-10)
### Fixes
* Find uppercased notes when referenced with lowercase. \[Nick Groenen\]
This commit fixes a bug where, if a note contained uppercase characters
(for example `Note.md`) but was referred to using lowercase
(`[[note]]`), that note would not be found.
## v0.5.0 (2021-01-05)
### New
* Add --no-recursive-embeds to break infinite recursion cycles. \[Nick Groenen\]
It's possible to end up with "recursive embeds" when two notes embed
each other. This happens for example when a `Note A.md` contains
`![[Note B]]` but `Note B.md` also contains `![[Note A]]`.
By default, this will trigger an error and display the chain of notes
which caused the recursion.
Using the new `--no-recursive-embeds`, if a note is encountered for a
second time while processing the original note, rather than embedding it
again a link to the note is inserted instead to break the cycle.
See also: https://github.com/zoni/obsidian-export/issues/1
* Make walk options configurable on CLI. \[Nick Groenen\]
By default hidden files, patterns listed in `.export-ignore` as well as
any files ignored by git are excluded from exports. This behavior has
been made configurable on the CLI using the new flags `--hidden`,
`--ignore-file` and `--no-git`.
* Support links referencing headings. \[Nick Groenen\]
Previously, links referencing a heading (`[[note#heading]]`) would just
link to the file name without including an anchor in the link target.
Now, such references will include an appropriate `#anchor` attribute.
Note that neither the original Markdown specification, nor the more
recent CommonMark standard, specify how anchors should be constructed
for a given heading.
There are also some differences between the various Markdown rendering
implementations.
Obsidian-export uses the [slug](https://crates.io/crates/slug) crate to generate anchors which should
be compatible with most implementations, however your mileage may vary.
(For example, GitHub may leave a trailing `-` on anchors when headings
end with a smiley. The slug library, and thus obsidian-export, will
avoid such dangling dashes).
* Support embeds referencing headings. \[Nick Groenen\]
Previously, partial embeds (`![[note#heading]]`) would always include
the entire file into the source note. Now, such embeds will only include
the contents of the referenced heading (and any subheadings).
Links and embeds of [arbitrary blocks](https://publish.obsidian.md/help/How+to/Link+to+blocks) remains unsupported at this time.
### Changes
* Print warnings to stderr rather than stdout. \[Nick Groenen\]
Warning messages emitted when encountering broken links/references will
now be printed to stderr as opposed to stdout.
### Other
* Include filter_fn field in WalkOptions debug display. \[Nick Groenen\]
## v0.4.0 (2020-12-23)
### Fixes
* Correct relative links within embedded notes. \[Nick Groenen\]
Links within an embedded note would point to other local resources
relative to the filesystem location of the note being embedded.
When a note inside a different directory would embed such a note, these
links would point to invalid locations.
Now these links are calculated relative to the top note, which ensures
these links will point to the right path.
### Other
* Add brief library documentation to all public types and functions. \[Nick Groenen\]
## v0.3.0 (2020-12-21)
### New
* Report file tree when RecursionLimitExceeded is hit. \[Nick Groenen\]
This refactors the Context to maintain a list of all the files which
have been processed so far in a chain of embeds. This information is
then used to print a more helpful error message to users of the CLI when
RecursionLimitExceeded is returned.
### Changes
* Add extra whitespace around multi-line warnings. \[Nick Groenen\]
This makes errors a bit easier to distinguish after a number of warnings
has been printed.
### Other
* Setup gitchangelog. \[Nick Groenen\]
This adds a changelog (CHANGES.md) which is automatically generated with
[gitchangelog](https://github.com/vaab/gitchangelog).
## v0.2.0 (2020-12-13)
* Allow custom filter function to be passed with WalkOptions. \[Nick Groenen\]
* Re-export vault_contents and WalkOptions as pub from crate root. \[Nick Groenen\]
* Run mdbook hook against README.md too. \[Nick Groenen\]
* Update installation instructions. \[Nick Groenen\]
Installation no longer requires a git repository URL now that a crate is
published.
* Add MdBook generation script and precommit hook. \[Nick Groenen\]
* Add more reliable non-ASCII tetscase. \[Nick Groenen\]
* Create FUNDING.yml. \[Nick Groenen\]
## v0.1.0 (2020-11-28)
* Public release. \[Nick Groenen\]
For a list of releases and the changes with each version, please refer to the [CHANGELOG](CHANGELOG.md).
[Obsidian]: https://obsidian.md/
[CommonMark]: https://commonmark.org/
[gitignore]: https://git-scm.com/docs/gitignore
[Cargo]: https://doc.rust-lang.org/cargo/
[Hugo]: https://gohugo.io
[`ref` and `relref` shortcodes]: https://gohugo.io/content-management/cross-references/
[Markdown Render Hooks]: https://gohugo.io/getting-started/configuration-markup#markdown-render-hooks
[BSD-2-Clause Plus Patent License]: https://spdx.org/licenses/BSD-2-Clause-Patent.html
[LICENSE]: LICENSE

70
cliff.toml Normal file
View File

@ -0,0 +1,70 @@
# https://git-cliff.org/docs/configuration
[changelog]
# changelog header
header = """
# Changelog\n
"""
# template for the changelog body
# https://keats.github.io/tera/docs/#introduction
body = """
{% if version -%}\
## {{ version }} ({{ timestamp | date(format="%Y-%m-%d") }})
{% else -%}\
## [unreleased]
{% endif %}\
{% set grouped_commits = commits | group_by(attribute="group") %}\
{% set_global groups = [] %}\
{% for group, commits in grouped_commits -%}\
{% set_global groups = groups | concat(with=group) %}\
{% endfor -%}\
{% for group in groups | sort %}
### {{ group | split(pat=" ") | slice(start=1) | join(sep=" ") | upper_first }}
{% for commit in grouped_commits[group] -%}
{% for line in commit.message | split(pat="\\n") -%}\
{% if loop.first %}
- {{ line | upper_first }} [{{ commit.author.name }}]{% else %} {{ line | trim }} {% endif %}
{% endfor -%}
{% endfor -%}
{% endfor %}\n
"""
trim = false
footer = """
<!-- generated by git-cliff -->
"""
postprocessors = [
{ pattern = "- ([Nn]ew|[Ff]ix|[Cc]hg):\\s*(.*)", replace = "- $2" },
]
[git]
# parse the commits based on https://www.conventionalcommits.org
conventional_commits = false
# filter out the commits that are not conventional
filter_unconventional = false
# process each line of a commit as an individual commit
split_commits = false
# regex for preprocessing the commit messages
commit_preprocessors = [
# { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](<REPO>/issues/${2}))"}, # replace issue numbers
]
# regex for parsing and grouping commits
commit_parsers = [
{ body = "!skip_changelog", skip = true },
{ message = "^[Nn]ew:\\s*(.*)", group = "01. New" },
{ message = "^[Ff]ix:\\s*(.*)", group = "02. Fixes" },
{ message = "^[Cc]hg:\\s*(.*)", group = "03. Changes" },
{ field = "author.name", pattern = "(dependabot|renovate)\\[bot\\]", skip = true },
{ message = ".*", group = "10. Other" },
]
# protect breaking changes from being skipped due to matching a skipping commit_parser
protect_breaking_commits = false
# filter out the commits that are not matched by commit parsers
filter_commits = false
# regex for matching git tags
tag_pattern = "v[0-9].*"
# sort the tags topologically
topo_order = false
# sort the commits inside sections by oldest/newest order
sort_commits = "oldest"

View File

@ -1 +1 @@
{"theme":"moonstone","pluginEnabledStatus":{"Open in default app":true,"file-explorer":true,"global-search":true,"switcher":true,"graph":true,"backlink":true,"command-palette":true,"markdown-importer":false,"word-count":true,"tag-pane":false,"daily-notes":false,"slides":false,"open-with-default-app":true,"random-note":false,"page-preview":true,"zk-prefixer":false,"starred":false,"outline":true,"templates":false,"workspaces":false},"isSidebarCollapsed":false,"isRightSidedockCollapsed":true,"lastOpenSidebarTab":"File explorer","useTab":false,"showLineNumber":true,"foldHeading":true,"foldIndent":true,"vimMode":true,"newFileLocation":"current","hotkeys":{"switcher:open":[{"modifiers":["Mod"],"key":" "}],"app:go-back":[{"modifiers":["Mod"],"key":"o"}],"app:go-forward":[{"modifiers":["Mod"],"key":"i"}],"editor:toggle-bold":[{"modifiers":["Mod"],"key":"8"}],"editor:toggle-italics":[{"modifiers":["Mod"],"key":"-"}],"editor:toggle-highlight":[{"modifiers":["Mod"],"key":"="}],"editor:delete-paragraph":[],"editor:focus-top":[{"modifiers":["Alt"],"key":"k"}],"editor:focus-bottom":[{"modifiers":["Alt"],"key":"j"}],"editor:focus-left":[{"modifiers":["Alt"],"key":"h"}],"editor:focus-right":[{"modifiers":["Alt"],"key":"l"}],"workspace:split-horizontal":[{"modifiers":["Alt"],"key":"s"}],"workspace:split-vertical":[{"modifiers":["Alt"],"key":"v"}],"workspace:toggle-pin":[{"modifiers":["Alt"],"key":"t"}],"graph:open":[],"backlink:open-backlinks":[{"modifiers":["Alt"],"key":"b"}],"workspace:close":[{"modifiers":["Alt"],"key":"w"}],"editor:swap-line-up":[{"modifiers":["Mod","Shift"],"key":"K"}],"editor:swap-line-down":[{"modifiers":["Mod","Shift"],"key":"J"}],"outline:open":[{"modifiers":["Alt"],"key":"o"}],"app:toggle-left-sidebar":[{"modifiers":["Alt"],"key":","}],"app:toggle-right-sidebar":[{"modifiers":["Alt"],"key":"."}],"graph:open-local":[{"modifiers":["Alt"],"key":"g"}]},"lastOpenRightSidedockTab":"Backlinks","sideDockWidth":{"left":300,"right":301},"fileSortOrder":"alphabetical","promptDelete":false,"readableLineLength":true,"alwaysUpdateLinks":true,"spellcheck":true,"strictLineBreaks":true,"spellcheckDictionary":[],"autoPairMarkdown":false,"autoPairBrackets":false,"showFrontmatter":true,"enabledPlugins":["todoist-sync-plugin"],"defaultViewMode":"preview","obsidianCss":false}
{"theme":"moonstone","pluginEnabledStatus":{"Open in default app":true,"file-explorer":true,"global-search":true,"switcher":true,"graph":true,"backlink":true,"command-palette":true,"markdown-importer":false,"word-count":true,"tag-pane":false,"daily-notes":false,"slides":false,"open-with-default-app":true,"random-note":false,"page-preview":true,"zk-prefixer":false,"starred":false,"outline":true,"templates":false,"workspaces":false},"isSidebarCollapsed":false,"isRightSidedockCollapsed":true,"lastOpenSidebarTab":"File explorer","useTab":false,"showLineNumber":true,"foldHeading":true,"foldIndent":true,"vimMode":true,"newFileLocation":"current","hotkeys":{"switcher:open":[{"modifiers":["Mod"],"key":" "}],"app:go-back":[{"modifiers":["Mod"],"key":"o"}],"app:go-forward":[{"modifiers":["Mod"],"key":"i"}],"editor:toggle-bold":[{"modifiers":["Mod"],"key":"8"}],"editor:toggle-italics":[{"modifiers":["Mod"],"key":"-"}],"editor:toggle-highlight":[{"modifiers":["Mod"],"key":"="}],"editor:delete-paragraph":[],"editor:focus-top":[{"modifiers":["Alt"],"key":"k"}],"editor:focus-bottom":[{"modifiers":["Alt"],"key":"j"}],"editor:focus-left":[{"modifiers":["Alt"],"key":"h"}],"editor:focus-right":[{"modifiers":["Alt"],"key":"l"}],"workspace:split-horizontal":[{"modifiers":["Alt"],"key":"s"}],"workspace:split-vertical":[{"modifiers":["Alt"],"key":"v"}],"workspace:toggle-pin":[{"modifiers":["Alt"],"key":"t"}],"graph:open":[],"backlink:open-backlinks":[{"modifiers":["Alt"],"key":"b"}],"workspace:close":[{"modifiers":["Alt"],"key":"w"}],"editor:swap-line-up":[{"modifiers":["Mod","Shift"],"key":"K"}],"editor:swap-line-down":[{"modifiers":["Mod","Shift"],"key":"J"}],"outline:open":[{"modifiers":["Alt"],"key":"o"}],"app:toggle-left-sidebar":[{"modifiers":["Alt"],"key":","}],"app:toggle-right-sidebar":[{"modifiers":["Alt"],"key":"."}],"graph:open-local":[{"modifiers":["Alt"],"key":"g"}]},"lastOpenRightSidedockTab":"Backlinks","sideDockWidth":{"left":300,"right":301},"fileSortOrder":"alphabetical","promptDelete":false,"readableLineLength":true,"alwaysUpdateLinks":true,"spellcheck":true,"strictLineBreaks":true,"spellcheckDictionary":[],"autoPairMarkdown":false,"autoPairBrackets":false,"showFrontmatter":true,"enabledPlugins":["todoist-sync-plugin"],"defaultViewMode":"preview","obsidianCss":false}

View File

@ -107,4 +107,4 @@
"Usage.md",
"License.md"
]
}
}

View File

@ -1 +1 @@
{}
{}

1
docs/CHANGELOG.md Symbolic link
View File

@ -0,0 +1 @@
../CHANGELOG.md

View File

@ -1 +0,0 @@
../CHANGES.md

1
docs/CONTRIBUTING.md Symbolic link
View File

@ -0,0 +1 @@
../CONTRIBUTING.md

View File

@ -2,6 +2,4 @@
- [ ] Run `./make-new-release.sh`
- [ ] Push the created release commit/tag to GitHub
- [ ] Wait for builds to turn green (<https://github.com/zoni/obsidian-export/actions>)
- [ ] Run `cargo publish`
- [ ] Publish drafted release (<https://github.com/zoni/obsidian-export/releases>)
- [ ] Wait for builds to turn green (<https://github.com/zoni/obsidian-export/actions>) and confirm everything looks OK.

View File

@ -4,5 +4,6 @@
![[usage-basic]]
![[usage-advanced]]
![[usage-library]]
![[contribute]]
![[license]]
![[CHANGES]]
![[changes]]

View File

@ -6,7 +6,7 @@ WARNING:
the docs directory.
Instead of editing README.md, edit the corresponding Markdown files in the
docs directory and run generate.sh.
docs directory and run generate.sh.
To add new sections, create new files under docs and add these to _combined.md

3
docs/changes.md Normal file
View File

@ -0,0 +1,3 @@
# Changelog
For a list of releases and the changes with each version, please refer to the [[CHANGELOG]].

4
docs/contribute.md Normal file
View File

@ -0,0 +1,4 @@
# Contributing
I will happily accept bug fixes as well as enhancements, as long as they align with the overall scope and vision of the project.
Please see [CONTRIBUTING](CONTRIBUTING.md) for more information.

View File

@ -1,4 +1,4 @@
#!/bin/sh
#!/bin/bash
set -euo pipefail

View File

@ -2,10 +2,9 @@
## Pre-built binaries
Binary releases for x86-64 processors are provided for Windows, Linux and Mac operating systems on a best-effort basis.
They are built with GitHub runners as part of the release workflow defined in `.github/workflows/release.yml`.
Pre-compiled binaries for all major platforms are available at <https://github.com/zoni/obsidian-export/releases>
The resulting binaries can be downloaded from <https://github.com/zoni/obsidian-export/releases>
In addition to the installation scripts provided, these releases are also suitable for [installation with cargo-binstall][cargo-binstall].
## Building from source
@ -24,3 +23,4 @@ If you downloaded a pre-built binary, upgrade by downloading the latest version
If you built from source, upgrade by running `cargo install obsidian-export` again.
[Cargo]: https://doc.rust-lang.org/cargo/
[cargo-binstall]: https://github.com/cargo-bins/cargo-binstall#readme

View File

@ -1,8 +1,9 @@
# License
Obsidian-export is dual-licensed under the [Apache 2.0] and the [MIT] licenses.
Obsidian-export is open-source software released under the [BSD-2-Clause Plus Patent License].
This license is designed to provide: a) a simple permissive license; b) that is compatible with the GNU General Public License (GPL), version 2; and c) which also has an express patent grant included.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
Please review the [LICENSE] file for the full text of the license.
[Apache 2.0]: https://github.com/zoni/obsidian-export/blob/master/LICENSE-APACHE
[MIT]: https://github.com/zoni/obsidian-export/blob/master/LICENSE-MIT
[BSD-2-Clause Plus Patent License]: https://spdx.org/licenses/BSD-2-Clause-Patent.html
[LICENSE]: LICENSE

View File

@ -12,9 +12,14 @@ 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 matching 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`)
* using `--skip-tags foo --skip-tags bar` will skip any files that have the tags `foo` or `bar` in their frontmatter
* using `--only-tags foo --only-tags bar` will skip any files that **don't** have the tags `foo` or `bar` in their frontmatter
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).
@ -112,4 +117,4 @@ With these hooks in place, links to both notes as well as file attachments shoul
[gitignore]: https://git-scm.com/docs/gitignore
[hugo-relative-linking]: https://notes.nick.groenen.me/notes/relative-linking-in-hugo/
[hugo]: https://gohugo.io
[markdown render hooks]: https://gohugo.io/getting-started/configuration-markup#markdown-render-hooks
[markdown render hooks]: https://gohugo.io/getting-started/configuration-markup#markdown-render-hooks

View File

@ -3,7 +3,7 @@
The main interface of _obsidian-export_ is the `obsidian-export` CLI command.
As a text interface, this must be run from a terminal or Windows PowerShell.
It is assumed that you have basic familiarity with command-line interfaces and that you set up your `PATH` correctly if you installed with `cargo`.
It is assumed that you have basic familiarity with command-line interfaces and that you set up your `PATH` correctly if you installed with `cargo`.
Running `obsidian-export --version` should print a version number rather than giving some kind of error.
> If you downloaded a pre-built binary and didn't put it a location referenced by `PATH` (for example, you put it in `Downloads`), you will need to provide the full path to the binary instead.
@ -44,7 +44,7 @@ Using the `--start-at` argument, you can export just a subset of your vault.
Given the following vault structure:
```
my-obsidian-vault
my-obsidian-vault
├── Notes/
├── Books/
└── People/

View File

@ -17,18 +17,28 @@ get_next_version_number() {
done
}
git add .
if ! git diff-index --quiet HEAD; then
printf "Working directory is not clean. Please commit or stash your changes.\n"
exit 1
fi
VERSION=$(get_next_version_number)
git tag "v${VERSION}"
git cliff --latest --prepend CHANGELOG.md > /dev/null
${EDITOR:-vim} CHANGELOG.md
docs/generate.sh
sed -i -E "s/^version = \".+\"$/version = \"${VERSION}\"/" Cargo.toml
cargo check
git commit "Cargo.*" --message "Release v${VERSION}"
git tag "v${VERSION}"
gitchangelog
${EDITOR:-vim} CHANGES.md
docs/generate.sh
git add CHANGES.md README.md
git commit --amend --no-edit
git add .
# There are likely trailing whitespace changes in the changelog, but a single
# run of pre-commit will fix these automatically.
pre-commit run || git add .
git commit --message "Release v${VERSION}"
git tag "v${VERSION}" --force
printf "\n\nSuccessfully created release %s\n" "v${VERSION}"

View File

@ -40,6 +40,7 @@ pub fn frontmatter_to_str(frontmatter: Frontmatter) -> Result<String> {
}
let mut buffer = String::new();
buffer.push_str("---\n");
buffer.push_str(&serde_yaml::to_string(&frontmatter)?);
buffer.push_str("---\n");
Ok(buffer)

View File

@ -6,6 +6,7 @@ extern crate lazy_static;
mod context;
mod frontmatter;
pub mod postprocessors;
mod references;
mod walker;
@ -16,7 +17,7 @@ pub use walker::{vault_contents, WalkOptions};
use frontmatter::{frontmatter_from_str, frontmatter_to_str};
use pathdiff::diff_paths;
use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS};
use pulldown_cmark::{CodeBlockKind, CowStr, Event, Options, Parser, Tag};
use pulldown_cmark::{CodeBlockKind, CowStr, Event, HeadingLevel, Options, Parser, Tag};
use pulldown_cmark_to_cmark::cmark_with_options;
use rayon::prelude::*;
use references::*;
@ -29,6 +30,7 @@ use std::io::prelude::*;
use std::io::ErrorKind;
use std::path::{Path, PathBuf};
use std::str;
use unicode_normalization::UnicodeNormalization;
/// A series of markdown [Event]s that are generated while traversing an Obsidian markdown note.
pub type MarkdownEvents<'a> = Vec<Event<'a>>;
@ -73,9 +75,8 @@ pub type MarkdownEvents<'a> = Vec<Event<'a>>;
/// defined inline as a closure.
///
/// ```
/// use obsidian_export::{Context, Exporter, MarkdownEvents, PostprocessorResult};
/// use obsidian_export::pulldown_cmark::{CowStr, Event};
/// use obsidian_export::serde_yaml::Value;
/// use obsidian_export::{Exporter, PostprocessorResult};
/// # use std::path::PathBuf;
/// # use tempfile::TempDir;
///
@ -85,7 +86,7 @@ pub type MarkdownEvents<'a> = Vec<Event<'a>>;
/// let mut exporter = Exporter::new(source, destination);
///
/// // add_postprocessor registers a new postprocessor. In this example we use a closure.
/// exporter.add_postprocessor(&|mut context, events| {
/// exporter.add_postprocessor(&|context, _events| {
/// // This is the key we'll insert into the frontmatter. In this case, the string "foo".
/// let key = Value::String("foo".to_string());
/// // This is the value we'll insert into the frontmatter. In this case, the string "bar".
@ -94,9 +95,8 @@ pub type MarkdownEvents<'a> = Vec<Event<'a>>;
/// // Frontmatter can be updated in-place, so we can call insert on it directly.
/// context.frontmatter.insert(key, value);
///
/// // Postprocessors must return their (modified) context, the markdown events that make
/// // up the note and a next action to take.
/// (context, events, PostprocessorResult::Continue)
/// // This return value indicates processing should continue.
/// PostprocessorResult::Continue
/// });
///
/// exporter.run().unwrap();
@ -116,18 +116,13 @@ pub type MarkdownEvents<'a> = Vec<Event<'a>>;
/// # use tempfile::TempDir;
/// #
/// /// This postprocessor replaces any instance of "foo" with "bar" in the note body.
/// fn foo_to_bar(
/// context: Context,
/// events: MarkdownEvents,
/// ) -> (Context, MarkdownEvents, PostprocessorResult) {
/// let events = events
/// .into_iter()
/// .map(|event| match event {
/// Event::Text(text) => Event::Text(CowStr::from(text.replace("foo", "bar"))),
/// event => event,
/// })
/// .collect();
/// (context, events, PostprocessorResult::Continue)
/// fn foo_to_bar(context: &mut Context, events: &mut MarkdownEvents) -> PostprocessorResult {
/// for event in events.iter_mut() {
/// if let Event::Text(text) = event {
/// *event = Event::Text(CowStr::from(text.replace("foo", "bar")))
/// }
/// }
/// PostprocessorResult::Continue
/// }
///
/// # let tmp_dir = TempDir::new().expect("failed to make tempdir");
@ -138,8 +133,8 @@ pub type MarkdownEvents<'a> = Vec<Event<'a>>;
/// # exporter.run().unwrap();
/// ```
pub type Postprocessor =
dyn Fn(Context, MarkdownEvents) -> (Context, MarkdownEvents, PostprocessorResult) + Send + Sync;
pub type Postprocessor<'f> =
dyn Fn(&mut Context, &mut MarkdownEvents) -> PostprocessorResult + Send + Sync + 'f;
type Result<T, E = ExportError> = std::result::Result<T, E>;
const PERCENTENCODE_CHARS: &AsciiSet = &CONTROLS.add(b' ').add(b'(').add(b')').add(b'%').add(b'?');
@ -210,7 +205,7 @@ pub enum ExportError {
},
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// Emitted by [Postprocessor]s to signal the next action to take.
pub enum PostprocessorResult {
/// Continue with the next post-processor (if any).
@ -236,8 +231,8 @@ pub struct Exporter<'a> {
vault_contents: Option<Vec<PathBuf>>,
walk_options: WalkOptions<'a>,
process_embeds_recursively: bool,
postprocessors: Vec<&'a Postprocessor>,
embed_postprocessors: Vec<&'a Postprocessor>,
postprocessors: Vec<&'a Postprocessor<'a>>,
embed_postprocessors: Vec<&'a Postprocessor<'a>>,
}
impl<'a> fmt::Debug for Exporter<'a> {
@ -386,7 +381,7 @@ impl<'a> Exporter<'a> {
.strip_prefix(&self.start_at.clone())
.expect("file should always be nested under root")
.to_path_buf();
let destination = &self.destination.join(&relative_path);
let destination = &self.destination.join(relative_path);
self.export_note(&file, destination)
})?;
Ok(())
@ -397,7 +392,7 @@ impl<'a> Exporter<'a> {
true => self.parse_and_export_obsidian_note(src, dest),
false => copy_file(src, dest),
}
.context(FileExportError { path: src })
.context(FileExportSnafu { path: src })
}
fn parse_and_export_obsidian_note(&self, src: &Path, dest: &Path) -> Result<()> {
@ -406,10 +401,7 @@ impl<'a> Exporter<'a> {
let (frontmatter, mut markdown_events) = self.parse_obsidian_note(src, &context)?;
context.frontmatter = frontmatter;
for func in &self.postprocessors {
let res = func(context, markdown_events);
context = res.0;
markdown_events = res.1;
match res.2 {
match func(&mut context, &mut markdown_events) {
PostprocessorResult::StopHere => break,
PostprocessorResult::StopAndSkipNote => return Ok(()),
PostprocessorResult::Continue => (),
@ -425,15 +417,15 @@ impl<'a> Exporter<'a> {
};
if write_frontmatter {
let mut frontmatter_str = frontmatter_to_str(context.frontmatter)
.context(FrontMatterEncodeError { path: src })?;
.context(FrontMatterEncodeSnafu { path: src })?;
frontmatter_str.push('\n');
outfile
.write_all(frontmatter_str.as_bytes())
.context(WriteError { path: &dest })?;
.context(WriteSnafu { path: &dest })?;
}
outfile
.write_all(render_mdevents_to_mdtext(markdown_events).as_bytes())
.context(WriteError { path: &dest })?;
.context(WriteSnafu { path: &dest })?;
Ok(())
}
@ -447,11 +439,11 @@ impl<'a> Exporter<'a> {
file_tree: context.file_tree(),
});
}
let content = fs::read_to_string(&path).context(ReadError { path })?;
let content = fs::read_to_string(path).context(ReadSnafu { path })?;
let (frontmatter, content) =
matter::matter(&content).unwrap_or(("".to_string(), content.to_string()));
let frontmatter =
frontmatter_from_str(&frontmatter).context(FrontMatterDecodeError { path })?;
frontmatter_from_str(&frontmatter).context(FrontMatterDecodeSnafu { path })?;
let mut parser_options = Options::empty();
parser_options.insert(Options::ENABLE_TABLES);
@ -614,10 +606,7 @@ impl<'a> Exporter<'a> {
for func in &self.embed_postprocessors {
// Postprocessors running on embeds shouldn't be able to change frontmatter (or
// any other metadata), so we give them a clone of the context.
let res = func(child_context, events);
child_context = res.0;
events = res.1;
match res.2 {
match func(&mut child_context, &mut events) {
PostprocessorResult::StopHere => break,
PostprocessorResult::StopAndSkipNote => {
events = vec![];
@ -658,9 +647,9 @@ impl<'a> Exporter<'a> {
Ok(events)
}
fn make_link_to_file<'b, 'c>(
fn make_link_to_file<'c>(
&self,
reference: ObsidianNoteReference<'b>,
reference: ObsidianNoteReference<'_>,
context: &Context,
) -> MarkdownEvents<'c> {
let target_file = reference
@ -689,7 +678,7 @@ impl<'a> Exporter<'a> {
// in case of embedded notes.
let rel_link = diff_paths(
target_file,
&context
context
.root_file()
.parent()
.expect("obsidian content files should always have a parent"),
@ -718,22 +707,33 @@ impl<'a> Exporter<'a> {
}
}
/// Get the full path for the given filename when it's contained in vault_contents, taking into
/// account:
///
/// 1. Standard Obsidian note references not including a .md extension.
/// 2. Case-insensitive matching
/// 3. Unicode normalization rules using normalization form C
/// (https://www.w3.org/TR/charmod-norm/#unicodeNormalization)
fn lookup_filename_in_vault<'a>(
filename: &str,
vault_contents: &'a [PathBuf],
) -> Option<&'a PathBuf> {
// Markdown files don't have their .md extension added by Obsidian, but other files (images,
// PDFs, etc) do so we match on both possibilities.
//
// References can also refer to notes in a different case (to lowercase text in a
// sentence even if the note is capitalized for example) so we also try a case-insensitive
// lookup.
let filename = PathBuf::from(filename);
let filename_normalized = filename.to_string_lossy().nfc().collect::<String>();
vault_contents.iter().find(|path| {
let path_lowered = PathBuf::from(path.to_string_lossy().to_lowercase());
path.ends_with(&filename)
|| path_lowered.ends_with(&filename.to_lowercase())
|| path.ends_with(format!("{}.md", &filename))
|| path_lowered.ends_with(format!("{}.md", &filename.to_lowercase()))
let path_normalized_str = path.to_string_lossy().nfc().collect::<String>();
let path_normalized = PathBuf::from(&path_normalized_str);
let path_normalized_lowered = PathBuf::from(&path_normalized_str.to_lowercase());
// It would be convenient if we could just do `filename.set_extension("md")` at the start
// of this funtion so we don't need multiple separate + ".md" match cases here, however
// that would break with a reference of `[[Note.1]]` linking to `[[Note.1.md]]`.
path_normalized.ends_with(&filename_normalized)
|| path_normalized.ends_with(filename_normalized.clone() + ".md")
|| path_normalized_lowered.ends_with(filename_normalized.to_lowercase())
|| path_normalized_lowered.ends_with(filename_normalized.to_lowercase() + ".md")
})
}
@ -742,7 +742,6 @@ fn render_mdevents_to_mdtext(markdown: MarkdownEvents) -> String {
cmark_with_options(
markdown.iter(),
&mut buffer,
None,
pulldown_cmark_to_cmark::Options::default(),
)
.expect("formatting to string not expected to fail");
@ -751,32 +750,28 @@ fn render_mdevents_to_mdtext(markdown: MarkdownEvents) -> String {
}
fn create_file(dest: &Path) -> Result<File> {
let file = File::create(&dest)
let file = File::create(dest)
.or_else(|err| {
if err.kind() == ErrorKind::NotFound {
let parent = dest.parent().expect("file should have a parent directory");
if let Err(err) = std::fs::create_dir_all(&parent) {
return Err(err);
}
std::fs::create_dir_all(parent)?
}
File::create(&dest)
File::create(dest)
})
.context(WriteError { path: dest })?;
.context(WriteSnafu { path: dest })?;
Ok(file)
}
fn copy_file(src: &Path, dest: &Path) -> Result<()> {
std::fs::copy(&src, &dest)
std::fs::copy(src, dest)
.or_else(|err| {
if err.kind() == ErrorKind::NotFound {
let parent = dest.parent().expect("file should have a parent directory");
if let Err(err) = std::fs::create_dir_all(&parent) {
return Err(err);
}
std::fs::create_dir_all(parent)?
}
std::fs::copy(&src, &dest)
std::fs::copy(src, dest)
})
.context(WriteError { path: dest })?;
.context(WriteSnafu { path: dest })?;
Ok(())
}
@ -788,18 +783,19 @@ fn is_markdown_file(file: &Path) -> bool {
/// Reduce a given `MarkdownEvents` to just those elements which are children of the given section
/// (heading name).
fn reduce_to_section<'a, 'b>(events: MarkdownEvents<'a>, section: &'b str) -> MarkdownEvents<'a> {
fn reduce_to_section<'a>(events: MarkdownEvents<'a>, section: &str) -> MarkdownEvents<'a> {
let mut filtered_events = Vec::with_capacity(events.len());
let mut target_section_encountered = false;
let mut currently_in_target_section = false;
let mut section_level = 0;
let mut last_level = 0;
let mut section_level = HeadingLevel::H1;
let mut last_level = HeadingLevel::H1;
let mut last_tag_was_heading = false;
for event in events.into_iter() {
filtered_events.push(event.clone());
match event {
Event::Start(Tag::Heading(level)) => {
// FIXME: This should propagate fragment_identifier and classes.
Event::Start(Tag::Heading(level, _fragment_identifier, _classes)) => {
last_tag_was_heading = true;
last_level = level;
if currently_in_target_section && level <= section_level {
@ -855,7 +851,10 @@ fn event_to_owned<'a>(event: Event) -> Event<'a> {
fn tag_to_owned<'a>(tag: Tag) -> Tag<'a> {
match tag {
Tag::Paragraph => Tag::Paragraph,
Tag::Heading(level) => Tag::Heading(level),
Tag::Heading(level, _fragment_identifier, _classes) => {
// FIXME: This should propagate fragment_identifier and classes.
Tag::Heading(level, None, Vec::new())
}
Tag::BlockQuote => Tag::BlockQuote,
Tag::CodeBlock(codeblock_kind) => Tag::CodeBlock(codeblock_kind_to_owned(codeblock_kind)),
Tag::List(optional) => Tag::List(optional),
@ -889,3 +888,77 @@ fn codeblock_kind_to_owned<'a>(codeblock_kind: CodeBlockKind) -> CodeBlockKind<'
CodeBlockKind::Fenced(cowstr) => CodeBlockKind::Fenced(CowStr::from(cowstr.into_string())),
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use rstest::rstest;
lazy_static! {
static ref VAULT: Vec<std::path::PathBuf> = vec![
PathBuf::from("NoteA.md"),
PathBuf::from("Document.pdf"),
PathBuf::from("Note.1.md"),
PathBuf::from("nested/NoteA.md"),
PathBuf::from("Note\u{E4}.md"), // Noteä.md, see also encodings() below
];
}
#[test]
fn encodings() {
// Standard "Latin Small Letter A with Diaeresis" (U+00E4)
// Encoded in UTF-8 as two bytes: 0xC3 0xA4
assert_eq!(String::from_utf8(vec![0xC3, 0xA4]).unwrap(), "ä");
assert_eq!("\u{E4}", "ä");
// Basic (ASCII) lowercase a followed by Unicode Character “◌̈” (U+0308)
// Renders the same visual appearance but is encoded in UTF-8 as three bytes:
// 0x61 0xCC 0x88
assert_eq!(String::from_utf8(vec![0x61, 0xCC, 0x88]).unwrap(), "");
assert_eq!("a\u{308}", "");
assert_eq!("\u{61}\u{308}", "");
// For more examples and a better explanation of this concept, see
// https://www.w3.org/TR/charmod-norm/#aringExample
}
#[rstest]
// Exact match
#[case("NoteA.md", "NoteA.md")]
#[case("NoteA", "NoteA.md")]
// Same note in subdir, exact match should find it
#[case("nested/NoteA.md", "nested/NoteA.md")]
#[case("nested/NoteA", "nested/NoteA.md")]
// Different extensions
#[case("Document.pdf", "Document.pdf")]
#[case("Note.1", "Note.1.md")]
#[case("Note.1.md", "Note.1.md")]
// Case-insensitive matches
#[case("notea.md", "NoteA.md")]
#[case("notea", "NoteA.md")]
#[case("NESTED/notea.md", "nested/NoteA.md")]
#[case("NESTED/notea", "nested/NoteA.md")]
// "Latin Small Letter A with Diaeresis" (U+00E4)
#[case("Note\u{E4}.md", "Note\u{E4}.md")]
#[case("Note\u{E4}", "Note\u{E4}.md")]
// Basic (ASCII) lowercase a followed by Unicode Character “◌̈” (U+0308)
// The UTF-8 encoding is different but it renders the same visual appearance as the case above,
// so we expect it to find the same file.
#[case("Note\u{61}\u{308}.md", "Note\u{E4}.md")]
#[case("Note\u{61}\u{308}", "Note\u{E4}.md")]
// We should expect this to work with lowercasing as well, so NoteÄ should find Noteä
// NoteÄ where Ä = Single Ä (U+00C4)
#[case("Note\u{C4}.md", "Note\u{E4}.md")]
#[case("Note\u{C4}", "Note\u{E4}.md")]
// NoteÄ where Ä = decomposed to A (U+0041)+ ◌̈ (U+0308)
#[case("Note\u{41}\u{308}.md", "Note\u{E4}.md")]
#[case("Note\u{41}\u{308}", "Note\u{E4}.md")]
fn test_lookup_filename_in_vault(#[case] input: &str, #[case] expected: &str) {
let result = lookup_filename_in_vault(input, &VAULT);
println!("Test input: {:?}", input);
println!("Expecting: {:?}", expected);
println!("Got: {:?}", result.unwrap_or(&PathBuf::from("")));
assert_eq!(result, Some(&PathBuf::from(expected)))
}
}

View File

@ -1,6 +1,7 @@
use eyre::{eyre, Result};
use gumdrop::Options;
use obsidian_export::{ExportError, Exporter, FrontmatterStrategy, WalkOptions};
use obsidian_export::{postprocessors::*, ExportError};
use obsidian_export::{Exporter, FrontmatterStrategy, WalkOptions};
use std::{env, path::PathBuf};
const VERSION: &str = env!("CARGO_PKG_VERSION");
@ -38,6 +39,12 @@ struct Opts {
)]
ignore_file: String,
#[options(no_short, help = "Exclude files with this tag from the export")]
skip_tags: Vec<String>,
#[options(no_short, help = "Export only files with this tag")]
only_tags: Vec<String>,
#[options(no_short, help = "Export hidden files", default = "false")]
hidden: bool,
@ -46,6 +53,13 @@ struct Opts {
#[options(no_short, help = "Don't process embeds recursively", default = "false")]
no_recursive_embeds: bool,
#[options(
no_short,
help = "Convert soft line breaks to hard line breaks. This mimics Obsidian's 'Strict line breaks' setting",
default = "false"
)]
hard_linebreaks: bool,
}
fn frontmatter_strategy_from_str(input: &str) -> Result<FrontmatterStrategy> {
@ -82,6 +96,13 @@ fn main() {
exporter.process_embeds_recursively(!args.no_recursive_embeds);
exporter.walk_options(walk_options);
if args.hard_linebreaks {
exporter.add_postprocessor(&softbreaks_to_hardbreaks);
}
let tags_postprocessor = filter_by_tags(args.skip_tags, args.only_tags);
exporter.add_postprocessor(&tags_postprocessor);
if let Some(path) = args.start_at {
exporter.start_at(path);
}

106
src/postprocessors.rs Normal file
View File

@ -0,0 +1,106 @@
//! A collection of officially maintained [postprocessors][crate::Postprocessor].
use super::{Context, MarkdownEvents, PostprocessorResult};
use pulldown_cmark::Event;
use serde_yaml::Value;
/// 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(
_context: &mut Context,
events: &mut MarkdownEvents,
) -> PostprocessorResult {
for event in events.iter_mut() {
if event == &Event::SoftBreak {
*event = Event::HardBreak;
}
}
PostprocessorResult::Continue
}
pub fn filter_by_tags(
skip_tags: Vec<String>,
only_tags: Vec<String>,
) -> impl Fn(&mut Context, &mut MarkdownEvents) -> PostprocessorResult {
move |context: &mut Context, _events: &mut MarkdownEvents| -> PostprocessorResult {
match context.frontmatter.get("tags") {
None => filter_by_tags_(&[], &skip_tags, &only_tags),
Some(Value::Sequence(tags)) => filter_by_tags_(tags, &skip_tags, &only_tags),
_ => PostprocessorResult::Continue,
}
}
}
fn filter_by_tags_(
tags: &[Value],
skip_tags: &[String],
only_tags: &[String],
) -> PostprocessorResult {
let skip = skip_tags
.iter()
.any(|tag| tags.contains(&Value::String(tag.to_string())));
let include = only_tags.is_empty()
|| only_tags
.iter()
.any(|tag| tags.contains(&Value::String(tag.to_string())));
if skip || !include {
PostprocessorResult::StopAndSkipNote
} else {
PostprocessorResult::Continue
}
}
#[test]
fn test_filter_tags() {
let tags = vec![
Value::String("skip".to_string()),
Value::String("publish".to_string()),
];
let empty_tags = vec![];
assert_eq!(
filter_by_tags_(&empty_tags, &[], &[]),
PostprocessorResult::Continue,
"When no exclusion & inclusion are specified, files without tags are included"
);
assert_eq!(
filter_by_tags_(&tags, &[], &[]),
PostprocessorResult::Continue,
"When no exclusion & inclusion are specified, files with tags are included"
);
assert_eq!(
filter_by_tags_(&tags, &["exclude".to_string()], &[]),
PostprocessorResult::Continue,
"When exclusion tags don't match files with tags are included"
);
assert_eq!(
filter_by_tags_(&empty_tags, &["exclude".to_string()], &[]),
PostprocessorResult::Continue,
"When exclusion tags don't match files without tags are included"
);
assert_eq!(
filter_by_tags_(&tags, &[], &["publish".to_string()]),
PostprocessorResult::Continue,
"When exclusion tags don't match files with tags are included"
);
assert_eq!(
filter_by_tags_(&empty_tags, &[], &["include".to_string()]),
PostprocessorResult::StopAndSkipNote,
"When inclusion tags are specified files without tags are excluded"
);
assert_eq!(
filter_by_tags_(&tags, &[], &["include".to_string()]),
PostprocessorResult::StopAndSkipNote,
"When exclusion tags don't match files with tags are exluded"
);
assert_eq!(
filter_by_tags_(&tags, &["skip".to_string()], &["skip".to_string()]),
PostprocessorResult::StopAndSkipNote,
"When both inclusion and exclusion tags are the same exclusion wins"
);
assert_eq!(
filter_by_tags_(&tags, &["skip".to_string()], &["publish".to_string()]),
PostprocessorResult::StopAndSkipNote,
"When both inclusion and exclusion tags match exclusion wins"
);
}

View File

@ -6,7 +6,7 @@ lazy_static! {
Regex::new(r"^(?P<file>[^#|]+)??(#(?P<section>.+?))??(\|(?P<label>.+?))??$").unwrap();
}
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Eq)]
/// ObsidianNoteReference represents the structure of a `[[note]]` or `![[embed]]` reference.
pub struct ObsidianNoteReference<'a> {
/// The file (note name or partial path) being referenced.
@ -18,7 +18,7 @@ pub struct ObsidianNoteReference<'a> {
pub label: Option<&'a str>,
}
#[derive(PartialEq)]
#[derive(PartialEq, Eq)]
/// RefParserState enumerates all the possible parsing states [RefParser] may enter.
pub enum RefParserState {
NoState,
@ -73,9 +73,9 @@ impl<'a> ObsidianNoteReference<'a> {
let captures = OBSIDIAN_NOTE_LINK_RE
.captures(text)
.expect("note link regex didn't match - bad input?");
let file = captures.name("file").map(|v| v.as_str());
let file = captures.name("file").map(|v| v.as_str().trim());
let label = captures.name("label").map(|v| v.as_str());
let section = captures.name("section").map(|v| v.as_str());
let section = captures.name("section").map(|v| v.as_str().trim());
ObsidianNoteReference {
file,

View File

@ -1,4 +1,4 @@
use crate::{ExportError, WalkDirError};
use crate::{ExportError, WalkDirSnafu};
use ignore::{DirEntry, Walk, WalkBuilder};
use snafu::ResultExt;
use std::fmt;
@ -88,9 +88,9 @@ pub fn vault_contents(path: &Path, opts: WalkOptions) -> Result<Vec<PathBuf>> {
let mut contents = Vec::new();
let walker = opts.build_walker(path);
for entry in walker {
let entry = entry.context(WalkDirError { path })?;
let entry = entry.context(WalkDirSnafu { path })?;
let path = entry.path();
let metadata = entry.metadata().context(WalkDirError { path })?;
let metadata = entry.metadata().context(WalkDirSnafu { path })?;
if metadata.is_dir() {
continue;

View File

@ -31,13 +31,14 @@ fn test_main_variants_with_default_options() {
continue;
};
let filename = entry.file_name().to_string_lossy().into_owned();
let expected = read_to_string(entry.path()).expect(&format!(
"failed to read {} from testdata/expected/main-samples/",
entry.path().display()
));
let actual = read_to_string(tmp_dir.path().clone().join(PathBuf::from(&filename))).expect(
&format!("failed to read {} from temporary exportdir", filename),
);
let expected = read_to_string(entry.path()).unwrap_or_else(|_| {
panic!(
"failed to read {} from testdata/expected/main-samples/",
entry.path().display()
)
});
let actual = read_to_string(tmp_dir.path().join(PathBuf::from(&filename)))
.unwrap_or_else(|_| panic!("failed to read {} from temporary exportdir", filename));
assert_eq!(
expected, actual,
@ -61,7 +62,6 @@ fn test_frontmatter_never() {
let actual = read_to_string(
tmp_dir
.path()
.clone()
.join(PathBuf::from("note-with-frontmatter.md")),
)
.unwrap();
@ -84,7 +84,6 @@ fn test_frontmatter_always() {
let actual = read_to_string(
tmp_dir
.path()
.clone()
.join(PathBuf::from("note-without-frontmatter.md")),
)
.unwrap();
@ -95,7 +94,6 @@ fn test_frontmatter_always() {
let actual = read_to_string(
tmp_dir
.path()
.clone()
.join(PathBuf::from("note-with-frontmatter.md")),
)
.unwrap();
@ -113,10 +111,7 @@ fn test_exclude() {
.run()
.expect("exporter returned error");
let excluded_note = tmp_dir
.path()
.clone()
.join(PathBuf::from("excluded-note.md"));
let excluded_note = tmp_dir.path().join(PathBuf::from("excluded-note.md"));
assert!(
!excluded_note.exists(),
"exluded-note.md was found in tmpdir, but should be absent due to .export-ignore rules"
@ -135,14 +130,14 @@ fn test_single_file_to_dir() {
assert_eq!(
read_to_string("tests/testdata/expected/single-file/note.md").unwrap(),
read_to_string(tmp_dir.path().clone().join(PathBuf::from("note.md"))).unwrap(),
read_to_string(tmp_dir.path().join(PathBuf::from("note.md"))).unwrap(),
);
}
#[test]
fn test_single_file_to_file() {
let tmp_dir = TempDir::new().expect("failed to make tempdir");
let dest = tmp_dir.path().clone().join(PathBuf::from("export.md"));
let dest = tmp_dir.path().join(PathBuf::from("export.md"));
Exporter::new(
PathBuf::from("tests/testdata/input/single-file/note.md"),
@ -170,14 +165,14 @@ fn test_start_at_subdir() {
let expected = if cfg!(windows) {
read_to_string("tests/testdata/expected/start-at/subdir/Note B.md")
.unwrap()
.replace("/", "\\")
.replace('/', "\\")
} else {
read_to_string("tests/testdata/expected/start-at/subdir/Note B.md").unwrap()
};
assert_eq!(
expected,
read_to_string(tmp_dir.path().clone().join(PathBuf::from("Note B.md"))).unwrap(),
read_to_string(tmp_dir.path().join(PathBuf::from("Note B.md"))).unwrap(),
);
}
@ -196,21 +191,21 @@ fn test_start_at_file_within_subdir_destination_is_dir() {
let expected = if cfg!(windows) {
read_to_string("tests/testdata/expected/start-at/single-file/Note B.md")
.unwrap()
.replace("/", "\\")
.replace('/', "\\")
} else {
read_to_string("tests/testdata/expected/start-at/single-file/Note B.md").unwrap()
};
assert_eq!(
expected,
read_to_string(tmp_dir.path().clone().join(PathBuf::from("Note B.md"))).unwrap(),
read_to_string(tmp_dir.path().join(PathBuf::from("Note B.md"))).unwrap(),
);
}
#[test]
fn test_start_at_file_within_subdir_destination_is_file() {
let tmp_dir = TempDir::new().expect("failed to make tempdir");
let dest = tmp_dir.path().clone().join(PathBuf::from("note.md"));
let dest = tmp_dir.path().join(PathBuf::from("note.md"));
let mut exporter = Exporter::new(
PathBuf::from("tests/testdata/input/start-at/"),
dest.clone(),
@ -223,7 +218,7 @@ fn test_start_at_file_within_subdir_destination_is_file() {
let expected = if cfg!(windows) {
read_to_string("tests/testdata/expected/start-at/single-file/Note B.md")
.unwrap()
.replace("/", "\\")
.replace('/', "\\")
} else {
read_to_string("tests/testdata/expected/start-at/single-file/Note B.md").unwrap()
};
@ -359,7 +354,7 @@ fn test_no_recursive_embeds() {
assert_eq!(
read_to_string("tests/testdata/expected/infinite-recursion/Note A.md").unwrap(),
read_to_string(tmp_dir.path().clone().join(PathBuf::from("Note A.md"))).unwrap(),
read_to_string(tmp_dir.path().join(PathBuf::from("Note A.md"))).unwrap(),
);
}
@ -385,13 +380,14 @@ fn test_non_ascii_filenames() {
continue;
};
let filename = entry.file_name().to_string_lossy().into_owned();
let expected = read_to_string(entry.path()).expect(&format!(
"failed to read {} from testdata/expected/non-ascii/",
entry.path().display()
));
let actual = read_to_string(tmp_dir.path().clone().join(PathBuf::from(&filename))).expect(
&format!("failed to read {} from temporary exportdir", filename),
);
let expected = read_to_string(entry.path()).unwrap_or_else(|_| {
panic!(
"failed to read {} from testdata/expected/non-ascii/",
entry.path().display()
)
});
let actual = read_to_string(tmp_dir.path().join(PathBuf::from(&filename)))
.unwrap_or_else(|_| panic!("failed to read {} from temporary exportdir", filename));
assert_eq!(
expected, actual,
@ -414,12 +410,12 @@ fn test_same_filename_different_directories() {
let expected = if cfg!(windows) {
read_to_string("tests/testdata/expected/same-filename-different-directories/Note.md")
.unwrap()
.replace("/", "\\")
.replace('/', "\\")
} else {
read_to_string("tests/testdata/expected/same-filename-different-directories/Note.md")
.unwrap()
};
let actual = read_to_string(tmp_dir.path().clone().join(PathBuf::from("Note.md"))).unwrap();
let actual = read_to_string(tmp_dir.path().join(PathBuf::from("Note.md"))).unwrap();
assert_eq!(expected, actual);
}

View File

@ -1,36 +1,32 @@
use obsidian_export::postprocessors::{filter_by_tags, softbreaks_to_hardbreaks};
use obsidian_export::{Context, Exporter, MarkdownEvents, PostprocessorResult};
use pretty_assertions::assert_eq;
use pulldown_cmark::{CowStr, Event};
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 walkdir::WalkDir;
/// This postprocessor replaces any instance of "foo" with "bar" in the note body.
fn foo_to_bar(
ctx: Context,
events: MarkdownEvents,
) -> (Context, MarkdownEvents, PostprocessorResult) {
let events = events
.into_iter()
.map(|event| match event {
Event::Text(text) => Event::Text(CowStr::from(text.replace("foo", "bar"))),
event => event,
})
.collect();
(ctx, events, PostprocessorResult::Continue)
fn foo_to_bar(_ctx: &mut Context, events: &mut MarkdownEvents) -> PostprocessorResult {
for event in events.iter_mut() {
if let Event::Text(text) = event {
*event = Event::Text(CowStr::from(text.replace("foo", "bar")))
}
}
PostprocessorResult::Continue
}
/// This postprocessor appends "bar: baz" to frontmatter.
fn append_frontmatter(
mut ctx: Context,
events: MarkdownEvents,
) -> (Context, MarkdownEvents, PostprocessorResult) {
fn append_frontmatter(ctx: &mut Context, _events: &mut MarkdownEvents) -> PostprocessorResult {
ctx.frontmatter.insert(
Value::String("bar".to_string()),
Value::String("baz".to_string()),
);
(ctx, events, PostprocessorResult::Continue)
PostprocessorResult::Continue
}
// The purpose of this test to verify the `append_frontmatter` postprocessor is called to extend
@ -49,7 +45,7 @@ fn test_postprocessors() {
exporter.run().unwrap();
let expected = read_to_string("tests/testdata/expected/postprocessors/Note.md").unwrap();
let actual = read_to_string(tmp_dir.path().clone().join(PathBuf::from("Note.md"))).unwrap();
let actual = read_to_string(tmp_dir.path().join(PathBuf::from("Note.md"))).unwrap();
assert_eq!(expected, actual);
}
@ -61,9 +57,8 @@ fn test_postprocessor_stophere() {
tmp_dir.path().to_path_buf(),
);
exporter.add_postprocessor(&|ctx, mdevents| (ctx, mdevents, PostprocessorResult::StopHere));
exporter
.add_embed_postprocessor(&|ctx, mdevents| (ctx, mdevents, PostprocessorResult::StopHere));
exporter.add_postprocessor(&|_ctx, _mdevents| PostprocessorResult::StopHere);
exporter.add_embed_postprocessor(&|_ctx, _mdevents| PostprocessorResult::StopHere);
exporter.add_postprocessor(&|_, _| panic!("should not be called due to above processor"));
exporter.add_embed_postprocessor(&|_, _| panic!("should not be called due to above processor"));
exporter.run().unwrap();
@ -72,7 +67,7 @@ fn test_postprocessor_stophere() {
#[test]
fn test_postprocessor_stop_and_skip() {
let tmp_dir = TempDir::new().expect("failed to make tempdir");
let note_path = tmp_dir.path().clone().join(PathBuf::from("Note.md"));
let note_path = tmp_dir.path().join(PathBuf::from("Note.md"));
let mut exporter = Exporter::new(
PathBuf::from("tests/testdata/input/postprocessors"),
@ -83,8 +78,7 @@ fn test_postprocessor_stop_and_skip() {
assert!(note_path.exists());
remove_file(&note_path).unwrap();
exporter
.add_postprocessor(&|ctx, mdevents| (ctx, mdevents, PostprocessorResult::StopAndSkipNote));
exporter.add_postprocessor(&|_ctx, _mdevents| PostprocessorResult::StopAndSkipNote);
exporter.run().unwrap();
assert!(!note_path.exists());
@ -93,7 +87,7 @@ fn test_postprocessor_stop_and_skip() {
#[test]
fn test_postprocessor_change_destination() {
let tmp_dir = TempDir::new().expect("failed to make tempdir");
let original_note_path = tmp_dir.path().clone().join(PathBuf::from("Note.md"));
let original_note_path = tmp_dir.path().join(PathBuf::from("Note.md"));
let mut exporter = Exporter::new(
PathBuf::from("tests/testdata/input/postprocessors"),
tmp_dir.path().to_path_buf(),
@ -103,17 +97,49 @@ fn test_postprocessor_change_destination() {
assert!(original_note_path.exists());
remove_file(&original_note_path).unwrap();
exporter.add_postprocessor(&|mut ctx, mdevents| {
exporter.add_postprocessor(&|ctx, _mdevents| {
ctx.destination.set_file_name("MovedNote.md");
(ctx, mdevents, PostprocessorResult::Continue)
PostprocessorResult::Continue
});
exporter.run().unwrap();
let new_note_path = tmp_dir.path().clone().join(PathBuf::from("MovedNote.md"));
let new_note_path = tmp_dir.path().join(PathBuf::from("MovedNote.md"));
assert!(!original_note_path.exists());
assert!(new_note_path.exists());
}
// Ensure postprocessor type definition has proper lifetimes to allow state (here: `parents`)
// to be passed in. Otherwise, this fails with an error like:
// error[E0597]: `parents` does not live long enough
// cast requires that `parents` is borrowed for `'static`
#[test]
fn test_postprocessor_stateful_callback() {
let tmp_dir = TempDir::new().expect("failed to make tempdir");
let mut exporter = Exporter::new(
PathBuf::from("tests/testdata/input/postprocessors"),
tmp_dir.path().to_path_buf(),
);
let parents: Mutex<HashSet<PathBuf>> = Default::default();
let callback = |ctx: &mut Context, _mdevents: &mut MarkdownEvents| -> PostprocessorResult {
parents
.lock()
.unwrap()
.insert(ctx.destination.parent().unwrap().to_path_buf());
PostprocessorResult::Continue
};
exporter.add_postprocessor(&callback);
exporter.run().unwrap();
let expected = tmp_dir.path();
let parents = parents.lock().unwrap();
println!("{:?}", parents);
assert_eq!(1, parents.len());
assert!(parents.contains(expected));
}
// The purpose of this test to verify the `append_frontmatter` postprocessor is called to extend
// the frontmatter, and the `foo_to_bar` postprocessor is called to replace instances of "foo" with
// "bar" (only in the note body).
@ -133,7 +159,7 @@ fn test_embed_postprocessors() {
let expected =
read_to_string("tests/testdata/expected/postprocessors/Note_embed_postprocess_only.md")
.unwrap();
let actual = read_to_string(tmp_dir.path().clone().join(PathBuf::from("Note.md"))).unwrap();
let actual = read_to_string(tmp_dir.path().join(PathBuf::from("Note.md"))).unwrap();
assert_eq!(expected, actual);
}
@ -146,16 +172,14 @@ fn test_embed_postprocessors_stop_and_skip() {
PathBuf::from("tests/testdata/input/postprocessors"),
tmp_dir.path().to_path_buf(),
);
exporter.add_embed_postprocessor(&|ctx, mdevents| {
(ctx, mdevents, PostprocessorResult::StopAndSkipNote)
});
exporter.add_embed_postprocessor(&|_ctx, _mdevents| PostprocessorResult::StopAndSkipNote);
exporter.run().unwrap();
let expected =
read_to_string("tests/testdata/expected/postprocessors/Note_embed_stop_and_skip.md")
.unwrap();
let actual = read_to_string(tmp_dir.path().clone().join(PathBuf::from("Note.md"))).unwrap();
let actual = read_to_string(tmp_dir.path().join(PathBuf::from("Note.md"))).unwrap();
assert_eq!(expected, actual);
}
@ -170,9 +194,9 @@ fn test_embed_postprocessors_context() {
tmp_dir.path().to_path_buf(),
);
exporter.add_postprocessor(&|ctx, mdevents| {
exporter.add_postprocessor(&|ctx, _mdevents| {
if ctx.current_file() != &PathBuf::from("Note.md") {
return (ctx, mdevents, PostprocessorResult::Continue);
return PostprocessorResult::Continue;
}
let is_root_note = ctx
.frontmatter
@ -187,9 +211,9 @@ fn test_embed_postprocessors_context() {
&ctx.current_file().display()
)
}
(ctx, mdevents, PostprocessorResult::Continue)
PostprocessorResult::Continue
});
exporter.add_embed_postprocessor(&|ctx, mdevents| {
exporter.add_embed_postprocessor(&|ctx, _mdevents| {
let is_root_note = ctx
.frontmatter
.get(&Value::String("is_root_note".to_string()))
@ -203,8 +227,66 @@ fn test_embed_postprocessors_context() {
&ctx.current_file().display()
)
}
(ctx, mdevents, PostprocessorResult::Continue)
PostprocessorResult::Continue
});
exporter.run().unwrap();
}
#[test]
fn test_softbreaks_to_hardbreaks() {
let tmp_dir = TempDir::new().expect("failed to make tempdir");
let mut exporter = Exporter::new(
PathBuf::from("tests/testdata/input/postprocessors"),
tmp_dir.path().to_path_buf(),
);
exporter.add_postprocessor(&softbreaks_to_hardbreaks);
exporter.run().unwrap();
let expected =
read_to_string("tests/testdata/expected/postprocessors/hard_linebreaks.md").unwrap();
let actual = read_to_string(tmp_dir.path().join(PathBuf::from("hard_linebreaks.md"))).unwrap();
assert_eq!(expected, actual);
}
#[test]
fn test_filter_by_tags() {
let tmp_dir = TempDir::new().expect("failed to make tempdir");
let mut exporter = Exporter::new(
PathBuf::from("tests/testdata/input/filter-by-tags"),
tmp_dir.path().to_path_buf(),
);
let filter_by_tags = filter_by_tags(
vec!["private".to_string(), "no-export".to_string()],
vec!["export".to_string()],
);
exporter.add_postprocessor(&filter_by_tags);
exporter.run().unwrap();
let walker = WalkDir::new("tests/testdata/expected/filter-by-tags/")
// 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/filter-by-tags",
entry.path().display()
)
});
let actual = read_to_string(tmp_dir.path().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
);
}
}

View File

@ -0,0 +1,7 @@
---
tags:
- export
- me
---
A public note

View File

@ -0,0 +1,6 @@
---
tags:
- export
---
A public note

View File

@ -0,0 +1,17 @@
Obsidian trims space before and after the filename in a wikilink target.
These should all be the same:
[foo](foo.md)
[foo](foo.md)
[foo](foo.md)
[foo](foo.md)
[foo](foo.md)
[foo](foo.md)
[foo](foo.md)
[foo](foo.md)
[foo > ^abc](foo.md#abc)
[foo > ^abc](foo.md#abc)
[foo > ^abc](foo.md#abc)
[foo > ^abc](foo.md#abc)

View File

@ -0,0 +1,18 @@
# Heading 1
Here's a random quote from fortune(6):
"I don't have to take this abuse from you -- I've got hundreds of
people waiting to abuse me."
-- Bill Murray, "Ghostbusters"
## Heading 2
Here's another random quote from fortune(6):
````
Cinemuck, n.:
The combination of popcorn, soda, and melted chocolate which
covers the floors of movie theaters.
-- Rich Hall, "Sniglets"
````

View File

@ -0,0 +1,5 @@
---
tags: [export, me]
---
A public note

View File

@ -0,0 +1,5 @@
---
tags: [export, no-export, private]
---
A private note

View File

@ -0,0 +1,5 @@
---
tags: [export]
---
A public note

View File

@ -0,0 +1 @@
A note without frontmatter should be exported.

View File

@ -0,0 +1,5 @@
---
tags: [no, no-export]
---
A private note

View File

@ -0,0 +1,5 @@
---
title: foo
---
A public note.

View File

@ -0,0 +1,5 @@
---
tags: [private]
---
A private note.

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1728" height="1728" viewBox="0 0 1728 1728"><path d="M1065.6 1468.8c0 21.197-17.203 38.4-38.4 38.4H720c-21.197 0-38.4-17.203-38.4-38.4 0-21.196 17.203-38.4 38.4-38.4h307.2c21.196 0 38.4 17.204 38.4 38.4zm-38.4 57.6H720c-25.268 0-44.832 24.403-36.422 50.918 5.068 15.994 21.254 25.882 38.035 25.882h.575c22.177 0 42.45 12.537 52.378 32.37l.403.808c13.38 26.727 40.703 43.622 70.598 43.622h56.063c29.896 0 57.217-16.896 70.58-43.622l.403-.807c9.927-19.833 30.202-32.37 52.378-32.37h.577c16.78 0 32.966-9.888 38.035-25.882 8.43-26.514-11.135-50.918-36.402-50.918zM873.6 297.6c21.197 0 38.4-17.203 38.4-38.4V105.6c0-21.197-17.203-38.4-38.4-38.4-21.196 0-38.4 17.203-38.4 38.4v153.6c0 21.197 17.203 38.4 38.4 38.4zM466.31 443.808c7.49 7.508 17.32 11.252 27.15 11.252s19.66-3.744 27.147-11.252c14.996-14.995 14.996-39.302 0-54.297L411.993 280.897c-14.976-14.995-39.32-14.995-54.297 0-14.995 14.995-14.995 39.302 0 54.297L466.31 443.808zM374.4 796.8c0-21.196-17.203-38.4-38.4-38.4H182.4c-21.197 0-38.4 17.204-38.4 38.4 0 21.197 17.203 38.4 38.4 38.4H336c21.197 0 38.4-17.203 38.4-38.4zm91.91 352.992l-108.613 108.614c-14.995 14.995-14.995 39.303 0 54.298 7.487 7.507 17.318 11.25 27.148 11.25s19.66-3.743 27.148-11.25l108.614-108.614c14.996-14.995 14.996-39.303 0-54.298-14.975-14.995-39.32-14.995-54.296 0zm814.58 0c-14.995-14.995-39.303-14.995-54.298 0s-14.995 39.303 0 54.298l108.614 108.614c7.508 7.507 17.32 11.25 27.15 11.25s19.64-3.743 27.147-11.25c14.995-14.995 14.995-39.303 0-54.298l-108.613-108.614zM1564.8 758.4h-153.6c-21.197 0-38.4 17.203-38.4 38.4 0 21.196 17.203 38.4 38.4 38.4h153.6c21.197 0 38.4-17.204 38.4-38.4 0-21.196-17.203-38.4-38.4-38.4zm-311.06-303.34c9.83 0 19.643-3.744 27.15-11.252l108.613-108.614c14.995-14.995 14.995-39.302 0-54.297s-39.303-14.995-54.298 0L1226.592 389.51c-14.995 14.996-14.995 39.302 0 54.297 7.488 7.508 17.318 11.253 27.15 11.253zM1065.6 1372.8c0 21.197-17.203 38.4-38.4 38.4H720c-21.197 0-38.4-17.203-38.4-38.4 0-20.448 16.032-37.018 36.192-38.17C693.888 1118.555 470.4 1070.42 470.4 816c0-222.682 180.518-403.2 403.2-403.2s403.2 180.52 403.2 403.2c0 254.42-223.488 302.554-247.393 518.63 20.16 1.152 36.193 17.722 36.193 38.17zM785.107 520.512c-5.972-14.727-22.733-21.81-37.518-15.878C649.46 544.396 575.06 629.396 548.58 732c-3.975 15.418 5.3 31.104 20.698 35.078 2.4.634 4.82.922 7.2.922 12.825 0 24.518-8.62 27.878-21.6 21.927-85.018 83.56-155.443 164.852-188.37 14.745-5.972 21.85-22.754 15.897-37.518z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="1728" height="1728" viewBox="0 0 1728 1728"><path d="M1065.6 1468.8c0 21.197-17.203 38.4-38.4 38.4H720c-21.197 0-38.4-17.203-38.4-38.4 0-21.196 17.203-38.4 38.4-38.4h307.2c21.196 0 38.4 17.204 38.4 38.4zm-38.4 57.6H720c-25.268 0-44.832 24.403-36.422 50.918 5.068 15.994 21.254 25.882 38.035 25.882h.575c22.177 0 42.45 12.537 52.378 32.37l.403.808c13.38 26.727 40.703 43.622 70.598 43.622h56.063c29.896 0 57.217-16.896 70.58-43.622l.403-.807c9.927-19.833 30.202-32.37 52.378-32.37h.577c16.78 0 32.966-9.888 38.035-25.882 8.43-26.514-11.135-50.918-36.402-50.918zM873.6 297.6c21.197 0 38.4-17.203 38.4-38.4V105.6c0-21.197-17.203-38.4-38.4-38.4-21.196 0-38.4 17.203-38.4 38.4v153.6c0 21.197 17.203 38.4 38.4 38.4zM466.31 443.808c7.49 7.508 17.32 11.252 27.15 11.252s19.66-3.744 27.147-11.252c14.996-14.995 14.996-39.302 0-54.297L411.993 280.897c-14.976-14.995-39.32-14.995-54.297 0-14.995 14.995-14.995 39.302 0 54.297L466.31 443.808zM374.4 796.8c0-21.196-17.203-38.4-38.4-38.4H182.4c-21.197 0-38.4 17.204-38.4 38.4 0 21.197 17.203 38.4 38.4 38.4H336c21.197 0 38.4-17.203 38.4-38.4zm91.91 352.992l-108.613 108.614c-14.995 14.995-14.995 39.303 0 54.298 7.487 7.507 17.318 11.25 27.148 11.25s19.66-3.743 27.148-11.25l108.614-108.614c14.996-14.995 14.996-39.303 0-54.298-14.975-14.995-39.32-14.995-54.296 0zm814.58 0c-14.995-14.995-39.303-14.995-54.298 0s-14.995 39.303 0 54.298l108.614 108.614c7.508 7.507 17.32 11.25 27.15 11.25s19.64-3.743 27.147-11.25c14.995-14.995 14.995-39.303 0-54.298l-108.613-108.614zM1564.8 758.4h-153.6c-21.197 0-38.4 17.203-38.4 38.4 0 21.196 17.203 38.4 38.4 38.4h153.6c21.197 0 38.4-17.204 38.4-38.4 0-21.196-17.203-38.4-38.4-38.4zm-311.06-303.34c9.83 0 19.643-3.744 27.15-11.252l108.613-108.614c14.995-14.995 14.995-39.302 0-54.297s-39.303-14.995-54.298 0L1226.592 389.51c-14.995 14.996-14.995 39.302 0 54.297 7.488 7.508 17.318 11.253 27.15 11.253zM1065.6 1372.8c0 21.197-17.203 38.4-38.4 38.4H720c-21.197 0-38.4-17.203-38.4-38.4 0-20.448 16.032-37.018 36.192-38.17C693.888 1118.555 470.4 1070.42 470.4 816c0-222.682 180.518-403.2 403.2-403.2s403.2 180.52 403.2 403.2c0 254.42-223.488 302.554-247.393 518.63 20.16 1.152 36.193 17.722 36.193 38.17zM785.107 520.512c-5.972-14.727-22.733-21.81-37.518-15.878C649.46 544.396 575.06 629.396 548.58 732c-3.975 15.418 5.3 31.104 20.698 35.078 2.4.634 4.82.922 7.2.922 12.825 0 24.518-8.62 27.878-21.6 21.927-85.018 83.56-155.443 164.852-188.37 14.745-5.972 21.85-22.754 15.897-37.518z"/></svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1,17 @@
Obsidian trims space before and after the filename in a wikilink target.
These should all be the same:
[[foo]]
[[ foo]]
[[foo ]]
[[ foo ]]
[[foo|foo]]
[[ foo|foo]]
[[foo |foo]]
[[ foo |foo]]
[[foo#^abc]]
[[foo#^abc ]]
[[foo#^abc |foo > ^abc]]
[[ foo#^abc ]]

View File

@ -4,4 +4,4 @@
## This is a header
This is a block ^dda637
This is a block ^dda637

View File

@ -0,0 +1,18 @@
# Heading 1
Here's a random quote from fortune(6):
"I don't have to take this abuse from you -- I've got hundreds of
people waiting to abuse me."
-- Bill Murray, "Ghostbusters"
## Heading 2
Here's another random quote from fortune(6):
```
Cinemuck, n.:
The combination of popcorn, soda, and melted chocolate which
covers the floors of movie theaters.
-- Rich Hall, "Sniglets"
```

View File

@ -1 +1 @@
Note in dir2.
Note in dir2.