diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fc0b63d..f84f6e8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,10 +33,19 @@ jobs: steps: - name: Checkout uses: actions/checkout@v6 - - name: Install rustfmt - run: rustup component add rustfmt + - name: Install toolchain + run: rustup update stable && rustup default stable - name: Check format - run: rustfmt --verbose --check **/*.rs + run: cargo fmt --check + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + - name: Install toolchain + run: rustup update stable && rustup default stable + - name: Run clippy + run: cargo clippy -- --deny warnings vint: runs-on: ubuntu-latest steps: diff --git a/Cargo.lock b/Cargo.lock index c5aabb6..d2f7610 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,20 +25,20 @@ checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -71,15 +71,15 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.5.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" @@ -117,15 +117,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "content_inspector" @@ -138,9 +138,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -157,27 +157,27 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "dunce" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "either" -version = "1.11.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" @@ -186,7 +186,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -239,9 +239,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "humantime" -version = "2.1.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" [[package]] name = "humantime-serde" @@ -271,15 +271,15 @@ checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "libc" -version = "0.2.177" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libredox" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" dependencies = [ "bitflags", "libc", @@ -303,19 +303,29 @@ dependencies = [ [[package]] name = "maxdown" -version = "0.1.0" +version = "1.0.0" dependencies = [ "anyhow", "clap", "markdown", + "minijinja", "trycmd", ] [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "minijinja" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "12ea9ac0a51fb5112607099560fdf0f90366ab088a2a9e6e8ae176794e9806aa" +dependencies = [ + "serde", +] [[package]] name = "normalize-line-endings" @@ -342,7 +352,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -356,9 +366,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] @@ -371,9 +381,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rayon" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", @@ -381,9 +391,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -391,24 +401,24 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.18" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +checksum = "ec96166dafa0886eb81fe1c0a388bece180fbef2135f97c1e2cf8302e74b43b5" dependencies = [ "bitflags", ] [[package]] name = "rustix" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -452,9 +462,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" dependencies = [ "serde_core", ] @@ -529,23 +539,23 @@ dependencies = [ "getrandom", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "toml_datetime" -version = "0.7.3" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.23.7" +version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ "indexmap", "serde_core", @@ -558,18 +568,18 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "trycmd" @@ -591,15 +601,15 @@ dependencies = [ [[package]] name = "unicode-id" -version = "0.3.4" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1b6def86329695390197b82c1e244a54a131ceb66c996f2088a3876e2ae083f" +checksum = "70ba288e709927c043cbe476718d37be306be53fb1fafecd0dbe36d072be2580" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "utf8parse" @@ -637,11 +647,11 @@ dependencies = [ [[package]] name = "winapi-util" -version = "0.1.8" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -650,31 +660,13 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-sys" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.5", + "windows-targets", ] [[package]] @@ -686,22 +678,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - [[package]] name = "windows-targets" version = "0.53.5" @@ -709,106 +685,58 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - [[package]] name = "windows_aarch64_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - [[package]] name = "windows_aarch64_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - [[package]] name = "windows_i686_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - [[package]] name = "windows_i686_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - [[package]] name = "windows_i686_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - [[package]] name = "windows_x86_64_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - [[package]] name = "windows_x86_64_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - [[package]] name = "windows_x86_64_msvc" version = "0.53.1" diff --git a/Cargo.toml b/Cargo.toml index 5369081..fd55fe3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "maxdown" -version = "0.1.0" -edition = "2021" +version = "1.0.0" +edition = "2024" # See more keys and their definitions at # https://doc.rust-lang.org/cargo/reference/manifest.html @@ -9,7 +9,8 @@ edition = "2021" [dependencies] anyhow = { version = "1.0.100", features = ["std"] } clap = { version = "4.5.53", features = ["derive"] } -markdown = "1.0.0" +markdown = { version = "1.0.0", features = [], default-features = false } +minijinja = { version = "2.14.0", features = ["debug", "serde"], default-features = false } [dev-dependencies] trycmd = "0.15.11" diff --git a/README.md b/README.md index 47d962e..5ace736 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ The `maxdown` command-line tool reads Markdown from `stdin` or a file and output For instance: ```sh -echo 'Hello *World*!' | maxdown - +echo 'Hello *World*!' | maxdown maxdown README.md ``` @@ -77,16 +77,16 @@ To learn about the extra knobs and switches, kindly ask `maxdown` for help: $ maxdown --help Convert Markdown to HTML -Usage: maxdown [OPTIONS] +Usage: maxdown [OPTIONS] [PATH] Arguments: - Path to the input markdown file or "-" for stdin + [PATH] Path to the input Markdown file [default: stdin] Options: -b, --base Base URL to use for all relative URLs in the document --dangerous Only use this if you trust the authors of the document -o, --output File to write output to [default: stdout] - -t, --template Template to use for output + -t, --template Template to use for output [default: built-in template] --title Title to pass to the template [default: Preview] -h, --help Print help -V, --version Print version @@ -121,6 +121,7 @@ Plug 'pmeinhardt/ql', {'do': ':QuickLookCompile'} Maxdown is built on top of the impressive work of other people: - markdown-rs: https://github.com/wooorm/markdown-rs +- minijinja: https://github.com/mitsuhiko/minijinja - clap: https://github.com/clap-rs/clap - trycmd: https://github.com/assert-rs/trycmd - github-markdown-css: https://github.com/sindresorhus/github-markdown-css diff --git a/examples/basic.md b/examples/basic.md index a7904e0..0ed849a 100644 --- a/examples/basic.md +++ b/examples/basic.md @@ -17,7 +17,6 @@ $ maxdown input.md </body> </html> - ``` Convert Markdown with custom document title: @@ -37,7 +36,6 @@ $ maxdown --title "Pizzazz" input.md </body> </html> - ``` Convert Markdown with custom base URL: @@ -57,7 +55,6 @@ $ maxdown --base "https://github.com/pmeinhardt/maxdown" input.md </body> </html> - ``` Convert Markdown with custom template: @@ -71,5 +68,4 @@ $ maxdown --template template.html input.md <body><p>Hello <strong>Markdown</strong>!</p></body> </html> - ``` diff --git a/examples/error.md b/examples/error.md index 941662d..6d819b8 100644 --- a/examples/error.md +++ b/examples/error.md @@ -24,12 +24,12 @@ Caused by: ``` -Print error when output file cannot be written: +Print error when output file cannot be opened: ```console $ maxdown --output enoent/output.html input.md ? failed -Error: Failed to write output to "enoent/output.html" +Error: Failed to open output file "enoent/output.html" Caused by: No such file or directory (os error 2) diff --git a/examples/help.md b/examples/help.md index 5987a76..e3aca88 100644 --- a/examples/help.md +++ b/examples/help.md @@ -1,45 +1,45 @@ # Help -Print help when invoked without arguments: +Print help when invoked with `--help`: ```console -$ maxdown -? failed +$ maxdown --help +? success Convert Markdown to HTML -Usage: maxdown [OPTIONS] <PATH> +Usage: maxdown [OPTIONS] [PATH] Arguments: - <PATH> Path to the input Markdown file or "-" for stdin + [PATH] Path to the input Markdown file [default: stdin] Options: -b, --base <url> Base URL to use for all relative URLs in the document --dangerous Only use this if you trust the authors of the document -o, --output <path> File to write output to [default: stdout] - -t, --template <path> Template to use for output + -t, --template <path> Template to use for output [default: built-in template] --title <title> Title to pass to the template [default: Preview] -h, --help Print help -V, --version Print version ``` -Print help when invoked with `--help`: +Print help when invoked with `-h`: ```console -$ maxdown --help +$ maxdown -h ? success Convert Markdown to HTML -Usage: maxdown [OPTIONS] <PATH> +Usage: maxdown [OPTIONS] [PATH] Arguments: - <PATH> Path to the input Markdown file or "-" for stdin + [PATH] Path to the input Markdown file [default: stdin] Options: -b, --base <url> Base URL to use for all relative URLs in the document --dangerous Only use this if you trust the authors of the document -o, --output <path> File to write output to [default: stdout] - -t, --template <path> Template to use for output + -t, --template <path> Template to use for output [default: built-in template] --title <title> Title to pass to the template [default: Preview] -h, --help Print help -V, --version Print version diff --git a/examples/read-stdin.stdout b/examples/read-stdin.stdout index 5f64322..8d8bdca 100644 --- a/examples/read-stdin.stdout +++ b/examples/read-stdin.stdout @@ -7,4 +7,3 @@ <p>Hello I/O!</p> </body> </html> - diff --git a/examples/read-stdin.toml b/examples/read-stdin.toml index 4c7fe88..b1e05cd 100644 --- a/examples/read-stdin.toml +++ b/examples/read-stdin.toml @@ -1,2 +1,2 @@ bin.name = "maxdown" -args = ["-"] +args = [] diff --git a/plugin/maxdown.vim b/plugin/maxdown.vim index 11def29..4f1db1b 100644 --- a/plugin/maxdown.vim +++ b/plugin/maxdown.vim @@ -31,7 +31,6 @@ function! s:convert() abort let args = [ \ '--dangerous', \ '--title', shellescape(expand('%:t')), - \ '-', \ ] execute '%!' . join([cmd] + args) @@ -45,7 +44,6 @@ function! s:invoke(dest, source, bnum) abort \ '--base', shellescape(a:source), \ '--output', shellescape(a:dest), \ '--template', shellescape(s:template), - \ '-' \ ] call s:exec(join([cmd] + args), a:bnum) diff --git a/src/io.rs b/src/io.rs deleted file mode 100644 index c3ecf77..0000000 --- a/src/io.rs +++ /dev/null @@ -1,17 +0,0 @@ -use std::fs; -use std::io; -use std::path::Path; - -/// Reads content from a file or stdin if the path is "-" -pub fn read(path: &Path) -> Result<String, io::Error> { - if path == Path::new("-") { - return io::read_to_string(io::stdin()); - } - - fs::read_to_string(path) -} - -/// Writes content to a file -pub fn write(path: &Path, content: &str) -> Result<(), io::Error> { - fs::write(path, content) -} diff --git a/src/main.rs b/src/main.rs index 7c57da4..f565d49 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,23 +1,23 @@ -use std::collections::HashMap; +use std::fs::{self, File}; +use std::io::{self, BufWriter, Write}; use std::path::PathBuf; -use anyhow::{anyhow, Context, Result}; +use anyhow::{Context, Result, anyhow}; use clap::Parser; +use minijinja::{Environment, context}; -mod io; +#[allow(clippy::style)] mod markdown; -mod template; -// The default HTML template embedded in the binary as a string +/// The default HTML template embedded into the binary as a string. const DEFAULT_TEMPLATE: &str = include_str!("../templates/default-template.html"); /// Convert Markdown to HTML #[derive(Parser, Debug)] -#[command(arg_required_else_help(true))] #[command(version)] struct Args { - /// Path to the input Markdown file or "-" for stdin - path: PathBuf, + /// Path to the input Markdown file [default: stdin] + path: Option<PathBuf>, /// Base URL to use for all relative URLs in the document #[arg(short, long, value_name = "url")] @@ -31,7 +31,7 @@ struct Args { #[arg(short, long, value_name = "path")] output: Option<PathBuf>, - /// Template to use for output + /// Template to use for output [default: built-in template] #[arg(short, long, value_name = "path")] template: Option<PathBuf>, @@ -42,45 +42,56 @@ struct Args { /// Main function to handle command-line arguments and orchestrate the Markdown-to-HTML conversion fn main() -> Result<()> { - // Parse command-line arguments + // Parse command-line arguments. let args = Args::parse(); - // Read the Markdown input from a file or stdin - let input = io::read(&args.path) - .with_context(|| format!("Failed to read input from {:?}", args.path))?; - - // Convert the Markdown input to HTML - let html = markdown::convert(&input, args.dangerous).map_err(|m| anyhow!(m))?; + // Read the Markdown input from a file or from stdin. + let source = match args.path { + Some(ref path) => fs::read_to_string(path) + .with_context(|| format!("Failed to read input from {:?}", path))?, + None => io::read_to_string(io::stdin()).context("Failed to read from stdin")?, + }; - // Prepare values for template rendering - let values: HashMap<String, String> = HashMap::from([ - ("base".to_string(), args.base.unwrap_or_default()), - ("title".to_string(), args.title), - ("content".to_string(), html.trim().to_string()), - ]); + // Convert the Markdown input to HTML. + let content = markdown::convert(&source, args.dangerous).map_err(|m| anyhow!(m))?; - // Read the custom template if provided, or use the default template + // Read the custom template, if provided, or use the default template. let template = match args.template { - Some(path) => { - io::read(&path).with_context(|| format!("Failed to read template from {:?}", path))? - } + Some(ref path) => fs::read_to_string(path) + .with_context(|| format!("Failed to read template from {:?}", path))?, None => DEFAULT_TEMPLATE.to_string(), }; - // Render the final HTML by replacing placeholders in the template - let result = template::render(&template, &values); + // Generate the final HTML output by rendering the template. + let mut env = Environment::empty(); + env.set_keep_trailing_newline(true); + + let ctx = context! { + base => args.base.unwrap_or_default(), + content => content.trim(), + title => args.title, + }; + + let result = env + .render_str(&template, ctx) + .context("Failed to render template")?; - // Write the output to a file or stdout - // Use match expression to handle the output path - match args.output { - Some(path) => { - io::write(&path, &result) - .with_context(|| format!("Failed to write output to {:?}", path))?; + // Direct output to a file, if a path was provided, or to stdout otherwise. + let mut out: Box<dyn Write> = match args.output { + Some(ref path) => { + let file = File::create(path) + .with_context(|| format!("Failed to open output file {:?}", path))?; + Box::new(BufWriter::new(file)) } None => { - println!("{}", result); + let stream = io::stdout(); + Box::new(BufWriter::new(stream)) } - } + }; + + // Write result to the output destination. + write!(out, "{}", result).context("Failed to write output")?; + out.flush().context("Failed to flush output")?; Ok(()) } diff --git a/src/markdown.rs b/src/markdown.rs index eac4e10..28d3e93 100644 --- a/src/markdown.rs +++ b/src/markdown.rs @@ -12,5 +12,5 @@ pub fn convert(input: &str, dangerous: bool) -> Result<String, Message> { ..markdown::Options::gfm() }; - markdown::to_html_with_options(input, &options) + markdown::to_html_with_options(input, options) } diff --git a/src/template.rs b/src/template.rs deleted file mode 100644 index 96538bf..0000000 --- a/src/template.rs +++ /dev/null @@ -1,194 +0,0 @@ -use std::collections::HashMap; -use std::ops::Range; - -// Define the delimiters for template placeholders -const OPEN: &str = "{{"; -const CLOSE: &str = "}}"; -const LEN: usize = 2; - -/// Represents an instruction for template rendering -enum Instruction { - Copy(Range<usize>), // Copy a span of text from the source - Replace(Range<usize>, String), // Replace a span of text with a value -} - -/// Represents a simple string template with placeholders -struct Template { - program: Vec<Instruction>, - source: String, -} - -impl Template { - /// Creates a new template from a string - pub fn new(source: String) -> Self { - let len = source.len(); - - let mut program = Vec::new(); - let mut offset = 0; - - while let Some(open) = source[offset..].find(OPEN) { - let start = offset + open; - - if let Some(close) = source[start..].find(CLOSE) { - let end = start + close + LEN; - - // Copy the text before the placeholder - if start > offset { - program.push(Instruction::Copy(offset..start)); - } - - // Add a replace instruction for the placeholder - let inner = &source[start + LEN..end - LEN]; - let key = inner.trim().to_string(); - program.push(Instruction::Replace(start..end, key)); - - // Move the start position past the end of the placeholder - offset = end; - } else { - // If no closing delimiter is found, treat the rest as regular text - program.push(Instruction::Copy(offset..len)); - offset = len; - break; - } - } - - if offset < len { - // Copy any text remaining after the last placeholder - program.push(Instruction::Copy(offset..len)); - } - - Template { program, source } - } - - /// Renders the template with the provided values - pub fn render(&self, values: &HashMap<String, String>) -> String { - let mut result = String::new(); - - for instruction in &self.program { - match instruction { - Instruction::Copy(range) => { - // Copy the text from the source within the specified range - result.push_str(&self.source[range.start..range.end]); - } - Instruction::Replace(range, key) => { - // Replace the placeholder with the corresponding value if it exists, - // otherwise keep the placeholder as is - match values.get(key) { - Some(value) => result.push_str(value), - None => result.push_str(&self.source[range.start..range.end]), - } - } - } - } - - result - } -} - -impl From<String> for Template { - fn from(source: String) -> Self { - Template::new(source) - } -} - -impl From<&str> for Template { - fn from(source: &str) -> Self { - Template::new(source.to_string()) - } -} - -/// Renders a template with the provided values and returns the result as a string -pub fn render(template: &str, values: &HashMap<String, String>) -> String { - let template = Template::from(template); - template.render(values) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_template_rendering() { - let template = Template::from("Hello, {{name}}! Welcome to {{place}}."); - let values = HashMap::from([ - ("name".to_string(), "Alice".to_string()), - ("place".to_string(), "Wonderland".to_string()), - ]); - - let output = template.render(&values); - - assert_eq!(output, "Hello, Alice! Welcome to Wonderland."); - } - - #[test] - fn test_template_with_whitespace_placeholders() { - let template = Template::from("Hello, {{ name }}! Welcome to {{ place }}."); - let values = HashMap::from([ - ("name".to_string(), "Alice".to_string()), - ("place".to_string(), "Wonderland".to_string()), - ]); - - let output = template.render(&values); - - assert_eq!(output, "Hello, Alice! Welcome to Wonderland."); - } - - #[test] - fn test_template_rendering_with_missing_keys() { - let template = Template::from("Hello, {{name}}! Welcome to {{place}}."); - let values = HashMap::from([ - ("name".to_string(), "Alice".to_string()), - // "place" is missing - ]); - - let output = template.render(&values); - - assert_eq!(output, "Hello, Alice! Welcome to {{place}}."); - } - - #[test] - fn test_template_with_empty_placeholders() { - let template = Template::from("Hello, {{}}! Welcome to {{place}}."); - let values = HashMap::from([ - ("".to_string(), "Alice".to_string()), - ("place".to_string(), "Wonderland".to_string()), - ]); - - let output = template.render(&values); - - assert_eq!(output, "Hello, Alice! Welcome to Wonderland."); - } - - #[test] - fn test_template_with_unicode_characters() { - let template = Template::from("Hello, {{ 🪪 }}! Welcome to {{ 📍 }}. 👋"); - let values = HashMap::from([ - ("🪪".to_string(), "👾".to_string()), - ("📍".to_string(), "🌍".to_string()), - ]); - - let output = template.render(&values); - - assert_eq!(output, "Hello, 👾! Welcome to 🌍. 👋"); - } - - #[test] - fn test_template_without_placeholders() { - let template = Template::from("Just a simple string."); - let values = HashMap::from([]); - - let output = template.render(&values); - - assert_eq!(output, "Just a simple string."); - } - - #[test] - fn test_empty_template() { - let template = Template::from(""); - let values = HashMap::from([]); - - let output = template.render(&values); - - assert_eq!(output, ""); - } -}