From 1467c23fc6d4ce4e6fb5d23743cfcb36dd2a358c Mon Sep 17 00:00:00 2001 From: tison Date: Tue, 28 Jan 2025 11:35:15 +0800 Subject: [PATCH] feat: render by minijnja and support git authors (#169) This closes #168 and closes #167. Signed-off-by: tison --- CHANGELOG.md | 24 +++ Cargo.lock | 198 +++++++++++++------------ Cargo.toml | 2 +- README.md | 14 +- cli/Cargo.toml | 2 +- cli/src/subcommand.rs | 4 +- fmt/Cargo.toml | 3 +- fmt/src/config/mod.rs | 9 +- fmt/src/document/factory.rs | 44 +++--- fmt/src/document/mod.rs | 55 ++++--- fmt/src/git.rs | 17 ++- fmt/src/header/model.rs | 4 +- fmt/src/license/Apache-2.0.txt | 2 +- fmt/src/license/Elastic-2.0.txt | 2 +- fmt/src/processor.rs | 22 ++- fmt/tests/tests.rs | 4 +- licenserc.toml | 5 +- tests/attrs_and_props/license.txt | 6 + tests/attrs_and_props/licenserc.toml | 12 ++ tests/attrs_and_props/main.rs | 3 + tests/attrs_and_props/main.rs.expected | 10 ++ tests/bom_issue/licenserc.toml | 2 +- tests/bom_issue/style.toml | 2 +- tests/it.py | 9 +- tests/load_header_path/license.txt | 2 +- 25 files changed, 284 insertions(+), 173 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 tests/attrs_and_props/license.txt create mode 100644 tests/attrs_and_props/licenserc.toml create mode 100644 tests/attrs_and_props/main.rs create mode 100644 tests/attrs_and_props/main.rs.expected diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..1e6746cf --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,24 @@ +# CHANGELOG + +All notable changes to this project will be documented in this file. + +## [6.0.0] 2025-01-28 + +### Breaking changes + +Now, HawkEye uses MiniJinja as the template engine. + +All the `properties` configured will be passed to the template engine as the `props` value, and thus: + +* Previous `${property}` should be replaced with `{{ props["property"] }}`. +* Previous built-in variables `hawkeye.core.filename` is now `attrs.filename`. +* Previous built-in variables `hawkeye.git.fileCreatedYear` is now `attrs.git_file_created_year`. +* Previous built-in variables `hawkeye.git.fileModifiedYear` is now `attrs.git_file_modified_year`. + +New properties: + +* `attrs.git_authors` is a collection of authors of the file. You can join them with `, ` to get a string by `{{ attrs.git_authors | join(", ") }}`. + +### Notable changes + +Now, HawkEye would detect a leading BOM (Byte Order Mark) and remove it if it exists (#166). I tend to treat this as a bug fix, but it may affect the output of the header. diff --git a/Cargo.lock b/Cargo.lock index 4b04bbdb..0675b818 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -449,9 +449,9 @@ dependencies = [ [[package]] name = "git2" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724" +checksum = "3fda788993cc341f69012feba8bf45c0ba4f3291fcc08e214b4d5a7332d88aff" dependencies = [ "bitflags", "libc", @@ -462,9 +462,9 @@ dependencies = [ [[package]] name = "gix" -version = "0.69.1" +version = "0.70.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d0eebdaecdcf405d5433a36f85e4f058cf4de48ee2604388be0dbccbaad353e" +checksum = "736f14636705f3a56ea52b553e67282519418d9a35bb1e90b3a9637a00296b68" dependencies = [ "gix-actor", "gix-attributes", @@ -510,9 +510,9 @@ dependencies = [ [[package]] name = "gix-actor" -version = "0.33.1" +version = "0.33.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b24171f514cef7bb4dfb72a0b06dacf609b33ba8ad2489d4c4559a03b7afb3" +checksum = "20018a1a6332e065f1fcc8305c1c932c6b8c9985edea2284b3c79dc6fa3ee4b2" dependencies = [ "bstr", "gix-date", @@ -524,9 +524,9 @@ dependencies = [ [[package]] name = "gix-attributes" -version = "0.23.1" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddf9bf852194c0edfe699a2d36422d2c1f28f73b7c6d446c3f0ccd3ba232cadc" +checksum = "f151000bf662ef5f641eca6102d942ee31ace80f271a3ef642e99776ce6ddb38" dependencies = [ "bstr", "gix-glob", @@ -541,27 +541,27 @@ dependencies = [ [[package]] name = "gix-bitmap" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d48b897b4bbc881aea994b4a5bbb340a04979d7be9089791304e04a9fbc66b53" +checksum = "b1db9765c69502650da68f0804e3dc2b5f8ccc6a2d104ca6c85bc40700d37540" dependencies = [ "thiserror", ] [[package]] name = "gix-chunk" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6ffbeb3a5c0b8b84c3fe4133a6f8c82fa962f4caefe8d0762eced025d3eb4f7" +checksum = "0b1f1d8764958699dc764e3f727cef280ff4d1bd92c107bbf8acd85b30c1bd6f" dependencies = [ "thiserror", ] [[package]] name = "gix-command" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9405c0a56e17f8365a46870cd2c7db71323ecc8bda04b50cb746ea37bd091e90" +checksum = "cb410b84d6575db45e62025a9118bdbf4d4b099ce7575a76161e898d9ca98df1" dependencies = [ "bstr", "gix-path", @@ -571,9 +571,9 @@ dependencies = [ [[package]] name = "gix-commitgraph" -version = "0.25.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8da6591a7868fb2b6dabddea6b09988b0b05e0213f938dbaa11a03dd7a48d85" +checksum = "e23a8ec2d8a16026a10dafdb6ed51bcfd08f5d97f20fa52e200bc50cb72e4877" dependencies = [ "bstr", "gix-chunk", @@ -585,9 +585,9 @@ dependencies = [ [[package]] name = "gix-config" -version = "0.42.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6649b406ca1f99cb148959cf00468b231f07950f8ec438cc0903cda563606f19" +checksum = "377c1efd2014d5d469e0b3cd2952c8097bce9828f634e04d5665383249f1d9e9" dependencies = [ "bstr", "gix-config-value", @@ -606,9 +606,9 @@ dependencies = [ [[package]] name = "gix-config-value" -version = "0.14.10" +version = "0.14.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49aaeef5d98390a3bcf9dbc6440b520b793d1bf3ed99317dc407b02be995b28e" +checksum = "11365144ef93082f3403471dbaa94cfe4b5e72743bdb9560719a251d439f4cee" dependencies = [ "bitflags", "bstr", @@ -631,9 +631,9 @@ dependencies = [ [[package]] name = "gix-diff" -version = "0.49.0" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8e92566eccbca205a0a0f96ffb0327c061e85bc5c95abbcddfe177498aa04f6" +checksum = "62afb7f4ca0acdf4e9dad92065b2eb1bf2993bcc5014b57bc796e3a365b17c4d" dependencies = [ "bstr", "gix-command", @@ -652,9 +652,9 @@ dependencies = [ [[package]] name = "gix-discover" -version = "0.37.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83bf6dfa4e266a4a9becb4d18fc801f92c3f7cc6c433dd86fdadbcf315ffb6ef" +checksum = "d0c2414bdf04064e0f5a5aa029dfda1e663cf9a6c4bfc8759f2d369299bb65d8" dependencies = [ "bstr", "dunce", @@ -668,9 +668,9 @@ dependencies = [ [[package]] name = "gix-features" -version = "0.39.1" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d85d673f2e022a340dba4713bed77ef2cf4cd737d2f3e0f159d45e0935fd81f" +checksum = "8bfdd4838a8d42bd482c9f0cb526411d003ee94cc7c7b08afe5007329c71d554" dependencies = [ "crc32fast", "flate2", @@ -687,9 +687,9 @@ dependencies = [ [[package]] name = "gix-filter" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0ecdee5667f840ba20c7fe56d63f8e1dc1e6b3bfd296151fe5ef07c874790a" +checksum = "bdcc36cd7dbc63ed0ec3558645886553d1afd3cd09daa5efb9cba9cceb942bbb" dependencies = [ "bstr", "encoding_rs", @@ -708,9 +708,9 @@ dependencies = [ [[package]] name = "gix-fs" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3d4fac505a621f97e5ce2c69fdc425742af00c0920363ca4074f0eb48b1db9" +checksum = "182e7fa7bfdf44ffb7cfe7451b373cdf1e00870ac9a488a49587a110c562063d" dependencies = [ "fastrand", "gix-features", @@ -719,9 +719,9 @@ dependencies = [ [[package]] name = "gix-glob" -version = "0.17.1" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaf69a6bec0a3581567484bf99a4003afcaf6c469fd4214352517ea355cf3435" +checksum = "4e9c7249fa0a78f9b363aa58323db71e0a6161fd69860ed6f48dedf0ef3a314e" dependencies = [ "bitflags", "bstr", @@ -731,9 +731,9 @@ dependencies = [ [[package]] name = "gix-hash" -version = "0.15.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5eccc17194ed0e67d49285e4853307e4147e95407f91c1c3e4a13ba9f4e4ce" +checksum = "e81c5ec48649b1821b3ed066a44efb95f1a268b35c1d91295e61252539fbe9f8" dependencies = [ "faster-hex", "thiserror", @@ -741,9 +741,9 @@ dependencies = [ [[package]] name = "gix-hashtable" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ef65b256631078ef733bc5530c4e6b1c2e7d5c2830b75d4e9034ab3997d18fe" +checksum = "189130bc372accd02e0520dc5ab1cef318dcc2bc829b76ab8d84bbe90ac212d1" dependencies = [ "gix-hash", "hashbrown 0.14.5", @@ -752,9 +752,9 @@ dependencies = [ [[package]] name = "gix-ignore" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b1fb24d2a4af0aa7438e2771d60c14a80cf2c9bd55c29cf1712b841f05bb8a" +checksum = "4f529dcb80bf9855c0a7c49f0ac588df6d6952d63a63fefc254b9c869d2cdf6f" dependencies = [ "bstr", "gix-glob", @@ -765,9 +765,9 @@ dependencies = [ [[package]] name = "gix-index" -version = "0.37.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "270645fd20556b64c8ffa1540d921b281e6994413a0ca068596f97e9367a257a" +checksum = "acd12e3626879369310fffe2ac61acc828613ef656b50c4ea984dd59d7dc85d8" dependencies = [ "bitflags", "bstr", @@ -793,9 +793,9 @@ dependencies = [ [[package]] name = "gix-lock" -version = "15.0.1" +version = "16.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd3ab68a452db63d9f3ebdacb10f30dba1fa0d31ac64f4203d395ed1102d940" +checksum = "9739815270ff6940968441824d162df9433db19211ca9ba8c3fc1b50b849c642" dependencies = [ "gix-tempfile", "gix-utils", @@ -804,9 +804,9 @@ dependencies = [ [[package]] name = "gix-object" -version = "0.46.1" +version = "0.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e42d58010183ef033f31088479b4eb92b44fe341b35b62d39eb8b185573d77ea" +checksum = "ddc4b3a0044244f0fe22347fb7a79cca165e37829d668b41b85ff46a43e5fd68" dependencies = [ "bstr", "gix-actor", @@ -825,9 +825,9 @@ dependencies = [ [[package]] name = "gix-odb" -version = "0.66.0" +version = "0.67.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb780eceb3372ee204469478de02eaa34f6ba98247df0186337e0333de97d0ae" +checksum = "3e93457df69cd09573608ce9fa4f443fbd84bc8d15d8d83adecd471058459c1b" dependencies = [ "arc-swap", "gix-date", @@ -846,9 +846,9 @@ dependencies = [ [[package]] name = "gix-pack" -version = "0.56.0" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4158928929be29cae7ab97afc8e820a932071a7f39d8ba388eed2380c12c566c" +checksum = "fc13a475b3db735617017fb35f816079bf503765312d4b1913b18cf96f3fa515" dependencies = [ "clru", "gix-chunk", @@ -864,9 +864,9 @@ dependencies = [ [[package]] name = "gix-packetline" -version = "0.18.2" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "911aeea8b2dabeed2f775af9906152a1f0109787074daf9e64224e3892dde453" +checksum = "c7e5ae6bc3ac160a6bf44a55f5537813ca3ddb08549c0fd3e7ef699c73c439cd" dependencies = [ "bstr", "faster-hex", @@ -876,9 +876,9 @@ dependencies = [ [[package]] name = "gix-packetline-blocking" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce9004ce1bc00fd538b11c1ec8141a1558fb3af3d2b7ac1ac5c41881f9e42d2a" +checksum = "c1cbf8767c6abd5a6779f586702b5bcd8702380f4208219449cf1c9d0cd1e17c" dependencies = [ "bstr", "faster-hex", @@ -888,9 +888,9 @@ dependencies = [ [[package]] name = "gix-path" -version = "0.10.13" +version = "0.10.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc292ef1a51e340aeb0e720800338c805975724c1dfbd243185452efd8645b7" +checksum = "c40f12bb65a8299be0cfb90fe718e3be236b7a94b434877012980863a883a99f" dependencies = [ "bstr", "gix-trace", @@ -901,9 +901,9 @@ dependencies = [ [[package]] name = "gix-pathspec" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c472dfbe4a4e96fcf7efddcd4771c9037bb4fdea2faaabf2f4888210c75b81e" +checksum = "6430d3a686c08e9d59019806faa78c17315fe22ae73151a452195857ca02f86c" dependencies = [ "bitflags", "bstr", @@ -916,9 +916,9 @@ dependencies = [ [[package]] name = "gix-protocol" -version = "0.47.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c84642e8b6fed7035ce9cc449593019c55b0ec1af7a5dce1ab8a0636eaaeb067" +checksum = "6c61bd61afc6b67d213241e2100394c164be421e3f7228d3521b04f48ca5ba90" dependencies = [ "bstr", "gix-date", @@ -935,9 +935,9 @@ dependencies = [ [[package]] name = "gix-quote" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a1e282216ec2ab2816cd57e6ed88f8009e634aec47562883c05ac8a7009a63" +checksum = "e49357fccdb0c85c0d3a3292a9f6db32d9b3535959b5471bb9624908f4a066c6" dependencies = [ "bstr", "gix-utils", @@ -946,9 +946,9 @@ dependencies = [ [[package]] name = "gix-ref" -version = "0.49.1" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a91b61776c839d0f1b7114901179afb0947aa7f4d30793ca1c56d335dfef485f" +checksum = "47adf4c5f933429f8554e95d0d92eee583cfe4b95d2bf665cd6fd4a1531ee20c" dependencies = [ "gix-actor", "gix-features", @@ -967,9 +967,9 @@ dependencies = [ [[package]] name = "gix-refspec" -version = "0.27.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00c056bb747868c7eb0aeb352c9f9181ab8ca3d0a2550f16470803500c6c413d" +checksum = "59650228d8f612f68e7f7a25f517fcf386c5d0d39826085492e94766858b0a90" dependencies = [ "bstr", "gix-hash", @@ -981,9 +981,9 @@ dependencies = [ [[package]] name = "gix-revision" -version = "0.31.1" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e1ddc474405a68d2ce8485705dd72fe6ce959f2f5fe718601ead5da2c8f9e7" +checksum = "3fe28bbccca55da6d66e6c6efc6bb4003c29d407afd8178380293729733e6b53" dependencies = [ "bstr", "gix-commitgraph", @@ -996,9 +996,9 @@ dependencies = [ [[package]] name = "gix-revwalk" -version = "0.17.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "510026fc32f456f8f067d8f37c34088b97a36b2229d88a6a5023ef179fcb109d" +checksum = "d4ecb80c235b1e9ef2b99b23a81ea50dd569a88a9eb767179793269e0e616247" dependencies = [ "gix-commitgraph", "gix-date", @@ -1011,9 +1011,9 @@ dependencies = [ [[package]] name = "gix-sec" -version = "0.10.10" +version = "0.10.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8b876ef997a955397809a2ec398d6a45b7a55b4918f2446344330f778d14fd6" +checksum = "d84dae13271f4313f8d60a166bf27e54c968c7c33e2ffd31c48cafe5da649875" dependencies = [ "bitflags", "gix-path", @@ -1023,9 +1023,9 @@ dependencies = [ [[package]] name = "gix-shallow" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2673242e87492cb6ff671f0c01f689061ca306c4020f137197f3abc84ce01" +checksum = "ab72543011e303e52733c85bef784603ef39632ddf47f69723def52825e35066" dependencies = [ "bstr", "gix-hash", @@ -1035,9 +1035,9 @@ dependencies = [ [[package]] name = "gix-submodule" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2455f8c0fcb6ebe2a6e83c8f522d30615d763eb2ef7a23c7d929f9476e89f5c" +checksum = "74972fe8d46ac8a09490ae1e843b4caf221c5b157c5ac17057e8e1c38417a3ac" dependencies = [ "bstr", "gix-config", @@ -1050,9 +1050,9 @@ dependencies = [ [[package]] name = "gix-tempfile" -version = "15.0.0" +version = "16.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2feb86ef094cc77a4a9a5afbfe5de626897351bbbd0de3cb9314baf3049adb82" +checksum = "2558f423945ef24a8328c55d1fd6db06b8376b0e7013b1bb476cc4ffdf678501" dependencies = [ "dashmap", "gix-fs", @@ -1064,15 +1064,15 @@ dependencies = [ [[package]] name = "gix-trace" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04bdde120c29f1fc23a24d3e115aeeea3d60d8e65bab92cc5f9d90d9302eb952" +checksum = "7c396a2036920c69695f760a65e7f2677267ccf483f25046977d87e4cb2665f7" [[package]] name = "gix-transport" -version = "0.44.0" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd04d91e507a8713cfa2318d5a85d75b36e53a40379cc7eb7634ce400ecacbaf" +checksum = "11187418489477b1b5b862ae1aedbbac77e582f2c4b0ef54280f20cfe5b964d9" dependencies = [ "bstr", "gix-command", @@ -1086,9 +1086,9 @@ dependencies = [ [[package]] name = "gix-traverse" -version = "0.43.1" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed47d648619e23e93f971d2bba0d10c1100e54ef95d2981d609907a8cabac89" +checksum = "2bec70e53896586ef32a3efa7e4427b67308531ed186bb6120fb3eca0f0d61b4" dependencies = [ "bitflags", "gix-commitgraph", @@ -1103,9 +1103,9 @@ dependencies = [ [[package]] name = "gix-url" -version = "0.28.2" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d096fb733ba6bd3f5403dba8bd72bdd8809fe2b347b57844040b8f49c93492d9" +checksum = "29218c768b53dd8f116045d87fec05b294c731a4b2bdd257eeca2084cc150b13" dependencies = [ "bstr", "gix-features", @@ -1117,9 +1117,9 @@ dependencies = [ [[package]] name = "gix-utils" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba427e3e9599508ed98a6ddf8ed05493db114564e338e41f6a996d2e4790335f" +checksum = "ff08f24e03ac8916c478c8419d7d3c33393da9bb41fa4c24455d5406aeefd35f" dependencies = [ "fastrand", "unicode-normalization", @@ -1127,9 +1127,9 @@ dependencies = [ [[package]] name = "gix-validate" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd520d09f9f585b34b32aba1d0b36ada89ab7fefb54a8ca3fe37fc482a750937" +checksum = "9eaa01c3337d885617c0a42e92823922a2aea71f4caeace6fe87002bdcadbd90" dependencies = [ "bstr", "thiserror", @@ -1137,9 +1137,9 @@ dependencies = [ [[package]] name = "gix-worktree" -version = "0.38.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "756dbbe15188fa22540d5eab941f8f9cf511a5364d5aec34c88083c09f4bea13" +checksum = "6673512f7eaa57a6876adceca6978a501d6c6569a4f177767dc405f8b9778958" dependencies = [ "bstr", "gix-attributes", @@ -1206,6 +1206,7 @@ dependencies = [ "gix", "ignore", "log", + "minijinja", "regex", "serde", "toml", @@ -1508,9 +1509,9 @@ checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libgit2-sys" -version = "0.17.0+1.8.1" +version = "0.18.0+1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224" +checksum = "e1a117465e7e1597e8febea8bb0c410f1c7fb93b1e1cddf34363f8390367ffec" dependencies = [ "cc", "libc", @@ -1612,6 +1613,15 @@ dependencies = [ "libc", ] +[[package]] +name = "minijinja" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212b4cab3aad057bc6e611814472905546c533295723b9e26a31c7feb19a8e65" +dependencies = [ + "serde", +] + [[package]] name = "miniz_oxide" version = "0.8.3" @@ -1906,9 +1916,9 @@ checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" [[package]] name = "shadow-rs" -version = "0.37.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "974eb8222c62a8588bc0f02794dd1ba5b60b3ec88b58e050729d0907ed6af610" +checksum = "69d433b5df1e1958a668457ebe4a9c5b7bcfe844f4eb2276ac43cf273baddd54" dependencies = [ "const_format", "git2", diff --git a/Cargo.toml b/Cargo.toml index ac829b7f..1828dada 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ build-data = { version = "0.2.1" } clap = { version = "4.5.23", features = ["derive"] } const_format = { version = "0.2.34" } log = { version = "0.4.22", features = ["kv_serde", "serde"] } -shadow-rs = { version = "0.37.0" } +shadow-rs = { version = "0.38.0" } toml = { version = "0.8.19" } [workspace.metadata.release] diff --git a/README.md b/README.md index 05a9f6d7..225358cd 100644 --- a/README.md +++ b/README.md @@ -166,14 +166,14 @@ filenames = ["..."] # e.g. "Dockerfile.native" extensions = ["..."] # e.g. "cc" # Properties to fulfill the template. -# For a defined key-value pair, you can use ${key} in the header template, which will be substituted -# with the corresponding value. -# -# Preset properties: -# * 'hawkeye.core.filename' is the current file name, like: pom.xml. +# For a defined key-value pair, you can use {{props["key"]}} in the header template, which will be +# substituted with the corresponding value. [properties] inceptionYear = 2023 +# There are also preset attributes that can be used in the header template (no need to surround them with `props[]`).: +# * 'attrs.filename' is the current file name, like: pom.xml. + # Options to configure Git features. [git] # If enabled, do not process files that are ignored by Git; possible value: ['auto', 'enable', 'disable'] @@ -185,8 +185,8 @@ inceptionYear = 2023 ignore = 'auto' # If enabled, populate file attrs determinated by Git; possible value: ['auto', 'enable', 'disable'] # Attributes contains: -# * 'hawkeye.git.fileCreatedYear' -# * 'hawkeye.git.fileModifiedYear' +# * 'attrs.git_file_created_year' +# * 'attrs.git_file_modified_year' # 'auto' means this feature tries to be enabled with: # * gix - if `basedir` is in a Git repository. # 'enable' means always enabled with gix; failed if it is impossible. diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 20ac7d12..ac2c1bed 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -34,4 +34,4 @@ shadow-rs = { workspace = true } [build-dependencies] build-data = { workspace = true } shadow-rs = { workspace = true } -gix-discover = { version = "0.37.0" } +gix-discover = { version = "0.38.0" } diff --git a/cli/src/subcommand.rs b/cli/src/subcommand.rs index 0f4bb0b0..02c3bdcc 100644 --- a/cli/src/subcommand.rs +++ b/cli/src/subcommand.rs @@ -143,11 +143,11 @@ impl Callback for FormatContext { fn on_not_matched(&mut self, header: &HeaderMatcher, mut doc: Document) -> anyhow::Result<()> { if doc.header_detected() { doc.remove_header(); - doc.update_header(header); + doc.update_header(header)?; self.updated .push(format!("{}=replaced", doc.filepath.display())); } else { - doc.update_header(header); + doc.update_header(header)?; self.updated .push(format!("{}=added", doc.filepath.display())); } diff --git a/fmt/Cargo.toml b/fmt/Cargo.toml index 988daf48..572b5905 100644 --- a/fmt/Cargo.toml +++ b/fmt/Cargo.toml @@ -24,12 +24,13 @@ repository.workspace = true [dependencies] anyhow = { workspace = true } -gix = { version = "0.69.1", default-features = false, features = [ +gix = { version = "0.70.0", default-features = false, features = [ "blob-diff", "excludes", ] } ignore = { version = "0.4.23" } log = { workspace = true } +minijinja = { version = "2.5.0" } regex = { version = "1.11.1" } serde = { version = "1.0.216", features = ["derive"] } toml = { workspace = true } diff --git a/fmt/src/config/mod.rs b/fmt/src/config/mod.rs index 63331249..46a5a4f2 100644 --- a/fmt/src/config/mod.rs +++ b/fmt/src/config/mod.rs @@ -21,12 +21,11 @@ use std::path::PathBuf; use serde::de::Error; use serde::Deserialize; use serde::Deserializer; -use serde::Serialize; use toml::Value; use crate::default_true; -#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, Deserialize)] #[serde(default, rename_all = "camelCase")] pub struct Config { #[serde(default = "default_cwd")] @@ -57,7 +56,7 @@ pub struct Config { pub additional_headers: Vec, } -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, Deserialize)] pub struct Git { pub attrs: FeatureGate, pub ignore: FeatureGate, @@ -72,7 +71,7 @@ impl Default for Git { } } -#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Deserialize)] #[serde(rename_all = "snake_case")] pub enum FeatureGate { /// Determinate whether turn on the feature. @@ -109,7 +108,7 @@ impl FeatureGate { } } -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Eq, PartialEq)] pub enum Mapping { Filename { pattern: String, diff --git a/fmt/src/document/factory.rs b/fmt/src/document/factory.rs index 0bea1fb2..fb0a9fe2 100644 --- a/fmt/src/document/factory.rs +++ b/fmt/src/document/factory.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::borrow::Cow; use std::collections::HashMap; use std::collections::HashSet; use std::path::Path; @@ -22,6 +21,7 @@ use anyhow::Context; use gix::date::time::CustomFormat; use crate::config::Mapping; +use crate::document::Attributes; use crate::document::Document; use crate::git::GitFileAttrs; use crate::header::model::HeaderDef; @@ -69,32 +69,34 @@ impl DocumentFactory { .ok_or_else(|| std::io::Error::other(format!("header type {header_type} not found"))) .with_context(|| format!("cannot to create document: {}", filepath.display()))?; - let mut properties = self.properties.clone(); + let props = self.properties.clone(); - let filename = filepath - .file_name() - .map(|s| s.to_string_lossy()) - .unwrap_or_else(|| Cow::Borrowed("")) - .to_string(); - properties.insert("hawkeye.core.filename".to_string(), filename); - - if let Some(attrs) = self.git_file_attrs.get(filepath) { - const YEAR_FORMAT: CustomFormat = CustomFormat::new("%Y"); - properties.insert( - "hawkeye.git.fileCreatedYear".to_string(), - attrs.created_time.format(YEAR_FORMAT), - ); - properties.insert( - "hawkeye.git.fileModifiedYear".to_string(), - attrs.modified_time.format(YEAR_FORMAT), - ); - } + const YEAR_FORMAT: CustomFormat = CustomFormat::new("%Y"); + let attrs = Attributes { + filename: filepath + .file_name() + .map(|s| s.to_string_lossy().to_string()), + git_file_created_year: self + .git_file_attrs + .get(filepath) + .map(|attrs| attrs.created_time.format(YEAR_FORMAT)), + git_file_modified_year: self + .git_file_attrs + .get(filepath) + .map(|attrs| attrs.modified_time.format(YEAR_FORMAT)), + git_authors: self + .git_file_attrs + .get(filepath) + .map(|attrs| attrs.authors.clone()) + .unwrap_or_default(), + }; Document::new( filepath.to_path_buf(), header_def.clone(), &self.keywords, - properties, + props, + attrs, ) } } diff --git a/fmt/src/document/mod.rs b/fmt/src/document/mod.rs index fd13b8eb..7744acad 100644 --- a/fmt/src/document/mod.rs +++ b/fmt/src/document/mod.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::collections::BTreeSet; use std::collections::HashMap; use std::fs; use std::fs::File; @@ -19,6 +20,10 @@ use std::io::BufRead; use std::path::PathBuf; use anyhow::Context; +use minijinja::context; +use minijinja::Environment; +use serde::Deserialize; +use serde::Serialize; use crate::header::matcher::HeaderMatcher; use crate::header::model::HeaderDef; @@ -29,12 +34,21 @@ use crate::header::parser::HeaderParser; pub mod factory; pub mod model; +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Attributes { + pub filename: Option, + pub git_file_created_year: Option, + pub git_file_modified_year: Option, + pub git_authors: BTreeSet, +} + #[derive(Debug)] pub struct Document { pub filepath: PathBuf, header_def: HeaderDef, - properties: HashMap, + props: HashMap, + attrs: Attributes, parser: HeaderParser, } @@ -43,14 +57,16 @@ impl Document { filepath: PathBuf, header_def: HeaderDef, keywords: &[String], - properties: HashMap, + props: HashMap, + attrs: Attributes, ) -> anyhow::Result> { match FileContent::new(&filepath) { Ok(content) => Ok(Some(Self { parser: parse_header(content, &header_def, keywords), filepath, header_def, - properties, + props, + attrs, })), Err(e) => { if matches!(e.kind(), std::io::ErrorKind::InvalidData) { @@ -79,7 +95,7 @@ impl Document { &self, header: &HeaderMatcher, strict_check: bool, - ) -> std::io::Result { + ) -> anyhow::Result { if strict_check { let file_header = { let mut lines = self.read_file_first_lines(header)?.join("\n"); @@ -88,13 +104,13 @@ impl Document { }; let expected_header = { let raw_header = header.build_for_definition(&self.header_def); - let resolved_header = self.merge_properties(&raw_header); + let resolved_header = self.merge_properties(&raw_header)?; resolved_header.replace(" *\r?\n", "\n") }; Ok(file_header.contains(expected_header.as_str())) } else { let file_header = self.read_file_header_on_one_line(header)?; - let expected_header = self.merge_properties(header.header_content_one_line()); + let expected_header = self.merge_properties(header.header_content_one_line())?; Ok(file_header.contains(expected_header.as_str())) } } @@ -121,13 +137,14 @@ impl Document { Ok(file_header) } - pub fn update_header(&mut self, header: &HeaderMatcher) { + pub fn update_header(&mut self, header: &HeaderMatcher) -> anyhow::Result<()> { let header_str = header.build_for_definition(&self.header_def); - let header_str = self.merge_properties(&header_str); + let header_str = self.merge_properties(&header_str)?; let begin_pos = self.parser.begin_pos; self.parser .file_content .insert(begin_pos, header_str.as_str()); + Ok(()) } pub fn remove_header(&mut self) { @@ -144,15 +161,17 @@ impl Document { .context(format!("cannot save document {}", filepath.display())) } - pub(crate) fn merge_properties(&self, s: &str) -> String { - merge_properties(&self.properties, s) - } -} - -pub fn merge_properties(properties: &HashMap, s: &str) -> String { - let mut result = s.to_string(); - for (key, value) in properties { - result = result.replace(&format!("${{{key}}}"), value); + pub(crate) fn merge_properties(&self, s: &str) -> anyhow::Result { + let mut env = Environment::new(); + env.add_template("template", s) + .context("malformed template")?; + + let tmpl = env.get_template("template").expect("template must exist"); + let mut result = tmpl.render(context! { + props => &self.props, + attrs => &self.attrs, + })?; + result.push('\n'); + Ok(result) } - result } diff --git a/fmt/src/git.rs b/fmt/src/git.rs index a9ea6d68..46fbaf94 100644 --- a/fmt/src/git.rs +++ b/fmt/src/git.rs @@ -13,6 +13,7 @@ // limitations under the License. use std::collections::hash_map::Entry; +use std::collections::BTreeSet; use std::collections::HashMap; use std::convert::Infallible; use std::path::Path; @@ -87,6 +88,7 @@ fn resolve_features(config: &config::Git) -> FeatureGate { pub struct GitFileAttrs { pub created_time: gix::date::Time, pub modified_time: gix::date::Time, + pub authors: BTreeSet, } pub fn resolve_file_attrs( @@ -116,6 +118,7 @@ pub fn resolve_file_attrs( let info = info?; let this_commit = info.object()?; let time = this_commit.time()?; + let author = this_commit.author()?.name.to_string(); let tree = this_commit.tree()?; let mut changes = tree.changes()?; @@ -128,16 +131,20 @@ pub fn resolve_file_attrs( let filepath = workdir.join(filepath); match attrs.entry(filepath) { Entry::Occupied(mut ent) => { - let attrs: &GitFileAttrs = ent.get(); - ent.insert(GitFileAttrs { - created_time: time.min(attrs.created_time), - modified_time: time.max(attrs.modified_time), - }); + let attrs: &mut GitFileAttrs = ent.get_mut(); + attrs.created_time = time.min(attrs.created_time); + attrs.modified_time = time.max(attrs.modified_time); + attrs.authors.insert(author.clone()); } Entry::Vacant(ent) => { ent.insert(GitFileAttrs { created_time: time, modified_time: time, + authors: { + let mut authors = BTreeSet::new(); + authors.insert(author.clone()); + authors + }, }); } } diff --git a/fmt/src/header/model.rs b/fmt/src/header/model.rs index dc3ff87e..ebaf5924 100644 --- a/fmt/src/header/model.rs +++ b/fmt/src/header/model.rs @@ -61,9 +61,9 @@ impl HeaderDef { } } -pub fn default_headers() -> anyhow::Result> { +pub fn default_headers() -> HashMap { let defaults = include_str!("defaults.toml"); - deserialize_header_definitions(defaults.to_string()) + deserialize_header_definitions(defaults.to_string()).unwrap() } pub fn deserialize_header_definitions(value: String) -> anyhow::Result> { diff --git a/fmt/src/license/Apache-2.0.txt b/fmt/src/license/Apache-2.0.txt index 696045a0..649055c8 100644 --- a/fmt/src/license/Apache-2.0.txt +++ b/fmt/src/license/Apache-2.0.txt @@ -1,4 +1,4 @@ -Copyright ${inceptionYear} ${copyrightOwner} +Copyright {{props["inceptionYear"]}} {{props["copyrightOwner"]}} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/fmt/src/license/Elastic-2.0.txt b/fmt/src/license/Elastic-2.0.txt index cb980f5f..41d99e19 100644 --- a/fmt/src/license/Elastic-2.0.txt +++ b/fmt/src/license/Elastic-2.0.txt @@ -1,4 +1,4 @@ -Copyright ${inceptionYear} ${copyrightOwner} +Copyright {{props["inceptionYear"]}} {{props["copyrightOwner"]}} Licensed under the Elastic License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/fmt/src/processor.rs b/fmt/src/processor.rs index 99ee01c5..ba7e3a93 100644 --- a/fmt/src/processor.rs +++ b/fmt/src/processor.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::collections::HashMap; use std::fs; use std::path::Path; use std::path::PathBuf; @@ -82,18 +83,33 @@ pub fn check_license_header( let mut mapping = config.mapping.clone(); if config.use_default_mapping { let default_mapping = default_mapping(); - mapping.extend(default_mapping); + for m in default_mapping { + if !mapping.contains(&m) { + mapping.insert(m); + } + } } mapping }; let definitions = { - let mut defs = default_headers()?; + let mut defs = HashMap::new(); + for (k, v) in default_headers() { + if defs.contains_key(&k) { + anyhow::bail!("Header definition {k} is defined more than once"); + } + defs.insert(k, v); + } for additional_header in &config.additional_headers { let additional_defs = fs::read_to_string(additional_header) .with_context(|| format!("cannot load header definitions: {additional_header}")) .and_then(deserialize_header_definitions)?; - defs.extend(additional_defs); + for (k, v) in additional_defs { + if defs.contains_key(&k) { + anyhow::bail!("Header definition {k} is defined more than once"); + } + defs.insert(k, v); + } } defs }; diff --git a/fmt/tests/tests.rs b/fmt/tests/tests.rs index 1b43dbda..c23bb369 100644 --- a/fmt/tests/tests.rs +++ b/fmt/tests/tests.rs @@ -21,7 +21,7 @@ use hawkeye_fmt::header::parser::FileContent; #[test] fn test_remove_file_only_header() { let file = Path::new("tests/content/empty.py"); - let defs = default_headers().unwrap(); + let defs = default_headers(); let def = defs.get("script_style").unwrap().clone(); let keywords = vec!["copyright".to_string()]; @@ -35,7 +35,7 @@ fn test_remove_file_only_header() { #[test] fn test_two_headers_should_only_remove_the_first() { let file = Path::new("tests/content/two_headers.rs"); - let defs = default_headers().unwrap(); + let defs = default_headers(); let def = defs.get("doubleslash_style").unwrap().clone(); let keywords = vec!["copyright".to_string()]; diff --git a/licenserc.toml b/licenserc.toml index d641698c..545d8979 100644 --- a/licenserc.toml +++ b/licenserc.toml @@ -20,9 +20,8 @@ excludes = [ # Test files "fmt/tests/content/**", - "tests/load_header_path/**", - "tests/regression_blank_line/**", - "tests/bom_issue/**", + "tests/**", + "!tests/it.py", # Generated files ".github/workflows/release.yml", diff --git a/tests/attrs_and_props/license.txt b/tests/attrs_and_props/license.txt new file mode 100644 index 00000000..b37397ed --- /dev/null +++ b/tests/attrs_and_props/license.txt @@ -0,0 +1,6 @@ +props["inceptionYear"]={{props["inceptionYear"]}} +props["copyrightOwner"]={{props["copyrightOwner"]}} + +attrs.filename={{attrs.filename}} +attrs.git_file_created_year={{attrs.git_file_created_year}} +attrs.git_file_modified_year={{attrs.git_file_modified_year}} diff --git a/tests/attrs_and_props/licenserc.toml b/tests/attrs_and_props/licenserc.toml new file mode 100644 index 00000000..300d00e4 --- /dev/null +++ b/tests/attrs_and_props/licenserc.toml @@ -0,0 +1,12 @@ +baseDir = "." +headerPath = "license.txt" + +excludes = ["licenserc.toml", "*.expected"] + +[git] +ignore = 'auto' +attrs = 'enable' + +[properties] +inceptionYear = 2024 +copyrightOwner = "Mike Delaney " diff --git a/tests/attrs_and_props/main.rs b/tests/attrs_and_props/main.rs new file mode 100644 index 00000000..e7a11a96 --- /dev/null +++ b/tests/attrs_and_props/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/tests/attrs_and_props/main.rs.expected b/tests/attrs_and_props/main.rs.expected new file mode 100644 index 00000000..69faee9c --- /dev/null +++ b/tests/attrs_and_props/main.rs.expected @@ -0,0 +1,10 @@ +// props["inceptionYear"]=2024 +// props["copyrightOwner"]=Mike Delaney +// +// attrs.filename=main.rs +// attrs.git_file_created_year=2025 +// attrs.git_file_modified_year=2025 + +fn main() { + println!("Hello, world!"); +} diff --git a/tests/bom_issue/licenserc.toml b/tests/bom_issue/licenserc.toml index 767fc598..3eeab3c9 100644 --- a/tests/bom_issue/licenserc.toml +++ b/tests/bom_issue/licenserc.toml @@ -7,5 +7,5 @@ additionalHeaders = [ "style.toml" ] -[mapping.HASH_SOURCE_STYLE] +[mapping.CS_TEST_STYLE] extensions = ["cs"] diff --git a/tests/bom_issue/style.toml b/tests/bom_issue/style.toml index 72d80137..f759213b 100644 --- a/tests/bom_issue/style.toml +++ b/tests/bom_issue/style.toml @@ -1,4 +1,4 @@ -[HASH_SOURCE_STYLE] +[CS_TEST_STYLE] firstLine = "// -------------------------------------------------------" endLine = "// -------------------------------------------------------\n" skipLinePattern = "^#!.*$" diff --git a/tests/it.py b/tests/it.py index 3ee5cfe4..e8153dac 100755 --- a/tests/it.py +++ b/tests/it.py @@ -33,11 +33,14 @@ def diff_files(file1, file2): subprocess.run(["cargo", "build", "--bin", "hawkeye"], cwd=rootdir, check=True) hawkeye = rootdir / "target" / "debug" / "hawkeye" +subprocess.run([hawkeye, "format", "--fail-if-unknown", "--fail-if-updated=false", "--dry-run"], cwd=(basedir / "attrs_and_props"), check=True) +diff_files(basedir / "attrs_and_props" / "main.rs.expected", basedir / "attrs_and_props" / "main.rs.formatted") + subprocess.run([hawkeye, "format", "--fail-if-unknown", "--fail-if-updated=false", "--dry-run"], cwd=(basedir / "load_header_path"), check=True) diff_files(basedir / "load_header_path" / "main.rs.expected", basedir / "load_header_path" / "main.rs.formatted") -subprocess.run([hawkeye, "format", "--fail-if-unknown", "--fail-if-updated=false", "--dry-run"], cwd=(basedir / "regression_blank_line"), check=True) -diff_files(basedir / "regression_blank_line" / "main.rs.expected", basedir / "regression_blank_line" / "main.rs.formatted") - subprocess.run([hawkeye, "format", "--fail-if-unknown", "--fail-if-updated=false", "--dry-run"], cwd=(basedir / "bom_issue"), check=True) diff_files(basedir / "bom_issue" / "headerless_bom.cs.expected", basedir / "bom_issue" / "headerless_bom.cs.formatted") + +subprocess.run([hawkeye, "format", "--fail-if-unknown", "--fail-if-updated=false", "--dry-run"], cwd=(basedir / "regression_blank_line"), check=True) +diff_files(basedir / "regression_blank_line" / "main.rs.expected", basedir / "regression_blank_line" / "main.rs.formatted") diff --git a/tests/load_header_path/license.txt b/tests/load_header_path/license.txt index 71e7c225..1dab1b39 100644 --- a/tests/load_header_path/license.txt +++ b/tests/load_header_path/license.txt @@ -1,4 +1,4 @@ -Copyright ${inceptionYear} ${copyrightOwner} +Copyright {{props["inceptionYear"]}} {{props["copyrightOwner"]}} Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: