diff --git a/Cargo.lock b/Cargo.lock
index ddcda592..d48b5724 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4,19 +4,13 @@ version = 3
[[package]]
name = "addr2line"
-version = "0.22.0"
+version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678"
+checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
dependencies = [
"gimli",
]
-[[package]]
-name = "adler"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
-
[[package]]
name = "adler2"
version = "2.0.0"
@@ -80,9 +74,9 @@ dependencies = [
[[package]]
name = "allocator-api2"
-version = "0.2.18"
+version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
+checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9"
[[package]]
name = "android-tzdata"
@@ -101,9 +95,9 @@ dependencies = [
[[package]]
name = "anstream"
-version = "0.6.15"
+version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
+checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
dependencies = [
"anstyle",
"anstyle-parse",
@@ -116,36 +110,36 @@ dependencies = [
[[package]]
name = "anstyle"
-version = "1.0.8"
+version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
+checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]]
name = "anstyle-parse"
-version = "0.2.5"
+version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb"
+checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
-version = "1.1.1"
+version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
+checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
dependencies = [
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
[[package]]
name = "anstyle-wincon"
-version = "3.0.4"
+version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
+checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
dependencies = [
"anstyle",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -156,9 +150,9 @@ checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775"
[[package]]
name = "arbitrary"
-version = "1.3.2"
+version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110"
+checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223"
dependencies = [
"derive_arbitrary",
]
@@ -199,9 +193,9 @@ dependencies = [
[[package]]
name = "async-trait"
-version = "0.1.81"
+version = "0.1.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107"
+checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
dependencies = [
"proc-macro2",
"quote",
@@ -216,23 +210,23 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "autocfg"
-version = "1.3.0"
+version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
+checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "backtrace"
-version = "0.3.73"
+version = "0.3.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a"
+checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
dependencies = [
"addr2line",
- "cc",
"cfg-if",
"libc",
- "miniz_oxide 0.7.4",
+ "miniz_oxide",
"object",
"rustc-demangle",
+ "windows-targets 0.52.6",
]
[[package]]
@@ -324,9 +318,9 @@ dependencies = [
[[package]]
name = "bstr"
-version = "1.10.0"
+version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c"
+checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22"
dependencies = [
"memchr",
"regex-automata",
@@ -363,9 +357,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
-version = "1.7.1"
+version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
+checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da"
[[package]]
name = "bytesize"
@@ -411,9 +405,9 @@ dependencies = [
[[package]]
name = "cc"
-version = "1.1.14"
+version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50d2eb3cd3d1bf4529e31c215ee6f93ec5a3d536d9f578f93d9d33ee19562932"
+checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47"
dependencies = [
"jobserver",
"libc",
@@ -426,6 +420,12 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+[[package]]
+name = "cfg_aliases"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
+
[[package]]
name = "chrono"
version = "0.4.38"
@@ -522,9 +522,9 @@ dependencies = [
[[package]]
name = "clap_lex"
-version = "0.7.2"
+version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
+checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7"
[[package]]
name = "clru"
@@ -534,9 +534,9 @@ checksum = "cbd0f76e066e64fdc5631e3bb46381254deab9ef1158292f27c8c57e3bf3fe59"
[[package]]
name = "colorchoice"
-version = "1.0.2"
+version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
+checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]]
name = "compact_str"
@@ -555,18 +555,18 @@ dependencies = [
[[package]]
name = "const_format"
-version = "0.2.32"
+version = "0.2.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673"
+checksum = "50c655d81ff1114fb0dcdea9225ea9f0cc712a6f8d189378e82bdf62a473a64b"
dependencies = [
"const_format_proc_macros",
]
[[package]]
name = "const_format_proc_macros"
-version = "0.2.32"
+version = "0.2.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500"
+checksum = "eff1a44b93f47b1bac19a27932f5c591e43d1ba357ee4f61526c8a25603f0eb1"
dependencies = [
"proc-macro2",
"quote",
@@ -606,9 +606,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "cpufeatures"
-version = "0.2.13"
+version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad"
+checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3"
dependencies = [
"libc",
]
@@ -707,6 +707,41 @@ dependencies = [
"typenum",
]
+[[package]]
+name = "darling"
+version = "0.20.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.20.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.20.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "data-encoding"
version = "2.6.0"
@@ -740,15 +775,21 @@ dependencies = [
[[package]]
name = "derive_arbitrary"
-version = "1.3.2"
+version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
+checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
+[[package]]
+name = "diff"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
+
[[package]]
name = "difflib"
version = "0.4.0"
@@ -833,9 +874,9 @@ dependencies = [
[[package]]
name = "encoding_rs"
-version = "0.8.34"
+version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59"
+checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
dependencies = [
"cfg-if",
]
@@ -878,15 +919,15 @@ dependencies = [
[[package]]
name = "fastrand"
-version = "2.1.0"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
+checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4"
[[package]]
name = "filetime"
-version = "0.2.24"
+version = "0.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf401df4a4e3872c4fe8151134cf483738e74b67fc934d6532c882b3d24a4550"
+checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586"
dependencies = [
"cfg-if",
"libc",
@@ -901,7 +942,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
dependencies = [
"crc32fast",
- "miniz_oxide 0.8.0",
+ "miniz_oxide",
]
[[package]]
@@ -921,6 +962,12 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+[[package]]
+name = "foldhash"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2"
+
[[package]]
name = "form_urlencoded"
version = "1.2.1"
@@ -948,9 +995,9 @@ checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]]
name = "futures-channel"
-version = "0.3.30"
+version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
+checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
dependencies = [
"futures-core",
"futures-sink",
@@ -958,15 +1005,15 @@ dependencies = [
[[package]]
name = "futures-core"
-version = "0.3.30"
+version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
+checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-executor"
-version = "0.3.30"
+version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
+checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
dependencies = [
"futures-core",
"futures-task",
@@ -975,15 +1022,15 @@ dependencies = [
[[package]]
name = "futures-io"
-version = "0.3.30"
+version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
+checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
[[package]]
name = "futures-macro"
-version = "0.3.30"
+version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
+checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
@@ -992,21 +1039,21 @@ dependencies = [
[[package]]
name = "futures-sink"
-version = "0.3.30"
+version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
+checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
[[package]]
name = "futures-task"
-version = "0.3.30"
+version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
+checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
[[package]]
name = "futures-util"
-version = "0.3.30"
+version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
+checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
"futures-core",
"futures-io",
@@ -1044,9 +1091,9 @@ dependencies = [
[[package]]
name = "gimli"
-version = "0.29.0"
+version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
+checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "gix"
@@ -1828,9 +1875,9 @@ dependencies = [
[[package]]
name = "h2"
-version = "0.4.6"
+version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205"
+checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e"
dependencies = [
"atomic-waker",
"bytes",
@@ -1857,9 +1904,14 @@ dependencies = [
[[package]]
name = "hashbrown"
-version = "0.15.0"
+version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
+checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3"
+dependencies = [
+ "allocator-api2",
+ "equivalent",
+ "foldhash",
+]
[[package]]
name = "heck"
@@ -1939,9 +1991,9 @@ dependencies = [
[[package]]
name = "httparse"
-version = "1.9.4"
+version = "1.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9"
+checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946"
[[package]]
name = "httpdate"
@@ -1957,9 +2009,9 @@ checksum = "5c3b1f728c459d27b12448862017b96ad4767b1ec2ec5e6434e99f1577f085b8"
[[package]]
name = "hyper"
-version = "1.4.1"
+version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05"
+checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f"
dependencies = [
"bytes",
"futures-channel",
@@ -1977,9 +2029,9 @@ dependencies = [
[[package]]
name = "hyper-rustls"
-version = "0.27.2"
+version = "0.27.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155"
+checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333"
dependencies = [
"futures-util",
"http",
@@ -1995,9 +2047,9 @@ dependencies = [
[[package]]
name = "hyper-util"
-version = "0.1.7"
+version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9"
+checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4"
dependencies = [
"bytes",
"futures-channel",
@@ -2008,16 +2060,15 @@ dependencies = [
"pin-project-lite",
"socket2",
"tokio",
- "tower",
"tower-service",
"tracing",
]
[[package]]
name = "iana-time-zone"
-version = "0.1.60"
+version = "0.1.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
+checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
dependencies = [
"android_system_properties",
"core-foundation-sys",
@@ -2154,6 +2205,12 @@ dependencies = [
"syn",
]
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
[[package]]
name = "idna"
version = "1.0.3"
@@ -2201,7 +2258,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
dependencies = [
"equivalent",
- "hashbrown 0.15.0",
+ "hashbrown 0.15.1",
]
[[package]]
@@ -2221,10 +2278,14 @@ dependencies = [
[[package]]
name = "instability"
-version = "0.3.2"
+version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b23a0c8dfe501baac4adf6ebbfa6eddf8f0c07f56b058cc1288017e32397846c"
+checksum = "b829f37dead9dc39df40c2d3376c179fdfd2ac771f53f55d3c30dc096a3c0c6e"
dependencies = [
+ "darling",
+ "indoc",
+ "pretty_assertions",
+ "proc-macro2",
"quote",
"syn",
]
@@ -2241,9 +2302,9 @@ dependencies = [
[[package]]
name = "ipnet"
-version = "2.9.0"
+version = "2.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
+checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708"
[[package]]
name = "is_ci"
@@ -2277,9 +2338,9 @@ dependencies = [
[[package]]
name = "itoa"
-version = "1.0.11"
+version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
+checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2"
[[package]]
name = "jaq-core"
@@ -2343,9 +2404,9 @@ dependencies = [
[[package]]
name = "jiff"
-version = "0.1.8"
+version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db2b7379a75544c94b3da32821b0bf41f9062e9970e23b78cc577d0d89676d16"
+checksum = "b9d9d414fc817d3e3d62b2598616733f76c4cc74fbac96069674739b881295c8"
dependencies = [
"jiff-tzdb-platform",
"windows-sys 0.59.0",
@@ -2353,15 +2414,15 @@ dependencies = [
[[package]]
name = "jiff-tzdb"
-version = "0.1.0"
+version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05fac328b3df1c0f18a3c2ab6cb7e06e4e549f366017d796e3e66b6d6889abe6"
+checksum = "91335e575850c5c4c673b9bd467b0e025f164ca59d0564f69d0c2ee0ffad4653"
[[package]]
name = "jiff-tzdb-platform"
-version = "0.1.0"
+version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8da387d5feaf355954c2c122c194d6df9c57d865125a67984bb453db5336940"
+checksum = "9835f0060a626fe59f160437bc725491a6af23133ea906500027d1bd2f8f4329"
dependencies = [
"jiff-tzdb",
]
@@ -2377,9 +2438,9 @@ dependencies = [
[[package]]
name = "js-sys"
-version = "0.3.70"
+version = "0.3.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a"
+checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9"
dependencies = [
"wasm-bindgen",
]
@@ -2440,15 +2501,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
-version = "0.2.158"
+version = "0.2.164"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
+checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f"
[[package]]
name = "libm"
-version = "0.2.8"
+version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
+checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
[[package]]
name = "libredox"
@@ -2469,9 +2530,9 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]]
name = "litemap"
-version = "0.7.3"
+version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704"
+checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104"
[[package]]
name = "lock_api"
@@ -2497,11 +2558,11 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "lru"
-version = "0.12.4"
+version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904"
+checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
dependencies = [
- "hashbrown 0.14.5",
+ "hashbrown 0.15.1",
]
[[package]]
@@ -2552,9 +2613,9 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "memmap2"
-version = "0.9.4"
+version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322"
+checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f"
dependencies = [
"libc",
]
@@ -2583,7 +2644,7 @@ dependencies = [
"terminal_size",
"textwrap",
"thiserror 1.0.69",
- "unicode-width 0.1.13",
+ "unicode-width 0.1.14",
]
[[package]]
@@ -2643,15 +2704,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
-[[package]]
-name = "miniz_oxide"
-version = "0.7.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08"
-dependencies = [
- "adler",
-]
-
[[package]]
name = "miniz_oxide"
version = "0.8.0"
@@ -2808,9 +2860,9 @@ dependencies = [
[[package]]
name = "object"
-version = "0.36.3"
+version = "0.36.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9"
+checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e"
dependencies = [
"memchr",
]
@@ -2912,9 +2964,9 @@ checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a"
[[package]]
name = "owo-colors"
-version = "4.0.0"
+version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f"
+checksum = "fb37767f6569cd834a413442455e0f066d0d522de8630436e2a1761d9726ba56"
[[package]]
name = "paris"
@@ -3024,31 +3076,11 @@ dependencies = [
"siphasher",
]
-[[package]]
-name = "pin-project"
-version = "1.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3"
-dependencies = [
- "pin-project-internal",
-]
-
-[[package]]
-name = "pin-project-internal"
-version = "1.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
[[package]]
name = "pin-project-lite"
-version = "0.2.14"
+version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
+checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff"
[[package]]
name = "pin-utils"
@@ -3058,9 +3090,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
-version = "0.3.30"
+version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
+checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
[[package]]
name = "pori"
@@ -3113,11 +3145,21 @@ dependencies = [
"termtree",
]
+[[package]]
+name = "pretty_assertions"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
+dependencies = [
+ "diff",
+ "yansi",
+]
+
[[package]]
name = "proc-macro2"
-version = "1.0.86"
+version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
+checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [
"unicode-ident",
]
@@ -3142,9 +3184,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quinn"
-version = "0.11.3"
+version = "0.11.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b22d8e7369034b9a7132bc2008cac12f2013c8132b45e0554e6e20e2617f2156"
+checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef"
dependencies = [
"bytes",
"pin-project-lite",
@@ -3153,39 +3195,43 @@ dependencies = [
"rustc-hash",
"rustls",
"socket2",
- "thiserror 1.0.69",
+ "thiserror 2.0.3",
"tokio",
"tracing",
]
[[package]]
name = "quinn-proto"
-version = "0.11.6"
+version = "0.11.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba92fb39ec7ad06ca2582c0ca834dfeadcaf06ddfc8e635c80aa7e1c05315fdd"
+checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d"
dependencies = [
"bytes",
+ "getrandom",
"rand 0.8.5",
"ring",
"rustc-hash",
"rustls",
+ "rustls-pki-types",
"slab",
- "thiserror 1.0.69",
+ "thiserror 2.0.3",
"tinyvec",
"tracing",
+ "web-time",
]
[[package]]
name = "quinn-udp"
-version = "0.5.4"
+version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285"
+checksum = "7d5a626c6807713b15cac82a6acaccd6043c9a5408c24baae07611fec3f243da"
dependencies = [
+ "cfg_aliases",
"libc",
"once_cell",
"socket2",
"tracing",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -3310,9 +3356,9 @@ dependencies = [
[[package]]
name = "redox_syscall"
-version = "0.5.3"
+version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4"
+checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f"
dependencies = [
"bitflags",
]
@@ -3375,9 +3421,9 @@ dependencies = [
[[package]]
name = "regex-automata"
-version = "0.4.8"
+version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
+checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
@@ -3433,9 +3479,9 @@ dependencies = [
[[package]]
name = "reqwest"
-version = "0.12.7"
+version = "0.12.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63"
+checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f"
dependencies = [
"base64 0.22.1",
"bytes",
@@ -3530,9 +3576,9 @@ checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152"
[[package]]
name = "rustix"
-version = "0.38.34"
+version = "0.38.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
+checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6"
dependencies = [
"bitflags",
"errno",
@@ -3543,9 +3589,9 @@ dependencies = [
[[package]]
name = "rustls"
-version = "0.23.12"
+version = "0.23.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044"
+checksum = "9c9cc1d47e243d655ace55ed38201c19ae02c148ae56412ab8750e8f0166ab7f"
dependencies = [
"log",
"once_cell",
@@ -3558,25 +3604,27 @@ dependencies = [
[[package]]
name = "rustls-pemfile"
-version = "2.1.3"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425"
+checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
dependencies = [
- "base64 0.22.1",
"rustls-pki-types",
]
[[package]]
name = "rustls-pki-types"
-version = "1.8.0"
+version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0"
+checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b"
+dependencies = [
+ "web-time",
+]
[[package]]
name = "rustls-webpki"
-version = "0.102.6"
+version = "0.102.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e"
+checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
dependencies = [
"ring",
"rustls-pki-types",
@@ -3585,9 +3633,9 @@ dependencies = [
[[package]]
name = "rustversion"
-version = "1.0.17"
+version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
+checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248"
[[package]]
name = "ryu"
@@ -3720,9 +3768,9 @@ dependencies = [
[[package]]
name = "serde_spanned"
-version = "0.6.7"
+version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d"
+checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
dependencies = [
"serde",
]
@@ -3937,9 +3985,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "supports-color"
-version = "3.0.0"
+version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9829b314621dfc575df4e409e79f9d6a66a3bd707ab73f23cb4aa3a854ac854f"
+checksum = "8775305acf21c96926c900ad056abeef436701108518cf890020387236ac5a77"
dependencies = [
"is_ci",
]
@@ -3958,9 +4006,9 @@ checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2"
[[package]]
name = "syn"
-version = "2.0.87"
+version = "2.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
+checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e"
dependencies = [
"proc-macro2",
"quote",
@@ -3969,9 +4017,9 @@ dependencies = [
[[package]]
name = "sync_wrapper"
-version = "1.0.1"
+version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
+checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
dependencies = [
"futures-core",
]
@@ -4031,9 +4079,9 @@ dependencies = [
[[package]]
name = "tempfile"
-version = "3.12.0"
+version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64"
+checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c"
dependencies = [
"cfg-if",
"fastrand",
@@ -4066,7 +4114,7 @@ checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9"
dependencies = [
"smawk",
"unicode-linebreak",
- "unicode-width 0.1.13",
+ "unicode-width 0.1.14",
]
[[package]]
@@ -4190,9 +4238,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
-version = "1.39.3"
+version = "1.41.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5"
+checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33"
dependencies = [
"backtrace",
"bytes",
@@ -4216,9 +4264,9 @@ dependencies = [
[[package]]
name = "tokio-util"
-version = "0.7.11"
+version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1"
+checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a"
dependencies = [
"bytes",
"futures-core",
@@ -4250,9 +4298,9 @@ dependencies = [
[[package]]
name = "toml_edit"
-version = "0.22.20"
+version = "0.22.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
+checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
dependencies = [
"indexmap",
"serde",
@@ -4261,27 +4309,6 @@ dependencies = [
"winnow",
]
-[[package]]
-name = "tower"
-version = "0.4.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
-dependencies = [
- "futures-core",
- "futures-util",
- "pin-project",
- "pin-project-lite",
- "tokio",
- "tower-layer",
- "tower-service",
-]
-
-[[package]]
-name = "tower-layer"
-version = "0.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
-
[[package]]
name = "tower-service"
version = "0.3.3"
@@ -4362,15 +4389,15 @@ checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217"
[[package]]
name = "unicode-id"
-version = "0.3.4"
+version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1b6def86329695390197b82c1e244a54a131ceb66c996f2088a3876e2ae083f"
+checksum = "10103c57044730945224467c09f71a4db0071c123a0648cc3e818913bde6b561"
[[package]]
name = "unicode-ident"
-version = "1.0.12"
+version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "unicode-linebreak"
@@ -4380,18 +4407,18 @@ checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
[[package]]
name = "unicode-normalization"
-version = "0.1.23"
+version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5"
+checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-segmentation"
-version = "1.11.0"
+version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
+checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "unicode-truncate"
@@ -4401,14 +4428,14 @@ checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf"
dependencies = [
"itertools 0.13.0",
"unicode-segmentation",
- "unicode-width 0.1.13",
+ "unicode-width 0.1.14",
]
[[package]]
name = "unicode-width"
-version = "0.1.13"
+version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
+checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "unicode-width"
@@ -4418,9 +4445,9 @@ checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
[[package]]
name = "unicode-xid"
-version = "0.2.5"
+version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a"
+checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "unsafe-libyaml"
@@ -4452,9 +4479,9 @@ dependencies = [
[[package]]
name = "url"
-version = "2.5.3"
+version = "2.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada"
+checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
dependencies = [
"form_urlencoded",
"idna",
@@ -4487,9 +4514,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
-version = "1.10.0"
+version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314"
+checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a"
dependencies = [
"getrandom",
"rand 0.8.5",
@@ -4554,9 +4581,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
-version = "0.2.93"
+version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5"
+checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
dependencies = [
"cfg-if",
"once_cell",
@@ -4565,9 +4592,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
-version = "0.2.93"
+version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b"
+checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358"
dependencies = [
"bumpalo",
"log",
@@ -4580,9 +4607,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
-version = "0.4.43"
+version = "0.4.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed"
+checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b"
dependencies = [
"cfg-if",
"js-sys",
@@ -4592,9 +4619,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.93"
+version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf"
+checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -4602,9 +4629,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.93"
+version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
+checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
dependencies = [
"proc-macro2",
"quote",
@@ -4615,9 +4642,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.93"
+version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484"
+checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
[[package]]
name = "wax"
@@ -4765,6 +4792,7 @@ dependencies = [
"serde",
"serde_json",
"serde_yaml",
+ "textwrap",
"thiserror 1.0.69",
"walkdir",
"weaver_common",
@@ -4852,9 +4880,19 @@ dependencies = [
[[package]]
name = "web-sys"
-version = "0.3.70"
+version = "0.3.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0"
+checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "web-time"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -4862,9 +4900,9 @@ dependencies = [
[[package]]
name = "webpki-roots"
-version = "0.26.3"
+version = "0.26.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd"
+checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e"
dependencies = [
"rustls-pki-types",
]
@@ -5089,9 +5127,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
-version = "0.6.18"
+version = "0.6.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f"
+checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b"
dependencies = [
"memchr",
]
@@ -5127,11 +5165,17 @@ dependencies = [
"toml",
]
+[[package]]
+name = "yansi"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
+
[[package]]
name = "yoke"
-version = "0.7.4"
+version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5"
+checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
dependencies = [
"serde",
"stable_deref_trait",
@@ -5141,9 +5185,9 @@ dependencies = [
[[package]]
name = "yoke-derive"
-version = "0.7.4"
+version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95"
+checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
dependencies = [
"proc-macro2",
"quote",
@@ -5174,18 +5218,18 @@ dependencies = [
[[package]]
name = "zerofrom"
-version = "0.1.4"
+version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55"
+checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
-version = "0.1.4"
+version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5"
+checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808"
dependencies = [
"proc-macro2",
"quote",
diff --git a/crates/weaver_diff/src/lib.rs b/crates/weaver_diff/src/lib.rs
index b63448df..e3b0265e 100644
--- a/crates/weaver_diff/src/lib.rs
+++ b/crates/weaver_diff/src/lib.rs
@@ -119,6 +119,19 @@ pub fn diff_dir>(expected_dir: P, observed_dir: P) -> std::io::Re
Ok(are_identical)
}
+#[macro_export]
+/// Macro to simplify comparing two strings with a diff.
+macro_rules! assert_string_eq {
+ ($lhs:expr, $rhs:expr) => {
+ assert_eq!(
+ $lhs,
+ $rhs,
+ "Found string differences: {}",
+ weaver_diff::diff_output($lhs, $rhs)
+ );
+ };
+}
+
/// Canonicalizes a JSON string by parsing it into a `serde_json::Value` and then canonicalizing it.
///
/// # Returns
diff --git a/crates/weaver_forge/Cargo.toml b/crates/weaver_forge/Cargo.toml
index 6dd5811b..0ff3bfb0 100644
--- a/crates/weaver_forge/Cargo.toml
+++ b/crates/weaver_forge/Cargo.toml
@@ -29,6 +29,7 @@ jaq-syn = "1.1.0"
indexmap = "2.6.0"
regex = "1.11.1"
markdown = "=1.0.0-alpha.21"
+textwrap = "0.16.1"
itertools.workspace = true
thiserror.workspace = true
diff --git a/crates/weaver_forge/README.md b/crates/weaver_forge/README.md
index 7a69e1f3..8d90df92 100644
--- a/crates/weaver_forge/README.md
+++ b/crates/weaver_forge/README.md
@@ -598,6 +598,9 @@ comment_formats: # optional
trim: # Flag to trim the comment content (default: true).
remove_trailing_dots: # Flag to remove trailing dots from the comment content (default: false).
enforce_trailing_dots: # Flag to enforce trailing dots for the comment content (default: false).
+ word_wrap:
+ line_length: # Maximum number of characters on a line (default: unlimited).
+ ignore_newlines: # Whether newlines in comment should be ignored when a max line_length is set (default: false).
# Fields specific to 'markdown' format
escape_backslashes: # Whether to escape backslashes in markdown (default: false).
@@ -605,6 +608,7 @@ comment_formats: # optional
shortcut_reference_links: # Convert inlined links into shortcut reference links (default: false).
indent_first_level_list_items: # Indent the first level of list items in markdown (default: false).
default_block_code_language: # Default language for block code snippets (default: "").
+ use_go_style_list_indent: , # Whether to use different indent spacing for ordered and unordered lists (default: false).
# Fields specific to 'html' format
old_style_paragraph: # Use old-style HTML paragraphs (default: false).
@@ -862,6 +866,7 @@ The `comment` filter accepts the following optional parameters:
- **`footer`**: A custom footer for the comment block.
- **`indent`**: Number of spaces to add before each comment line for indentation purposes.
- **`indent_type`**: The type of indentation to use. Supported values are `space` (default) and `tab`.
+- **`line_length`**: The maximum number of characters in a line.
> Please open an issue if you have any suggestions for new formats or features.
diff --git a/crates/weaver_forge/expected_output/comment_format/example.go b/crates/weaver_forge/expected_output/comment_format/example.go
index 756eb37d..5802463c 100644
--- a/crates/weaver_forge/expected_output/comment_format/example.go
+++ b/crates/weaver_forge/expected_output/comment_format/example.go
@@ -1,122 +1,141 @@
-// Examples of comments for group device
+package test
- // DEVICE_ID
- // A unique identifier representing the device
- //
- // The device identifier MUST only be defined using the values outlined below. This value is not an advertising identifier and MUST NOT be used as such. On iOS (Swift or Objective-C), this value MUST be equal to the [vendor identifier]. On Android (Java or Kotlin), this value MUST be equal to the Firebase Installation ID or a globally unique UUID which is persisted across sessions in your application. More information can be found [here] on best practices and exact implementation details. Caution should be taken when storing personal data or anything which can identify a user. GDPR and data protection laws may apply, ensure you do your own due diligence
- //
- // [vendor identifier]: https://developer.apple.com/documentation/uikit/uidevice/1620059-identifierforvendor
- // [here]: https://developer.android.com/training/articles/user-data-ids
- const DEVICE_ID = ""
+// Examples of comments for group device
- // DEVICE_MANUFACTURER
- // The name of the device manufacturer
- //
- // The Android OS provides this field via [Build]. iOS apps SHOULD hardcode the value `Apple`
- //
- // [Build]: https://developer.android.com/reference/android/os/Build#MANUFACTURER
- const DEVICE_MANUFACTURER = ""
+// DEVICE_ID
+// A unique identifier representing the device
+//
+// The device identifier MUST only be defined using the values outlined below.
+// This value is not an advertising identifier and MUST NOT be used as such. On
+// iOS (Swift or Objective-C), this value MUST be equal to the
+// [vendor identifier]. On Android (Java or Kotlin), this value MUST be equal to
+// the Firebase Installation ID or a globally unique UUID which is persisted
+// across sessions in your application. More information can be found [here] on
+// best practices and exact implementation details. Caution should be taken when
+// storing personal data or anything which can identify a user. GDPR and data
+// protection laws may apply, ensure you do your own due diligence
+//
+// [vendor identifier]: https://developer.apple.com/documentation/uikit/uidevice/1620059-identifierforvendor
+// [here]: https://developer.android.com/training/articles/user-data-ids
+const DEVICE_ID = ""
- // DEVICE_MODEL_IDENTIFIER
- // The model identifier for the device
- //
- // It's recommended this value represents a machine-readable version of the model identifier rather than the market or consumer-friendly name of the device
- const DEVICE_MODEL_IDENTIFIER = ""
+// DEVICE_MANUFACTURER
+// The name of the device manufacturer
+//
+// The Android OS provides this field via [Build]. iOS apps SHOULD hardcode the
+// value `Apple`
+//
+// [Build]: https://developer.android.com/reference/android/os/Build#MANUFACTURER
+const DEVICE_MANUFACTURER = ""
- // DEVICE_MODEL_NAME
- // The marketing name for the device model
- //
- // It's recommended this value represents a human-readable version of the device model rather than a machine-readable alternative
- const DEVICE_MODEL_NAME = ""
+// DEVICE_MODEL_IDENTIFIER
+// The model identifier for the device
+//
+// It's recommended this value represents a machine-readable version of the model
+// identifier rather than the market or consumer-friendly name of the device
+const DEVICE_MODEL_IDENTIFIER = ""
+// DEVICE_MODEL_NAME
+// The marketing name for the device model
+//
+// It's recommended this value represents a human-readable version of the device
+// model rather than a machine-readable alternative
+const DEVICE_MODEL_NAME = ""
// Examples of comments for group dns
- // DNS_QUESTION_NAME
- // The name being queried.
- //
- // If the name field contains non-printable characters (below 32 or above 126), those characters should be represented as escaped base 10 integers (\DDD). Back slashes and quotes should be escaped. Tabs, carriage returns, and line feeds should be converted to \t, \r, and \n respectively
- const DNS_QUESTION_NAME = ""
-
+// DNS_QUESTION_NAME
+// The name being queried.
+//
+// If the name field contains non-printable characters (below 32 or above 126),
+// those characters should be represented as escaped base 10 integers (\DDD).
+// Back slashes and quotes should be escaped. Tabs, carriage returns, and line
+// feeds should be converted to \t, \r, and \n respectively
+const DNS_QUESTION_NAME = ""
// Examples of comments for group error
- // ERROR_TYPE
- // Describes a class of error the operation ended with.
- //
- // The `error.type` SHOULD be predictable, and SHOULD have low cardinality.
- //
- // When `error.type` is set to a type (e.g., an exception type), its
- // canonical class name identifying the type within the artifact SHOULD be used.
- //
- // Instrumentations SHOULD document the list of errors they report.
- //
- // The cardinality of `error.type` within one instrumentation library SHOULD be low.
- // Telemetry consumers that aggregate data from multiple instrumentation libraries and applications
- // should be prepared for `error.type` to have high cardinality at query time when no
- // additional filters are applied.
- //
- // If the operation has completed successfully, instrumentations SHOULD NOT set `error.type`.
- //
- // If a specific domain defines its own set of error identifiers (such as HTTP or gRPC status codes),
- // it's RECOMMENDED to:
- //
- // - Use a domain-specific attribute
- // - Set `error.type` to capture all errors, regardless of whether they are defined within the domain-specific set or not
- const ERROR_TYPE = ""
-
+// ERROR_TYPE
+// Describes a class of error the operation ended with.
+//
+// The `error.type` SHOULD be predictable, and SHOULD have low cardinality.
+//
+// When `error.type` is set to a type (e.g., an exception type), its
+// canonical class name identifying the type within the artifact SHOULD be used.
+//
+// Instrumentations SHOULD document the list of errors they report.
+//
+// The cardinality of `error.type` within one instrumentation library SHOULD be
+// low.
+// Telemetry consumers that aggregate data from multiple instrumentation
+// libraries and applications
+// should be prepared for `error.type` to have high cardinality at query time
+// when no
+// additional filters are applied.
+//
+// If the operation has completed successfully, instrumentations SHOULD NOT set
+// `error.type`.
+//
+// If a specific domain defines its own set of error identifiers (such as HTTP or
+// gRPC status codes),
+// it's RECOMMENDED to:
+//
+// - Use a domain-specific attribute
+// - Set `error.type` to capture all errors, regardless of whether they are
+// defined within the domain-specific set or not
+const ERROR_TYPE = ""
// Examples of comments for group other
- // ATTR
- // This is a brief description of the attribute + a short link [OTEL].
- //
- // This is a note about the attribute `attr`. It can be multiline.
- //
- // It can contain a list:
- //
- // - item **1**,
- // - lorem ipsum dolor sit amet, consectetur
- // adipiscing elit sed do eiusmod tempor
- // [incididunt] ut labore et dolore magna aliqua.
- // - item 2
- // - lorem ipsum dolor sit amet, consectetur
- // adipiscing elit sed do eiusmod tempor
- // incididunt ut labore et dolore magna aliqua.
- //
- // And an **inline code snippet**: `Attr.attr`.
- //
- // # Summary
- //
- // ## Examples
- //
- // 1. Example 1
- // 2. [Example] with lorem ipsum dolor sit amet, consectetur adipiscing elit
- // [sed] do eiusmod tempor incididunt ut
- // [labore] et dolore magna aliqua.
- // 3. Example 3
- //
- // ## Appendix
- //
- // - [Link 1]
- // - [Link 2]
- // - A very long item in the list with lorem ipsum dolor sit amet, consectetur adipiscing elit sed do eiusmod
- // tempor incididunt ut labore et dolore magna aliqua.
- //
- // > This is a blockquote.
- // > It can contain multiple lines.
- // > Lorem ipsum dolor sit amet, consectetur adipiscing
- // > elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
- //
- // > [!NOTE] Something very important here
- //
- // [OTEL]: https://www.opentelemetry.com
- // [incididunt]: https://www.loremipsum.com
- // [Example]: https://loremipsum.com
- // [sed]: https://loremipsum.com
- // [labore]: https://loremipsum.com
- // [Link 1]: https://www.link1.com
- // [Link 2]: https://www.link2.com
- const ATTR = ""
-
+// ATTR
+// This is a brief description of the attribute + a short link [OTEL].
+//
+// This is a note about the attribute `attr`. It can be multiline.
+//
+// It can contain a list:
+//
+// - item **1**,
+// - lorem ipsum dolor sit amet, consectetur
+// adipiscing elit sed do eiusmod tempor
+// [incididunt] ut labore et dolore magna aliqua.
+// - item 2
+// - lorem ipsum dolor sit amet, consectetur
+// adipiscing elit sed do eiusmod tempor
+// incididunt ut labore et dolore magna aliqua.
+//
+// And an **inline code snippet**: `Attr.attr`.
+//
+// # Summary
+//
+// ## Examples
+//
+// 1. Example 1
+// 2. [Example] with lorem ipsum dolor sit amet, consectetur adipiscing elit
+// [sed] do eiusmod tempor incididunt ut
+// [labore] et dolore magna aliqua.
+// 3. Example 3
+//
+// ## Appendix
+//
+// - [Link 1]
+// - [Link 2]
+// - A very long item in the list with lorem ipsum dolor sit amet, consectetur
+// adipiscing elit sed do eiusmod
+// tempor incididunt ut labore et dolore magna aliqua.
+//
+// > This is a blockquote.
+// > It can contain multiple lines.
+// > Lorem ipsum dolor sit amet, consectetur adipiscing
+// > elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
+//
+// > [!NOTE] Something very important here
+//
+// [OTEL]: https://www.opentelemetry.com
+// [incididunt]: https://www.loremipsum.com
+// [Example]: https://loremipsum.com
+// [sed]: https://loremipsum.com
+// [labore]: https://loremipsum.com
+// [Link 1]: https://www.link1.com
+// [Link 2]: https://www.link2.com
+const ATTR = ""
diff --git a/crates/weaver_forge/src/config.rs b/crates/weaver_forge/src/config.rs
index c14a1604..61de173f 100644
--- a/crates/weaver_forge/src/config.rs
+++ b/crates/weaver_forge/src/config.rs
@@ -40,6 +40,7 @@ use crate::error::Error::InvalidConfigFile;
use crate::file_loader::{FileContent, FileLoader};
use crate::formats::html::HtmlRenderOptions;
use crate::formats::markdown::MarkdownRenderOptions;
+use crate::formats::WordWrapConfig;
use crate::WEAVER_YAML;
/// Weaver configuration.
@@ -322,6 +323,10 @@ pub struct CommentFormat {
/// Flag to enforce trailing dots on the comment content.
#[serde(default = "default_bool::")]
pub enforce_trailing_dots: bool,
+
+ /// Configuration of word-wrapping behavior for comments.
+ #[serde(default)]
+ pub word_wrap: WordWrapConfig,
}
/// The type of indentation to use for the comment.
diff --git a/crates/weaver_forge/src/error.rs b/crates/weaver_forge/src/error.rs
index df49e23f..a25b3f84 100644
--- a/crates/weaver_forge/src/error.rs
+++ b/crates/weaver_forge/src/error.rs
@@ -241,6 +241,14 @@ impl From for DiagnosticMessages {
}
}
+impl From for Error {
+ fn from(_: std::fmt::Error) -> Self {
+ Self::TemplateEngineError {
+ error: "Unexpected string formatting error".to_owned(),
+ }
+ }
+}
+
#[must_use]
pub(crate) fn jinja_err_convert(e: minijinja::Error) -> Error {
Error::WriteGeneratedCodeFailed {
diff --git a/crates/weaver_forge/src/extensions/code.rs b/crates/weaver_forge/src/extensions/code.rs
index 7cf4cb92..90c87155 100644
--- a/crates/weaver_forge/src/extensions/code.rs
+++ b/crates/weaver_forge/src/extensions/code.rs
@@ -85,6 +85,12 @@ pub(crate) fn comment(
.as_ref()
.and_then(|comment_formats| comment_formats.get(&comment_format_name).cloned())
.unwrap_or_default();
+ // Grab line length limit, custom option.
+ let line_length_limit: Option = args
+ .get("line_length")
+ .map(|v: u32| v as usize)
+ .ok()
+ .or(comment_format.word_wrap.line_length);
// If the input is an iterable (i.e. an array), join the values with a newline.
let mut comment = if input.kind() == ValueKind::Seq {
@@ -122,31 +128,6 @@ pub(crate) fn comment(
{
comment.push('.');
}
-
- comment = match &comment_format.format {
- RenderFormat::Markdown(..) => markdown_snippet_renderer
- .render(&comment, &comment_format_name)
- .map_err(|e| {
- minijinja::Error::new(
- ErrorKind::InvalidOperation,
- format!(
- "Comment Markdown rendering failed for format '{}': {}",
- default_comment_format, e
- ),
- )
- })?,
- RenderFormat::Html(..) => html_snippet_renderer
- .render(&comment, &comment_format_name)
- .map_err(|e| {
- minijinja::Error::new(
- ErrorKind::InvalidOperation,
- format!(
- "Comment HTML rendering failed for format '{}': {}",
- default_comment_format, e
- ),
- )
- })?,
- };
let indent_arg = args.get("indent").map(|v: usize| v).unwrap_or(0);
let indent_type_arg = args
.get("indent_type")
@@ -162,7 +143,6 @@ pub(crate) fn comment(
))
}
};
-
let header = args
.get("header")
.map(|v: String| v)
@@ -176,6 +156,35 @@ pub(crate) fn comment(
.map(|v: String| v)
.unwrap_or_else(|_| comment_format.footer.clone().unwrap_or("".to_owned()));
+ // TODO - reduce line limit by tabs.
+ let actual_length_limit =
+ line_length_limit.map(|limit| limit - (indent.len() + prefix.len()));
+ comment = match &comment_format.format {
+ RenderFormat::Markdown(..) => markdown_snippet_renderer
+ .render(&comment, &comment_format_name, actual_length_limit)
+ .map_err(|e| {
+ minijinja::Error::new(
+ ErrorKind::InvalidOperation,
+ format!(
+ "Comment Markdown rendering failed for format '{}': {}",
+ default_comment_format, e
+ ),
+ )
+ })?,
+ RenderFormat::Html(..) => html_snippet_renderer
+ .render(&comment, &comment_format_name, actual_length_limit)
+ .map_err(|e| {
+ minijinja::Error::new(
+ ErrorKind::InvalidOperation,
+ format!(
+ "Comment HTML rendering failed for format '{}': {}",
+ default_comment_format, e
+ ),
+ )
+ })?,
+ };
+
+ // Expand all text with prefix.
let mut new_comment = String::new();
for line in comment.lines() {
if !new_comment.is_empty() {
@@ -197,12 +206,12 @@ pub(crate) fn comment(
}
comment = new_comment;
+ // Add header + footer to the comment.
if !header.is_empty() {
comment = format!("{}\n{}", header, comment);
}
-
if !footer.is_empty() {
- comment = format!("{}\n{}{}", comment, indent, footer);
+ comment = format!("{}\n{}{}", comment.trim_end(), indent, footer);
}
// Remove all trailing spaces from the comment
@@ -266,10 +275,13 @@ pub(crate) fn map_text(
#[cfg(test)]
mod tests {
+ use weaver_diff::assert_string_eq;
+
use super::*;
use crate::config::{CommentFormat, IndentType};
use crate::extensions::code;
use crate::formats::html::HtmlRenderOptions;
+ use crate::formats::WordWrapConfig;
#[test]
fn test_comment() -> Result<(), Error> {
@@ -292,6 +304,10 @@ mod tests {
trim: true,
remove_trailing_dots: true,
enforce_trailing_dots: false,
+ word_wrap: WordWrapConfig {
+ line_length: None,
+ ignore_newlines: true,
+ },
},
)]
.into_iter()
@@ -329,8 +345,8 @@ it's RECOMMENDED to:
let observed_comment = env
.render_str("{{ note | comment(format='java', indent=2) }}", &ctx)
.unwrap();
- assert_eq!(
- observed_comment,
+ assert_string_eq!(
+ &observed_comment,
r##"/**
* The {@code error.type} SHOULD be predictable, and SHOULD have low cardinality.
*
@@ -360,8 +376,8 @@ it's RECOMMENDED to:
let observed_comment = env
.render_str("{{ note | comment(indent=2) }}", &ctx)
.unwrap();
- assert_eq!(
- observed_comment,
+ assert_string_eq!(
+ &observed_comment,
r##"/**
* The {@code error.type} SHOULD be predictable, and SHOULD have low cardinality.
*
@@ -391,8 +407,8 @@ it's RECOMMENDED to:
let observed_comment = env
.render_str("{{ note | comment(indent=2, indent_type='space') }}", &ctx)
.unwrap();
- assert_eq!(
- observed_comment,
+ assert_string_eq!(
+ &observed_comment,
r##"/**
* The {@code error.type} SHOULD be predictable, and SHOULD have low cardinality.
*
@@ -422,8 +438,8 @@ it's RECOMMENDED to:
let observed_comment = env
.render_str("{{ note | comment(indent=2, indent_type='tab') }}", &ctx)
.unwrap();
- assert_eq!(
- observed_comment,
+ assert_string_eq!(
+ &observed_comment,
r##"/**
* The {@code error.type} SHOULD be predictable, and SHOULD have low cardinality.
*
@@ -449,6 +465,68 @@ it's RECOMMENDED to:
*/"##
);
+ // Test with the optional parameter `line_length=30`
+ // TODO - Figure out where extra space is coming from li/code near bottom.
+ let observed_comment = env
+ .render_str("{{ note | comment(line_length=30) }}", &ctx)
+ .unwrap();
+ assert_string_eq!(
+ &observed_comment,
+ r##"/**
+ * The {@code error.type}
+ * SHOULD be predictable, and
+ * SHOULD have low cardinality.
+ *
+ *
+ * When {@code error.type} is
+ * set to a type (e.g., an
+ * exception type), its
+ * canonical class name
+ * identifying the type within
+ * the artifact SHOULD be used.
+ *
+ *
+ * Instrumentations SHOULD
+ * document the list of errors
+ * they report.
+ *
+ * The cardinality of
+ * {@code error.type} within
+ * one instrumentation library
+ * SHOULD be low. Telemetry
+ * consumers that aggregate
+ * data from multiple
+ * instrumentation libraries
+ * and applications should be
+ * prepared for
+ * {@code error.type} to have
+ * high cardinality at query
+ * time when no additional
+ * filters are applied.
+ *
+ * If the operation has
+ * completed successfully,
+ * instrumentations SHOULD NOT
+ * set {@code error.type}.
+ *
+ * If a specific domain defines
+ * its own set of error
+ * identifiers (such as HTTP or
+ * gRPC status codes), it's
+ * RECOMMENDED to:
+ *
+ *
+ * - Use a domain-specific
+ * attribute
+ *
- Set {@code error.type}
+ * to capture all errors,
+ * regardless of whether they
+ * are defined within the
+ * domain-specific set or not
+ *
+ */"##
+ );
+
// New configuration with `indent_type='tab'`
let mut env = Environment::new();
let config = WeaverConfig {
@@ -469,6 +547,10 @@ it's RECOMMENDED to:
trim: true,
remove_trailing_dots: true,
enforce_trailing_dots: false,
+ word_wrap: WordWrapConfig {
+ line_length: None,
+ ignore_newlines: false,
+ },
},
)]
.into_iter()
@@ -497,8 +579,8 @@ it's RECOMMENDED to:
&ctx,
)
.unwrap();
- assert_eq!(
- observed_comment,
+ assert_string_eq!(
+ &observed_comment,
r##"/**
* This is a brief description.
* Note:
@@ -530,6 +612,10 @@ it's RECOMMENDED to:
remove_trailing_dots: true,
enforce_trailing_dots: false,
indent_type: Default::default(),
+ word_wrap: WordWrapConfig {
+ line_length: None,
+ ignore_newlines: false,
+ },
},
)]
.into_iter()
@@ -549,8 +635,8 @@ it's RECOMMENDED to:
let observed_comment = env
.render_str("{{ note | comment(format='java') }}", &ctx)
.unwrap();
- assert_eq!(
- observed_comment,
+ assert_string_eq!(
+ &observed_comment,
r##"/**
* The {@code error.type} SHOULD be predictable, and SHOULD have low cardinality
*/"##
@@ -565,8 +651,8 @@ it's RECOMMENDED to:
let observed_comment = env
.render_str("{{ note | comment(format='java') }}", &ctx)
.unwrap();
- assert_eq!(
- observed_comment,
+ assert_string_eq!(
+ &observed_comment,
r##"/**
* The {@code error.type} SHOULD be predictable, and SHOULD have low cardinality
*/"##
@@ -596,6 +682,10 @@ it's RECOMMENDED to:
remove_trailing_dots: false,
enforce_trailing_dots: true,
indent_type: Default::default(),
+ word_wrap: WordWrapConfig {
+ line_length: None,
+ ignore_newlines: false,
+ },
},
)]
.into_iter()
@@ -615,8 +705,8 @@ it's RECOMMENDED to:
let observed_comment = env
.render_str("{{ note | comment(format='java') }}", &ctx)
.unwrap();
- assert_eq!(
- observed_comment,
+ assert_string_eq!(
+ &observed_comment,
r##"/**
* The {@code error.type} SHOULD be predictable, and SHOULD have low cardinality.
*/"##
@@ -631,8 +721,8 @@ it's RECOMMENDED to:
let observed_comment = env
.render_str("{{ note | comment(format='java') }}", &ctx)
.unwrap();
- assert_eq!(
- observed_comment,
+ assert_string_eq!(
+ &observed_comment,
r##"/**
* The {@code error.type} SHOULD be predictable, and SHOULD have low cardinality.
*/"##
@@ -660,8 +750,8 @@ This also covers UDP network interactions where one side initiates the interacti
/// protocol / API doesn't expose a clear notion of client and server).
/// This also covers UDP network interactions where one side initiates the interaction, e.g. QUIC (HTTP/3) and DNS."#;
- assert_eq!(
- comment_with_prefix(&Value::from(brief), "/// "),
+ assert_string_eq!(
+ &comment_with_prefix(&Value::from(brief), "/// "),
expected_brief
);
}
diff --git a/crates/weaver_forge/src/formats/html.rs b/crates/weaver_forge/src/formats/html.rs
index cd6fed43..903a021c 100644
--- a/crates/weaver_forge/src/formats/html.rs
+++ b/crates/weaver_forge/src/formats/html.rs
@@ -1,3 +1,4 @@
+use super::{WordWrapConfig, WordWrapContext};
use crate::config::{RenderFormat, WeaverConfig};
use crate::error::Error;
use crate::error::Error::InvalidCodeSnippet;
@@ -56,10 +57,10 @@ struct CodeContext {
pub(crate) struct HtmlRenderer<'source> {
options_by_format: HashMap,
+ word_wrap_by_format: HashMap,
env: Environment<'source>,
}
-#[derive(Default)]
struct RenderContext {
// The rendered HTML.
html: String,
@@ -70,6 +71,39 @@ struct RenderContext {
// such a tag left by the previous node, which must be added by the current
// node during rendering, if it exists.
leftover_tag: Option,
+
+ // Context for wrapping words.
+ word_wrap: WordWrapContext,
+}
+
+impl RenderContext {
+ fn new(cfg: &WordWrapConfig) -> Self {
+ Self {
+ html: Default::default(),
+ leftover_tag: Default::default(),
+ word_wrap: WordWrapContext::new(cfg),
+ }
+ }
+ // Pushes a string without splitting it into words.
+ // This will wrap lines if the string is too long for the current line.
+ fn push_unbroken(&mut self, input: &str, indent: &str) -> std::fmt::Result {
+ self.word_wrap.write_unbroken(&mut self.html, input, indent)
+ }
+
+ fn push_unbroken_ln(&mut self, input: &str, indent: &str) -> std::fmt::Result {
+ self.word_wrap
+ .write_unbroken_ln(&mut self.html, input, indent)
+ }
+
+ fn pushln(&mut self, indent: &str) -> std::fmt::Result {
+ self.word_wrap.write_ln(&mut self.html, indent)
+ }
+
+ // Pushes a string after splitting it into words.
+ // This may alter end-of-line splits.
+ fn push_words(&mut self, input: &str, indent: &str) -> std::fmt::Result {
+ self.word_wrap.write_words(&mut self.html, input, indent)
+ }
}
impl<'source> HtmlRenderer<'source> {
@@ -82,7 +116,6 @@ impl<'source> HtmlRenderer<'source> {
// Add all Weaver filters and tests, except the comment filter
// (in code extension), to avoid infinite recursion
install_weaver_extensions(&mut env, config, false)?;
-
Ok(Self {
options_by_format: config
.comment_formats
@@ -94,6 +127,16 @@ impl<'source> HtmlRenderer<'source> {
RenderFormat::Markdown(..) => None,
})
.collect(),
+ word_wrap_by_format: config
+ .comment_formats
+ .clone()
+ .unwrap_or_default()
+ .into_iter()
+ .filter_map(|(name, format)| match format.format {
+ RenderFormat::Html(_) => Some((name, format.word_wrap)),
+ RenderFormat::Markdown(..) => None,
+ })
+ .collect(),
env,
})
}
@@ -104,7 +147,12 @@ impl<'source> HtmlRenderer<'source> {
///
/// * `markdown` - The markdown text to render.
/// * `format` - The comment format to use.
- pub fn render(&self, markdown: &str, format: &str) -> Result {
+ pub fn render(
+ &self,
+ markdown: &str,
+ format: &str,
+ line_length_override: Option,
+ ) -> Result {
let html_render_options = if let Some(options) = self.options_by_format.get(format) {
options
} else {
@@ -113,13 +161,21 @@ impl<'source> HtmlRenderer<'source> {
formats: self.options_by_format.keys().cloned().collect(),
});
};
+ let word_wrap_options = if let Some(options) = self.word_wrap_by_format.get(format) {
+ options.with_line_length_override(line_length_override)
+ } else {
+ return Err(Error::CommentFormatNotFound {
+ format: format.to_owned(),
+ formats: self.options_by_format.keys().cloned().collect(),
+ });
+ };
let md_options = markdown::ParseOptions::default();
let md_node =
markdown::to_mdast(markdown, &md_options).map_err(|e| Error::InvalidMarkdown {
error: e.to_string(),
})?;
- let mut render_context = RenderContext::default();
+ let mut render_context = RenderContext::new(&word_wrap_options);
self.write_html_to(
&mut render_context,
"",
@@ -188,7 +244,7 @@ impl<'source> HtmlRenderer<'source> {
options: &HtmlRenderOptions,
) -> Result<(), Error> {
if let Some(tag) = ctx.leftover_tag.take() {
- ctx.html.push_str(&tag);
+ ctx.push_unbroken_ln(&tag, indent)?;
}
match md_node {
@@ -198,35 +254,35 @@ impl<'source> HtmlRenderer<'source> {
}
}
Node::Text(text) => {
- ctx.html.push_str(&text.value);
+ ctx.push_words(&text.value, indent)?;
}
Node::Paragraph(p) => {
if !options.old_style_paragraph {
- ctx.html.push_str("");
+ ctx.push_unbroken("
", indent)?;
}
for child in &p.children {
self.write_html_to(ctx, indent, child, format, options)?;
}
if options.old_style_paragraph {
- ctx.leftover_tag = Some("\n
\n".to_owned());
+ ctx.leftover_tag = Some("\n
".to_owned());
} else {
- ctx.html.push_str("
\n");
+ ctx.push_unbroken_ln("
", indent)?;
}
}
Node::List(list) => {
let tag = if list.ordered { "ol" } else { "ul" };
- ctx.html.push_str(&format!("<{}>\n", tag));
+ ctx.push_unbroken(&format!("<{}>", tag), indent)?;
let li_indent = format!("{} ", indent);
for item in &list.children {
- ctx.html.push_str(&format!("{}", li_indent));
- self.write_html_to(ctx, indent, item, format, options)?;
- if options.omit_closing_li {
- ctx.html.push('\n');
- } else {
- ctx.html.push_str("\n");
+ ctx.pushln(&li_indent)?;
+ ctx.push_unbroken("", &li_indent)?;
+ self.write_html_to(ctx, &li_indent, item, format, options)?;
+ if !options.omit_closing_li {
+ ctx.push_unbroken("", indent)?;
}
}
- ctx.html.push_str(&format!("{}>\n", tag));
+ ctx.pushln(indent)?;
+ ctx.push_unbroken_ln(&format!("{}>", tag), indent)?;
}
Node::ListItem(item) => {
for child in &item.children {
@@ -243,61 +299,66 @@ impl<'source> HtmlRenderer<'source> {
}
}
Node::Html(html) => {
+ // TODO Calculate line length
ctx.html.push_str(&html.value);
}
Node::InlineCode(code) => {
- ctx.html.push_str(
- self.render_inline_code(code.value.as_str(), format, options)?
- .as_str(),
- );
+ // TODO Calculate line length
+ ctx.push_unbroken(
+ &self.render_inline_code(code.value.as_str(), format, options)?,
+ indent,
+ )?;
}
Node::Code(code) => {
- ctx.html.push_str(
- self.render_block_code(code.value.as_str(), format, options)?
- .as_str(),
- );
+ ctx.push_unbroken(
+ &self.render_block_code(code.value.as_str(), format, options)?,
+ indent,
+ )?;
}
Node::Blockquote(block_quote) => {
- ctx.html.push_str("\n");
+ // Should we enforce line length on block quotes?
+ ctx.push_unbroken_ln("", indent)?;
for child in &block_quote.children {
self.write_html_to(ctx, indent, child, format, options)?;
}
- ctx.html.push_str("
\n");
+ ctx.push_unbroken_ln("
", indent)?;
}
Node::Link(link) => {
- ctx.html.push_str(&format!("", link.url));
+ ctx.push_unbroken(&format!("", link.url), indent)?;
for child in &link.children {
self.write_html_to(ctx, indent, child, format, options)?;
}
- ctx.html.push_str("");
+ ctx.push_unbroken("", indent)?;
}
Node::Strong(Strong { children, .. }) => {
- ctx.html.push_str("");
+ ctx.push_unbroken("", indent)?;
for child in children {
self.write_html_to(ctx, indent, child, format, options)?;
}
- ctx.html.push_str("");
+ ctx.push_unbroken("", indent)?;
}
Node::Emphasis(Emphasis { children, .. }) => {
- ctx.html.push_str("");
+ ctx.push_unbroken("", indent)?;
for child in children {
self.write_html_to(ctx, indent, child, format, options)?;
}
- ctx.html.push_str("");
+ ctx.push_unbroken("", indent)?;
}
Node::Delete(Delete { children, .. }) => {
- ctx.html.push_str("");
+ // TODO Calculate line length
+ ctx.push_unbroken("", indent)?;
for child in children {
self.write_html_to(ctx, indent, child, format, options)?;
}
- ctx.html.push_str("");
+ ctx.push_unbroken("", indent)?;
}
Node::Heading(heading) => {
- ctx.html.push_str(&format!("", heading.depth));
+ // TODO Calculate line length
+ ctx.push_unbroken(&format!("", heading.depth), indent)?;
for child in &heading.children {
self.write_html_to(ctx, indent, child, format, options)?;
}
- ctx.html.push_str(&format!("\n", heading.depth));
+ ctx.push_unbroken(&format!("\n", heading.depth), indent)?;
}
// Not supported markdown node types.
Node::Toml(_) => {}
@@ -330,6 +391,8 @@ mod tests {
use crate::config::{CommentFormat, IndentType, RenderFormat, WeaverConfig};
use crate::error::Error;
use crate::formats::html::{HtmlRenderOptions, HtmlRenderer};
+ use crate::formats::WordWrapConfig;
+ use weaver_diff::assert_string_eq;
#[test]
fn test_html_renderer() -> Result<(), Error> {
@@ -351,6 +414,10 @@ mod tests {
trim: true,
remove_trailing_dots: true,
enforce_trailing_dots: false,
+ word_wrap: WordWrapConfig {
+ line_length: None,
+ ignore_newlines: false,
+ },
},
)]
.into_iter()
@@ -363,9 +430,9 @@ mod tests {
let renderer = HtmlRenderer::try_new(&config)?;
let markdown = r##"In some cases a URL may refer to an IP and/or port directly,
The file extension extracted from the `url.full`, excluding the leading dot."##;
- let html = renderer.render(markdown, "java")?;
- assert_eq!(
- html,
+ let html = renderer.render(markdown, "java", None)?;
+ assert_string_eq!(
+ &html,
r##"In some cases a URL may refer to an IP and/or port directly,
The file extension extracted from the {@code url.full}, excluding the leading dot."##
);
@@ -377,9 +444,9 @@ and specifically the
An example can be found in
[Example Image Manifest](https://docs.docker.com/registry/spec/manifest-v2-2/#example-image-manifest)."##;
- let html = renderer.render(markdown, "java")?;
- assert_eq!(
- html,
+ let html = renderer.render(markdown, "java", None)?;
+ assert_string_eq!(
+ &html,
r##"Follows
OCI Image Manifest Specification,
and specifically the
@@ -393,9 +460,9 @@ An example can be found in
without a domain name. In this case, the IP address would go to the domain field.
If the URL contains a [literal IPv6 address](https://www.rfc-editor.org/rfc/rfc2732#section-2)
enclosed by `[` and `]`, the `[` and `]` characters should also be captured in the domain field."##;
- let html = renderer.render(markdown, "java")?;
- assert_eq!(
- html,
+ let html = renderer.render(markdown, "java", None)?;
+ assert_string_eq!(
+ &html,
r##"In some cases a URL may refer to an IP and/or port directly,
without a domain name. In this case, the IP address would go to the domain field.
If the URL contains a literal IPv6 address
@@ -410,9 +477,9 @@ In such case username and password SHOULD be redacted and attribute's value SHOU
`url.full` SHOULD capture the absolute URL when it is available (or can be reconstructed).
Sensitive content provided in `url.full` SHOULD be scrubbed when instrumentations can identify it."##;
- let html = renderer.render(markdown, "java")?;
- assert_eq!(
- html,
+ let html = renderer.render(markdown, "java", None)?;
+ assert_string_eq!(
+ &html,
r##"For network calls, URL usually has {@code scheme://host[:port][path][?query][#fragment]} format, where the fragment
is not transmitted over HTTP, but if it is known, it SHOULD be included nevertheless.
@@ -425,17 +492,17 @@ Sensitive content provided in {@code url.full} SHOULD be scrubbed when instrumen
let markdown = r##"Pool names are generally obtained via
[BufferPoolMXBean#getName()](https://docs.oracle.com/en/java/javase/11/docs/api/java.management/java/lang/management/BufferPoolMXBean.html#getName())."##;
- let html = renderer.render(markdown, "java")?;
- assert_eq!(
- html,
+ let html = renderer.render(markdown, "java", None)?;
+ assert_string_eq!(
+ &html,
r##"Pool names are generally obtained via
BufferPoolMXBean#getName()."##
);
let markdown = r##"Value can be retrieved from value `space_name` of [`v8.getHeapSpaceStatistics()`](https://nodejs.org/api/v8.html#v8getheapspacestatistics)"##;
- let html = renderer.render(markdown, "java")?;
- assert_eq!(
- html,
+ let html = renderer.render(markdown, "java", None)?;
+ assert_string_eq!(
+ &html,
r##"Value can be retrieved from value {@code space_name} of {@code v8.getHeapSpaceStatistics()}"##
);
@@ -458,9 +525,9 @@ it's RECOMMENDED to:
* Use a domain-specific attribute
* Set `error.type` to capture all errors, regardless of whether they are defined within the domain-specific set or not."##;
- let html = renderer.render(markdown, "java")?;
- assert_eq!(
- html,
+ let html = renderer.render(markdown, "java", None)?;
+ assert_string_eq!(
+ &html,
r##"The {@code error.type} SHOULD be predictable, and SHOULD have low cardinality.
When {@code error.type} is set to a type (e.g., an exception type), its
@@ -482,6 +549,233 @@ it's RECOMMENDED to:
Use a domain-specific attribute
Set {@code error.type} to capture all errors, regardless of whether they are defined within the domain-specific set or not.
+"##
+ );
+ Ok(())
+ }
+
+ #[test]
+ fn test_html_renderer_word_wrap() -> Result<(), Error> {
+ let config = WeaverConfig {
+ comment_formats: Some(
+ vec![(
+ "java".to_owned(),
+ CommentFormat {
+ header: Some("/**".to_owned()),
+ prefix: Some(" * ".to_owned()),
+ footer: Some(" */".to_owned()),
+ indent_type: IndentType::Space,
+ format: RenderFormat::Html(HtmlRenderOptions {
+ old_style_paragraph: true,
+ omit_closing_li: true,
+ inline_code_snippet: "{@code {{code}}}".to_owned(),
+ block_code_snippet: "{@code {{code}}}
".to_owned(),
+ }),
+ trim: true,
+ remove_trailing_dots: true,
+ enforce_trailing_dots: false,
+ word_wrap: WordWrapConfig {
+ line_length: Some(30),
+ ignore_newlines: true,
+ },
+ },
+ )]
+ .into_iter()
+ .collect(),
+ ),
+ default_comment_format: Some("java".to_owned()),
+ ..WeaverConfig::default()
+ };
+
+ let renderer = HtmlRenderer::try_new(&config)?;
+ let markdown = r##"In some cases a URL may refer to an IP and/or port directly,
+ The file extension extracted from the `url.full`, excluding the leading dot."##;
+ let html = renderer.render(markdown, "java", None)?;
+ assert_string_eq!(
+ &html,
+ r##"In some cases a URL may refer
+to an IP and/or port directly,
+The file extension extracted
+from the {@code url.full},
+excluding the leading dot."##
+ );
+
+ let markdown = r##"Follows
+[OCI Image Manifest Specification](https://github.com/opencontainers/image-spec/blob/main/manifest.md),
+and specifically the
+[Digest property](https://github.com/opencontainers/image-spec/blob/main/descriptor.md#digests).
+
+An example can be found in
+[Example Image Manifest](https://docs.docker.com/registry/spec/manifest-v2-2/#example-image-manifest)."##;
+ let html = renderer.render(markdown, "java", None)?;
+ assert_string_eq!(
+ &html,
+ r##"Follows
+
+OCI Image Manifest
+Specification, and
+specifically the
+
+Digest property.
+
+An example can be found in
+
+Example Image Manifest."##
+ );
+
+ let markdown = r##"In some cases a URL may refer to an IP and/or port directly,
+without a domain name. In this case, the IP address would go to the domain field.
+If the URL contains a [literal IPv6 address](https://www.rfc-editor.org/rfc/rfc2732#section-2)
+enclosed by `[` and `]`, the `[` and `]` characters should also be captured in the domain field."##;
+ let html = renderer.render(markdown, "java", None)?;
+ assert_string_eq!(
+ &html,
+ r##"In some cases a URL may refer
+to an IP and/or port directly,
+without a domain name. In this
+case, the IP address would go
+to the domain field. If the URL
+contains a
+
+literal IPv6 address
+enclosed by {@code [} and
+{@code ]}, the {@code [} and
+{@code ]} characters should
+also be captured in the domain
+field."##
+ );
+
+ let markdown = r##"For network calls, URL usually has `scheme://host[:port][path][?query][#fragment]` format, where the fragment
+is not transmitted over HTTP, but if it is known, it SHOULD be included nevertheless.
+
+`url.full` MUST NOT contain credentials passed via URL in form of `https://username:password@www.example.com/`.
+In such case username and password SHOULD be redacted and attribute's value SHOULD be `https://REDACTED:REDACTED@www.example.com/`.
+
+`url.full` SHOULD capture the absolute URL when it is available (or can be reconstructed).
+Sensitive content provided in `url.full` SHOULD be scrubbed when instrumentations can identify it."##;
+ let html = renderer.render(markdown, "java", None)?;
+ assert_string_eq!(
+ &html,
+ r##"For network calls, URL usually
+has
+{@code scheme://host[:port][path][?query][#fragment]}
+ format, where the fragment is
+not transmitted over HTTP, but
+if it is known, it SHOULD be
+included nevertheless.
+
+{@code url.full} MUST NOT
+contain credentials passed via
+URL in form of
+{@code https://username:password@www.example.com/}
+. In such case username and
+password SHOULD be redacted and
+attribute's value SHOULD be
+{@code https://REDACTED:REDACTED@www.example.com/}
+.
+
+{@code url.full} SHOULD capture
+the absolute URL when it is
+available (or can be
+reconstructed). Sensitive
+content provided in
+{@code url.full} SHOULD be
+scrubbed when instrumentations
+can identify it."##
+ );
+
+ let markdown = r##"Pool names are generally obtained via
+[BufferPoolMXBean#getName()](https://docs.oracle.com/en/java/javase/11/docs/api/java.management/java/lang/management/BufferPoolMXBean.html#getName())."##;
+ let html = renderer.render(markdown, "java", None)?;
+ assert_string_eq!(
+ &html,
+ r##"Pool names are generally
+obtained via
+
+BufferPoolMXBean#getName()
+."##
+ );
+
+ let markdown = r##"Value can be retrieved from value `space_name` of [`v8.getHeapSpaceStatistics()`](https://nodejs.org/api/v8.html#v8getheapspacestatistics)"##;
+ let html = renderer.render(markdown, "java", None)?;
+ assert_string_eq!(
+ &html,
+ r##"Value can be retrieved from
+value {@code space_name} of
+
+{@code v8.getHeapSpaceStatistics()}
+"##
+ );
+
+ let markdown = r##"The `error.type` SHOULD be predictable, and SHOULD have low cardinality.
+
+When `error.type` is set to a type (e.g., an exception type), its
+canonical class name identifying the type within the artifact SHOULD be used.
+
+Instrumentations SHOULD document the list of errors they report.
+
+The cardinality of `error.type` within one instrumentation library SHOULD be low.
+Telemetry consumers that aggregate data from multiple instrumentation libraries and applications
+should be prepared for `error.type` to have high cardinality at query time when no
+additional filters are applied.
+
+If the operation has completed successfully, instrumentations SHOULD NOT set `error.type`.
+
+If a specific domain defines its own set of error identifiers (such as HTTP or gRPC status codes),
+it's RECOMMENDED to:
+
+* Use a domain-specific attribute
+* Set `error.type` to capture all errors, regardless of whether they are defined within the domain-specific set or not."##;
+ let html = renderer.render(markdown, "java", None)?;
+ assert_string_eq!(
+ &html,
+ r##"The {@code error.type} SHOULD
+be predictable, and SHOULD have
+low cardinality.
+
+When {@code error.type} is set
+to a type (e.g., an exception
+type), its canonical class name
+identifying the type within the
+artifact SHOULD be used.
+
+Instrumentations SHOULD
+document the list of errors
+they report.
+
+The cardinality of
+{@code error.type} within one
+instrumentation library SHOULD
+be low. Telemetry consumers
+that aggregate data from
+multiple instrumentation
+libraries and applications
+should be prepared for
+{@code error.type} to have high
+cardinality at query time when
+no additional filters are
+applied.
+
+If the operation has completed
+successfully, instrumentations
+SHOULD NOT set
+{@code error.type}.
+
+If a specific domain defines
+its own set of error
+identifiers (such as HTTP or
+gRPC status codes), it's
+RECOMMENDED to:
+
+
+ - Use a domain-specific
+ attribute
+
- Set {@code error.type} to
+ capture all errors,
+ regardless of whether they
+ are defined within the
+ domain-specific set or not.
+
"##
);
Ok(())
diff --git a/crates/weaver_forge/src/formats/markdown.rs b/crates/weaver_forge/src/formats/markdown.rs
index 3dea59f3..4e96488a 100644
--- a/crates/weaver_forge/src/formats/markdown.rs
+++ b/crates/weaver_forge/src/formats/markdown.rs
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: Apache-2.0
+use crate::config::default_bool;
use crate::config::{RenderFormat, WeaverConfig};
use crate::error::Error;
use crate::install_weaver_extensions;
@@ -8,6 +9,8 @@ use minijinja::Environment;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
+use super::{WordWrapConfig, WordWrapContext};
+
/// Options for rendering markdown.
#[derive(Deserialize, Serialize, Debug, Clone, Default)]
#[serde(rename_all = "snake_case")]
@@ -32,6 +35,8 @@ pub struct MarkdownRenderOptions {
/// The default language for code blocks.
/// Default is None.
pub(crate) default_block_code_language: Option,
+ #[serde(default = "default_bool::")]
+ pub(crate) use_go_style_list_indent: bool,
}
pub(crate) struct ShortcutReferenceLink {
@@ -41,9 +46,9 @@ pub(crate) struct ShortcutReferenceLink {
pub(crate) struct MarkdownRenderer {
options_by_format: HashMap,
+ word_wrap_by_format: HashMap,
}
-#[derive(Default)]
struct RenderContext {
// The rendered markdown.
markdown: String,
@@ -63,9 +68,27 @@ struct RenderContext {
line_prefix: String,
// Whether to skip the line prefix on the first line.
skip_line_prefix_on_first_line: bool,
+ // Word wrapping helper.
+ word_wrap: WordWrapContext,
+ // A buffer of text we cannot break apart when dealing with links, emphasis, etc.
+ unbreakable_buffer: Option,
}
impl RenderContext {
+ fn new(cfg: &WordWrapConfig) -> Self {
+ Self {
+ markdown: Default::default(),
+ list_level: Default::default(),
+ list_item_number: Default::default(),
+ shortcut_reference_links: Default::default(),
+ leftover_newlines: Default::default(),
+ line_prefix: Default::default(),
+ skip_line_prefix_on_first_line: Default::default(),
+ word_wrap: WordWrapContext::new(cfg),
+ unbreakable_buffer: Default::default(),
+ }
+ }
+
/// Return the number of leftover newlines and reset the count.
fn take_leftover_newlines(&mut self) -> usize {
let leftover_newlines = self.leftover_newlines;
@@ -81,10 +104,19 @@ impl RenderContext {
/// Add a blank line if the current markdown buffer
/// does not end already with a double newline.
fn add_cond_blank_line(&mut self) {
+ // TODO - This is a workaround for not truly
+ // refactoring word-wrap vs. regular add-text.
if !self.markdown.ends_with("\n\n") && !self.markdown.is_empty() {
- self.markdown.push('\n');
+ let _ = self
+ .word_wrap
+ .write_ln(&mut self.markdown, &self.line_prefix);
}
}
+ fn add_blank_line(&mut self) {
+ let _ = self
+ .word_wrap
+ .write_ln(&mut self.markdown, &self.line_prefix);
+ }
/// Set the line prefix to add in front of each new line.
fn set_line_prefix(&mut self, prefix: &str) {
@@ -102,17 +134,78 @@ impl RenderContext {
self.skip_line_prefix_on_first_line = false;
}
+ fn start_unbreakable_block(&mut self, text: &str) {
+ // TODO - check for existing unbreakable.
+ if let Some(buf) = self.unbreakable_buffer.as_ref() {
+ // ToDo - we should error out here.
+ // For now, we just FLUSH this to write to the buffer.
+ let _ = self
+ .word_wrap
+ .write_unbroken(&mut self.markdown, buf, &self.line_prefix);
+ }
+ if self.word_wrap.line_length.is_some() {
+ // Start a buffer
+ self.unbreakable_buffer = Some(text.to_owned());
+ } else {
+ self.markdown.push_str(text);
+ }
+ }
+ fn end_unbreakable_block(&mut self, text: &str) {
+ let result = if let Some(buffer) = self.unbreakable_buffer.as_ref() {
+ format!("{}{}", buffer, text)
+ } else {
+ text.to_owned()
+ };
+ self.unbreakable_buffer = None;
+ self.add_unbreakable_text(&result);
+ }
/// Add text to the markdown buffer.
fn add_text(&mut self, text: &str) {
- let lines = text.split('\n');
- for (i, line) in lines.enumerate() {
- if i > 0 {
- self.markdown.push('\n');
+ if let Some(buf) = self.unbreakable_buffer.as_mut() {
+ buf.push_str(text);
+ } else if self.word_wrap.line_length.is_some() {
+ if !self.line_prefix.is_empty() && !self.skip_line_prefix_on_first_line {
+ let prefix = self.line_prefix.to_owned();
+ self.add_unbreakable_text(&prefix);
+ }
+ // Word wrap algorithm.
+ if self.word_wrap.ignore_newlines {
+ let _ = self
+ .word_wrap
+ .write_words(&mut self.markdown, text, &self.line_prefix);
+ } else {
+ // Now we need to deal with newlines.
+ let lines = text.split('\n');
+ for (i, line) in lines.enumerate() {
+ if i > 0 {
+ self.add_blank_line();
+ }
+ let _ = self
+ .word_wrap
+ .write_words(&mut self.markdown, line, &self.line_prefix);
+ }
}
- if !self.line_prefix.is_empty() && (!self.skip_line_prefix_on_first_line || i > 0) {
- self.markdown.push_str(self.line_prefix.as_str());
+ } else {
+ // Preserve original lines
+ let lines = text.split('\n');
+ for (i, line) in lines.enumerate() {
+ if i > 0 {
+ self.markdown.push('\n');
+ }
+ if !self.line_prefix.is_empty() && (!self.skip_line_prefix_on_first_line || i > 0) {
+ self.markdown.push_str(self.line_prefix.as_str());
+ }
+ self.markdown.push_str(line);
}
- self.markdown.push_str(line);
+ }
+ }
+ fn add_unbreakable_text(&mut self, text: &str) {
+ if let Some(buf) = self.unbreakable_buffer.as_mut() {
+ buf.push_str(text);
+ } else {
+ let _ = self
+ .word_wrap
+ .write_unbroken(&mut self.markdown, text, &self.line_prefix);
}
}
}
@@ -139,6 +232,16 @@ impl MarkdownRenderer {
RenderFormat::Markdown(markdown_options) => Some((name, markdown_options)),
})
.collect(),
+ word_wrap_by_format: config
+ .comment_formats
+ .clone()
+ .unwrap_or_default()
+ .into_iter()
+ .filter_map(|(name, format)| match format.format {
+ RenderFormat::Html(..) => None,
+ RenderFormat::Markdown(_) => Some((name, format.word_wrap)),
+ })
+ .collect(),
})
}
@@ -148,7 +251,12 @@ impl MarkdownRenderer {
///
/// * `markdown` - The markdown text to render.
/// * `format` - The comment format to use.
- pub fn render(&self, markdown: &str, format: &str) -> Result {
+ pub fn render(
+ &self,
+ markdown: &str,
+ format: &str,
+ line_length_override: Option,
+ ) -> Result {
let render_options = if let Some(options) = self.options_by_format.get(format) {
options
} else {
@@ -157,16 +265,30 @@ impl MarkdownRenderer {
formats: self.options_by_format.keys().cloned().collect(),
});
};
+ let word_wrap_options = if let Some(options) = self.word_wrap_by_format.get(format) {
+ options.with_line_length_override(line_length_override)
+ } else {
+ return Err(Error::CommentFormatNotFound {
+ format: format.to_owned(),
+ formats: self.options_by_format.keys().cloned().collect(),
+ });
+ };
let md_options = markdown::ParseOptions::default();
let md_node =
markdown::to_mdast(markdown, &md_options).map_err(|e| Error::InvalidMarkdown {
error: e.to_string(),
})?;
- let mut render_context = RenderContext::default();
+ let mut render_context = RenderContext::new(&word_wrap_options);
Self::write_markdown_to(&mut render_context, "", &md_node, render_options)?;
if !render_context.shortcut_reference_links.is_empty() {
+ let blank_line_count = render_context.take_leftover_newlines();
+ if blank_line_count > 0 {
+ render_context
+ .markdown
+ .push_str(&"\n".repeat(blank_line_count));
+ }
for link in &render_context.shortcut_reference_links {
render_context.markdown.push('\n');
render_context
@@ -190,7 +312,9 @@ impl MarkdownRenderer {
// Add the newlines left by the previous node only if the current node
// is not a list.
if !matches!(md_node, Node::List(..)) {
- ctx.markdown.push_str(&"\n".repeat(leftover_newlines));
+ for _ in 0..leftover_newlines {
+ ctx.add_blank_line();
+ }
}
}
match md_node {
@@ -236,20 +360,25 @@ impl MarkdownRenderer {
for child in &p.children {
Self::write_markdown_to(ctx, indent, child, options)?;
}
- ctx.markdown.push('\n');
+ ctx.add_blank_line();
}
Node::List(list) => {
ctx.list_level += 1;
let indent = if !options.indent_first_level_list_items && ctx.list_level == 1 {
indent.to_owned()
+ } else if options.use_go_style_list_indent && list.ordered {
+ format!("{} ", indent)
} else {
format!("{} ", indent)
};
- ctx.markdown.push('\n');
+ ctx.add_blank_line();
for item in &list.children {
let leftover_newlines = ctx.take_leftover_newlines();
if leftover_newlines > 0 {
- ctx.markdown.push_str(&"\n".repeat(leftover_newlines));
+ ctx.set_line_prefix("");
+ for _ in 0..leftover_newlines {
+ ctx.add_blank_line();
+ }
}
ctx.list_item_number += 1;
let line_prefix = if list.ordered {
@@ -259,7 +388,8 @@ impl MarkdownRenderer {
};
ctx.skip_line_prefix_on_first_line();
ctx.set_line_prefix(" ".repeat(line_prefix.len()).as_str());
- ctx.markdown.push_str(&line_prefix);
+ // ctx.markdown.push_str(&line_prefix);
+ ctx.add_unbreakable_text(&line_prefix);
Self::write_markdown_to(ctx, &indent, item, options)?;
ctx.add_leftover_newlines(1);
}
@@ -283,10 +413,10 @@ impl MarkdownRenderer {
}
}
Node::Html(html) => {
- ctx.markdown.push_str(&html.value);
+ ctx.add_unbreakable_text(&html.value);
}
Node::InlineCode(code) => {
- ctx.markdown.push_str(&format!("`{}`", code.value));
+ ctx.add_unbreakable_text(&format!("`{}`", code.value));
}
Node::Code(code) => {
// If the language is not specified, use the default language and if no default
@@ -297,58 +427,74 @@ impl MarkdownRenderer {
.or(options.default_block_code_language.as_deref())
.unwrap_or("");
- ctx.markdown
- .push_str(&format!("```{}\n{}\n```\n", lang, code.value));
+ ctx.add_unbreakable_text(&format!("```{}\n{}\n```", lang, code.value));
+ ctx.add_blank_line();
}
Node::Blockquote(block_quote) => {
+ // Somehow we're getting end of lines from the block quote.
ctx.add_cond_blank_line();
ctx.set_line_prefix("> ");
for child in &block_quote.children {
- Self::write_markdown_to(ctx, indent, child, options)?;
+ match child {
+ Node::Paragraph(paragraph) => {
+ for child in ¶graph.children {
+ Self::write_markdown_to(ctx, indent, child, options)?;
+ }
+ }
+ _ => {
+ Self::write_markdown_to(ctx, indent, child, options)?;
+ }
+ }
}
ctx.reset_line_prefix();
+ ctx.add_blank_line();
}
Node::Link(link) => {
- ctx.markdown.push('[');
+ ctx.start_unbreakable_block("[");
let start = ctx.markdown.len();
for child in &link.children {
Self::write_markdown_to(ctx, indent, child, options)?;
}
- let label = ctx.markdown[start..].to_string();
- ctx.markdown.push(']');
+ let label = if let Some(buf) = ctx.unbreakable_buffer.as_ref() {
+ buf[1..].to_string()
+ } else {
+ ctx.markdown[start..].to_string()
+ };
+ ctx.add_unbreakable_text("]");
if options.shortcut_reference_link && !link.url.is_empty() {
let url = link.url.clone();
ctx.shortcut_reference_links
.push(ShortcutReferenceLink { label, url });
} else {
- ctx.markdown.push_str(&format!("({})", link.url));
+ ctx.add_unbreakable_text(&format!("({})", link.url));
}
+ ctx.end_unbreakable_block("");
}
Node::Strong(Strong { children, .. }) => {
- ctx.markdown.push_str("**");
+ ctx.start_unbreakable_block("**");
for child in children {
Self::write_markdown_to(ctx, indent, child, options)?;
}
- ctx.markdown.push_str("**");
+ ctx.end_unbreakable_block("**");
}
Node::Emphasis(Emphasis { children, .. }) => {
- ctx.markdown.push('*');
+ ctx.start_unbreakable_block("*");
for child in children {
Self::write_markdown_to(ctx, indent, child, options)?;
}
- ctx.markdown.push('*');
+ ctx.end_unbreakable_block("*");
}
Node::Delete(Delete { children, .. }) => {
- ctx.markdown.push_str("~~");
+ ctx.start_unbreakable_block("~~");
for child in children {
Self::write_markdown_to(ctx, indent, child, options)?;
}
- ctx.markdown.push_str("~~");
+ ctx.end_unbreakable_block("~~");
}
Node::Heading(heading) => {
// Heading nodes must surrounded by newlines.
ctx.add_cond_blank_line();
- ctx.markdown.push_str(&format!(
+ ctx.start_unbreakable_block(&format!(
"{}{} ",
indent,
"#".repeat(heading.depth as usize),
@@ -356,7 +502,8 @@ impl MarkdownRenderer {
for child in &heading.children {
Self::write_markdown_to(ctx, indent, child, options)?;
}
- ctx.markdown.push('\n');
+ ctx.end_unbreakable_block("");
+ ctx.add_blank_line();
ctx.add_leftover_newlines(1);
}
// Not supported markdown node types.
@@ -387,9 +534,12 @@ impl MarkdownRenderer {
#[cfg(test)]
mod tests {
+ use weaver_diff::assert_string_eq;
+
use crate::config::{CommentFormat, IndentType, RenderFormat, WeaverConfig};
use crate::error::Error;
use crate::formats::markdown::{MarkdownRenderOptions, MarkdownRenderer};
+ use crate::formats::WordWrapConfig;
#[test]
fn test_markdown_renderer() -> Result<(), Error> {
@@ -408,10 +558,15 @@ mod tests {
indent_first_level_list_items: true,
shortcut_reference_link: true,
default_block_code_language: None,
+ use_go_style_list_indent: false,
}),
trim: true,
remove_trailing_dots: true,
enforce_trailing_dots: false,
+ word_wrap: WordWrapConfig {
+ line_length: None,
+ ignore_newlines: false,
+ },
},
)]
.into_iter()
@@ -424,9 +579,9 @@ mod tests {
let renderer = MarkdownRenderer::try_new(&config)?;
let markdown = r##"In some cases a URL may refer to an IP and/or port directly,
The file extension extracted from the `url.full`, excluding the leading dot."##;
- let html = renderer.render(markdown, "go")?;
- assert_eq!(
- html,
+ let rendered_md = renderer.render(markdown, "go", None)?;
+ assert_string_eq!(
+ &rendered_md,
r##"In some cases a URL may refer to an IP and/or port directly,
The file extension extracted from the `url.full`, excluding the leading dot.
"## // ToDo why a new line at the end?
@@ -439,9 +594,9 @@ and specifically the
An example can be found in
[Example Image Manifest](https://docs.docker.com/registry/spec/manifest-v2-2/#example-image-manifest)."##;
- let html = renderer.render(markdown, "go")?;
- assert_eq!(
- html,
+ let rendered_md = renderer.render(markdown, "go", None)?;
+ assert_string_eq!(
+ &rendered_md,
r##"Follows
[OCI Image Manifest Specification],
and specifically the
@@ -474,9 +629,9 @@ it's RECOMMENDED to:
* Use a domain-specific attribute
* Set `error.type` to capture all errors, regardless of whether they are defined within the domain-specific set or not."##;
- let html = renderer.render(markdown, "go")?;
- assert_eq!(
- html,
+ let rendered_md = renderer.render(markdown, "go", None)?;
+ assert_string_eq!(
+ &rendered_md,
r##"The `error.type` SHOULD be predictable, and SHOULD have low cardinality.
When `error.type` is set to a type (e.g., an exception type), its
@@ -512,11 +667,16 @@ it's RECOMMENDED to:
indent_first_level_list_items: true,
shortcut_reference_link: true,
default_block_code_language: None,
+ use_go_style_list_indent: false,
}),
trim: true,
remove_trailing_dots: true,
indent_type: Default::default(),
enforce_trailing_dots: false,
+ word_wrap: WordWrapConfig {
+ line_length: None,
+ ignore_newlines: false,
+ },
},
)]
.into_iter()
@@ -529,9 +689,9 @@ it's RECOMMENDED to:
let renderer = MarkdownRenderer::try_new(&config)?;
let markdown = r##"In some cases a [URL] may refer to an [IP](http://ip.com) and/or port directly,
The file \\[extension\\] extracted \\[from] the `url.full`, excluding the leading dot."##;
- let html = renderer.render(markdown, "go")?;
- assert_eq!(
- html,
+ let rendered_md = renderer.render(markdown, "go", None)?;
+ assert_string_eq!(
+ &rendered_md,
r##"In some cases a [URL] may refer to an [IP] and/or port directly,
The file \[extension\] extracted \[from] the `url.full`, excluding the leading dot.
@@ -552,11 +712,16 @@ The file \[extension\] extracted \[from] the `url.full`, excluding the leading d
indent_first_level_list_items: true,
shortcut_reference_link: true,
default_block_code_language: None,
+ use_go_style_list_indent: false,
}),
trim: true,
remove_trailing_dots: true,
indent_type: Default::default(),
enforce_trailing_dots: false,
+ word_wrap: WordWrapConfig {
+ line_length: None,
+ ignore_newlines: false,
+ },
},
)]
.into_iter()
@@ -569,15 +734,498 @@ The file \[extension\] extracted \[from] the `url.full`, excluding the leading d
let renderer = MarkdownRenderer::try_new(&config)?;
let markdown = r##"In some cases a [URL] may refer to an [IP](http://ip.com) and/or port directly,
The file \[extension\] extracted \[from] the `url.full`, excluding the leading dot."##;
- let html = renderer.render(markdown, "go")?;
- assert_eq!(
- html,
+ let rendered_md = renderer.render(markdown, "go", None)?;
+ assert_string_eq!(
+ &rendered_md,
r##"In some cases a \[URL\] may refer to an [IP] and/or port directly,
The file \[extension\] extracted \[from\] the `url.full`, excluding the leading dot.
[IP]: http://ip.com"##
);
+ Ok(())
+ }
+ #[test]
+ fn test_markdown_renderer_wrap() -> Result<(), Error> {
+ let config = WeaverConfig {
+ comment_formats: Some(
+ vec![(
+ "go".to_owned(),
+ CommentFormat {
+ header: None,
+ prefix: Some("// ".to_owned()),
+ footer: None,
+ indent_type: IndentType::Space,
+ format: RenderFormat::Markdown(MarkdownRenderOptions {
+ escape_backslashes: false,
+ escape_square_brackets: false,
+ indent_first_level_list_items: true,
+ shortcut_reference_link: true,
+ default_block_code_language: None,
+ use_go_style_list_indent: false,
+ }),
+ trim: true,
+ remove_trailing_dots: true,
+ enforce_trailing_dots: false,
+ word_wrap: WordWrapConfig {
+ line_length: Some(30),
+ ignore_newlines: true,
+ },
+ },
+ )]
+ .into_iter()
+ .collect(),
+ ),
+ default_comment_format: Some("go".to_owned()),
+ ..WeaverConfig::default()
+ };
+
+ let renderer = MarkdownRenderer::try_new(&config)?;
+ let markdown = r##"In some cases a URL may refer to an IP and/or port directly,
+ The file extension extracted from the `url.full`, excluding the leading dot."##;
+ let rendered_md = renderer.render(markdown, "go", None)?;
+ assert_string_eq!(
+ &rendered_md,
+ r##"In some cases a URL may refer
+to an IP and/or port directly,
+The file extension extracted
+from the `url.full`, excluding
+the leading dot.
+"## // ToDo why a new line at the end?
+ );
+
+ let markdown = r##"Follows
+[OCI Image Manifest Specification](https://github.com/opencontainers/image-spec/blob/main/manifest.md),
+and specifically the
+[Digest property](https://github.com/opencontainers/image-spec/blob/main/descriptor.md#digests).
+
+An example can be found in
+[Example Image Manifest](https://docs.docker.com/registry/spec/manifest-v2-2/#example-image-manifest)."##;
+ let rendered_md = renderer.render(markdown, "go", None)?;
+ assert_string_eq!(
+ &rendered_md,
+ r##"Follows
+[OCI Image Manifest Specification]
+, and specifically the
+[Digest property].
+
+An example can be found in
+[Example Image Manifest].
+
+[OCI Image Manifest Specification]: https://github.com/opencontainers/image-spec/blob/main/manifest.md
+[Digest property]: https://github.com/opencontainers/image-spec/blob/main/descriptor.md#digests
+[Example Image Manifest]: https://docs.docker.com/registry/spec/manifest-v2-2/#example-image-manifest"##
+ );
+
+ let markdown = r##"The `error.type` SHOULD be predictable, and SHOULD have low cardinality.
+
+When `error.type` is set to a type (e.g., an exception type), its
+canonical class name identifying the type within the artifact SHOULD be used.
+
+Instrumentations SHOULD document the list of errors they report.
+
+The cardinality of `error.type` within one instrumentation library SHOULD be low.
+Telemetry consumers that aggregate data from multiple instrumentation libraries and applications
+should be prepared for `error.type` to have high cardinality at query time when no
+additional filters are applied.
+
+If the operation has completed successfully, instrumentations SHOULD NOT set `error.type`.
+
+If a specific domain defines its own set of error identifiers (such as HTTP or gRPC status codes),
+it's RECOMMENDED to:
+
+* Use a domain-specific attribute
+* Set `error.type` to capture all errors, regardless of whether they are defined within the domain-specific set or not."##;
+ let rendered_md = renderer.render(markdown, "go", None)?;
+ assert_string_eq!(
+ &rendered_md,
+ r##"The `error.type` SHOULD be
+predictable, and SHOULD have
+low cardinality.
+
+When `error.type` is set to a
+type (e.g., an exception type),
+its canonical class name
+identifying the type within the
+artifact SHOULD be used.
+
+Instrumentations SHOULD
+document the list of errors
+they report.
+
+The cardinality of `error.type`
+ within one instrumentation
+library SHOULD be low.
+Telemetry consumers that
+aggregate data from multiple
+instrumentation libraries and
+applications should be prepared
+for `error.type` to have high
+cardinality at query time when
+no additional filters are
+applied.
+
+If the operation has completed
+successfully, instrumentations
+SHOULD NOT set `error.type`.
+
+If a specific domain defines
+its own set of error
+identifiers (such as HTTP or
+gRPC status codes), it's
+RECOMMENDED to:
+
+ - Use a domain-specific
+ attribute
+ - Set `error.type` to capture
+ all errors, regardless of
+ whether they are defined
+ within the domain-specific
+ set or not."##
+ );
+
+ let config = WeaverConfig {
+ comment_formats: Some(
+ vec![(
+ "go".to_owned(),
+ CommentFormat {
+ header: None,
+ prefix: Some("// ".to_owned()),
+ footer: None,
+ format: RenderFormat::Markdown(MarkdownRenderOptions {
+ escape_backslashes: false,
+ escape_square_brackets: false,
+ indent_first_level_list_items: true,
+ shortcut_reference_link: true,
+ default_block_code_language: None,
+ use_go_style_list_indent: false,
+ }),
+ trim: true,
+ remove_trailing_dots: true,
+ indent_type: Default::default(),
+ enforce_trailing_dots: false,
+ word_wrap: WordWrapConfig {
+ line_length: Some(30),
+ ignore_newlines: true,
+ },
+ },
+ )]
+ .into_iter()
+ .collect(),
+ ),
+ default_comment_format: Some("go".to_owned()),
+ ..WeaverConfig::default()
+ };
+
+ let renderer = MarkdownRenderer::try_new(&config)?;
+ let markdown = r##"In some cases a [URL] may refer to an [IP](http://ip.com) and/or port directly,
+ The file \\[extension\\] extracted \\[from] the `url.full`, excluding the leading dot."##;
+ let rendered_md = renderer.render(markdown, "go", None)?;
+ assert_string_eq!(
+ &rendered_md,
+ r##"In some cases a [URL] may refer
+to an [IP] and/or port
+directly, The file
+\[extension\] extracted \[from]
+the `url.full`, excluding the
+leading dot.
+
+[IP]: http://ip.com"##
+ );
+
+ let config = WeaverConfig {
+ comment_formats: Some(
+ vec![(
+ "go".to_owned(),
+ CommentFormat {
+ header: None,
+ prefix: Some("// ".to_owned()),
+ footer: None,
+ format: RenderFormat::Markdown(MarkdownRenderOptions {
+ escape_backslashes: false,
+ escape_square_brackets: true,
+ indent_first_level_list_items: true,
+ shortcut_reference_link: true,
+ default_block_code_language: None,
+ use_go_style_list_indent: false,
+ }),
+ trim: true,
+ remove_trailing_dots: true,
+ indent_type: Default::default(),
+ enforce_trailing_dots: false,
+ word_wrap: WordWrapConfig {
+ line_length: Some(30),
+ ignore_newlines: true,
+ },
+ },
+ )]
+ .into_iter()
+ .collect(),
+ ),
+ default_comment_format: Some("go".to_owned()),
+ ..WeaverConfig::default()
+ };
+
+ let renderer = MarkdownRenderer::try_new(&config)?;
+ let markdown = r##"In some cases a [URL] may refer to an [IP](http://ip.com) and/or port directly,
+ The file \[extension\] extracted \[from] the `url.full`, excluding the leading dot."##;
+ let rendered_md = renderer.render(markdown, "go", None)?;
+ assert_string_eq!(
+ &rendered_md,
+ r##"In some cases a \[URL\] may
+refer to an [IP] and/or port
+directly, The file
+\[extension\] extracted
+\[from\] the `url.full`,
+excluding the leading dot.
+
+[IP]: http://ip.com"##
+ );
+
+ let renderer = MarkdownRenderer::try_new(&config)?;
+ let markdown = r##"It should handle weirdly split lists.
+
+## Unordered
+
+- [Link 1](https://www.link1.com)
+- [Link 2](https://www.link2.com)
+- A very long item in the list with lorem ipsum dolor sit amet, consectetur adipiscing elit sed do eiusmod
+ tempor incididunt ut labore et dolore magna aliqua.
+
+## Ordered
+
+1. Example 1
+2. [Example](https://loremipsum.com) with lorem ipsum dolor sit amet, consectetur adipiscing elit
+ [sed](https://loremipsum.com) do eiusmod tempor incididunt ut
+ [labore](https://loremipsum.com) et dolore magna aliqua.
+3. Example 3
+"##;
+ let rendered_md = renderer.render(markdown, "go", None)?;
+ assert_string_eq!(
+ &rendered_md,
+ r##"It should handle weirdly split
+lists.
+
+## Unordered
+
+ - [Link 1]
+ - [Link 2]
+ - A very long item in the
+ list with lorem ipsum dolor
+ sit amet, consectetur
+ adipiscing elit sed do
+ eiusmod tempor incididunt
+ ut labore et dolore magna
+ aliqua.
+
+## Ordered
+
+ 1. Example 1
+ 2. [Example] with lorem ipsum
+ dolor sit amet,
+ consectetur adipiscing
+ elit [sed] do eiusmod
+ tempor incididunt ut
+ [labore] et dolore magna
+ aliqua.
+ 3. Example 3
+
+
+[Link 1]: https://www.link1.com
+[Link 2]: https://www.link2.com
+[Example]: https://loremipsum.com
+[sed]: https://loremipsum.com
+[labore]: https://loremipsum.com"##
+ );
+
+ Ok(())
+ }
+ #[test]
+ fn test_markdown_render_keep_newlines() -> Result<(), Error> {
+ let config = WeaverConfig {
+ comment_formats: Some(
+ vec![(
+ "go".to_owned(),
+ CommentFormat {
+ header: None,
+ prefix: Some("// ".to_owned()),
+ footer: None,
+ format: RenderFormat::Markdown(MarkdownRenderOptions {
+ escape_backslashes: false,
+ escape_square_brackets: true,
+ indent_first_level_list_items: true,
+ shortcut_reference_link: true,
+ default_block_code_language: None,
+ use_go_style_list_indent: false,
+ }),
+ trim: true,
+ remove_trailing_dots: true,
+ indent_type: Default::default(),
+ enforce_trailing_dots: false,
+ word_wrap: WordWrapConfig {
+ line_length: Some(30),
+ ignore_newlines: false,
+ },
+ },
+ )]
+ .into_iter()
+ .collect(),
+ ),
+ default_comment_format: Some("go".to_owned()),
+ ..WeaverConfig::default()
+ };
+ let renderer = MarkdownRenderer::try_new(&config)?;
+ let markdown = r##"It should handle weirdly split lists.
+
+## Unordered
+
+- [Link 1](https://www.link1.com)
+- [Link 2](https://www.link2.com)
+- A very long item in the list with lorem ipsum dolor sit amet, consectetur adipiscing elit sed do eiusmod
+ tempor incididunt ut labore et dolore magna aliqua.
+
+## Ordered
+
+1. Example 1
+2. [Example](https://loremipsum.com) with lorem ipsum dolor sit amet, consectetur adipiscing elit
+ [sed](https://loremipsum.com) do eiusmod tempor incididunt ut
+ [labore](https://loremipsum.com) et dolore magna aliqua.
+3. Example 3
+"##;
+ let rendered_md = renderer.render(markdown, "go", None)?;
+ assert_string_eq!(
+ &rendered_md,
+ r##"It should handle weirdly split
+lists.
+
+## Unordered
+
+ - [Link 1]
+ - [Link 2]
+ - A very long item in the
+ list with lorem ipsum dolor
+ sit amet, consectetur
+ adipiscing elit sed do
+ eiusmod
+ tempor incididunt ut labore
+ et dolore magna aliqua.
+
+## Ordered
+
+ 1. Example 1
+ 2. [Example] with lorem ipsum
+ dolor sit amet,
+ consectetur adipiscing
+ elit
+ [sed] do eiusmod tempor
+ incididunt ut
+ [labore] et dolore magna
+ aliqua.
+ 3. Example 3
+
+
+[Link 1]: https://www.link1.com
+[Link 2]: https://www.link2.com
+[Example]: https://loremipsum.com
+[sed]: https://loremipsum.com
+[labore]: https://loremipsum.com"##
+ );
+
+ // We do not want to split on punctuations like this, e.g.
+ // `.`, `:`, etc.
+ let renderer = MarkdownRenderer::try_new(&config)?;
+ let markdown = r##"And an **inline code snippet**: `Attr.attr`."##;
+ let rendered_md = renderer.render(markdown, "go", Some(80))?;
+ assert_string_eq!(
+ &rendered_md,
+ r##"And an **inline code snippet**: `Attr.attr`.
+"##
+ );
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_markdown_render_go_lists() -> Result<(), Error> {
+ let config = WeaverConfig {
+ comment_formats: Some(
+ vec![(
+ "go".to_owned(),
+ CommentFormat {
+ header: None,
+ prefix: Some("// ".to_owned()),
+ footer: None,
+ format: RenderFormat::Markdown(MarkdownRenderOptions {
+ escape_backslashes: false,
+ escape_square_brackets: true,
+ indent_first_level_list_items: true,
+ shortcut_reference_link: true,
+ default_block_code_language: None,
+ use_go_style_list_indent: true,
+ }),
+ trim: true,
+ remove_trailing_dots: true,
+ indent_type: Default::default(),
+ enforce_trailing_dots: false,
+ word_wrap: WordWrapConfig {
+ line_length: Some(30),
+ ignore_newlines: false,
+ },
+ },
+ )]
+ .into_iter()
+ .collect(),
+ ),
+ default_comment_format: Some("go".to_owned()),
+ ..WeaverConfig::default()
+ };
+ let renderer = MarkdownRenderer::try_new(&config)?;
+ let markdown = r##"It should handle weirdly split lists for go.
+
+## Unordered
+
+ - [Link 1](https://www.link1.com)
+ - [Link 2](https://www.link2.com)
+ - A very long item in the list with lorem ipsum dolor sit amet, consectetur adipiscing elit sed do eiusmod
+ tempor incididunt ut labore et dolore magna aliqua.
+
+## Ordered
+
+ 1. Example 1
+ 2. [Example](https://loremipsum.com) with lorem ipsum dolor sit amet, consectetur adipiscing elit
+ [sed](https://loremipsum.com) do eiusmod tempor incididunt ut
+ [labore](https://loremipsum.com) et dolore magna aliqua.
+ 3. Example 3
+"##;
+ let rendered_md = renderer.render(markdown, "go", Some(80))?;
+ assert_string_eq!(
+ &rendered_md,
+ r##"It should handle weirdly split lists for go.
+
+## Unordered
+
+ - [Link 1]
+ - [Link 2]
+ - A very long item in the list with lorem ipsum dolor sit amet, consectetur
+ adipiscing elit sed do eiusmod
+ tempor incididunt ut labore et dolore magna aliqua.
+
+## Ordered
+
+ 1. Example 1
+ 2. [Example] with lorem ipsum dolor sit amet, consectetur adipiscing elit
+ [sed] do eiusmod tempor incididunt ut
+ [labore] et dolore magna aliqua.
+ 3. Example 3
+
+
+[Link 1]: https://www.link1.com
+[Link 2]: https://www.link2.com
+[Example]: https://loremipsum.com
+[sed]: https://loremipsum.com
+[labore]: https://loremipsum.com"##
+ );
+
Ok(())
}
}
diff --git a/crates/weaver_forge/src/formats/mod.rs b/crates/weaver_forge/src/formats/mod.rs
index 4dce0dc4..5bdedbf8 100644
--- a/crates/weaver_forge/src/formats/mod.rs
+++ b/crates/weaver_forge/src/formats/mod.rs
@@ -2,5 +2,217 @@
//! Output formats supported by the `comment` filter.
+use std::fmt::Write;
+
+use crate::config::default_bool;
+use serde::{Deserialize, Serialize};
+use textwrap::{core::Word, WordSeparator};
+
pub mod html;
pub mod markdown;
+
+/// Configuration for word-wrap behavior.
+#[derive(Serialize, Deserialize, Debug, Clone, Default)]
+pub struct WordWrapConfig {
+ /// The limit of characters per-line.
+ /// If this contains a size, word wrapping will ensure
+ /// lines do not exceed this size UNLESS un-splittable
+ /// words exceed this size.
+ pub(crate) line_length: Option,
+ /// True if we wrap words across newlines and don't preserve them.
+ /// Note: This behavior is nuanced and likely needs finer grained control.
+ #[serde(default = "default_bool::")]
+ pub(crate) ignore_newlines: bool,
+}
+
+impl WordWrapConfig {
+ // Applies an override to the config.
+ fn with_line_length_override(&self, line_length_override: Option) -> Self {
+ match line_length_override {
+ Some(size) => WordWrapConfig {
+ line_length: Some(size),
+ ignore_newlines: self.ignore_newlines,
+ },
+ None => self.clone(),
+ }
+ }
+}
+
+fn is_ascii_space_or_newline(ch: char) -> bool {
+ ch == ' ' || ch == '\r' || ch == '\n'
+}
+
+fn find_words_ascii_space_and_newline<'a>(
+ line: &'a str,
+) -> Box> + 'a> {
+ let mut start = 0;
+ let mut in_whitespace = false;
+ let mut char_indices = line.char_indices();
+
+ Box::new(std::iter::from_fn(move || {
+ for (idx, ch) in char_indices.by_ref() {
+ if in_whitespace && !is_ascii_space_or_newline(ch) {
+ let word = Word::from(line[start..idx].trim_end());
+ start = idx;
+ in_whitespace = is_ascii_space_or_newline(ch);
+ return Some(word);
+ }
+
+ in_whitespace = is_ascii_space_or_newline(ch);
+ }
+
+ if start < line.len() {
+ let word = Word::from(line[start..].trim_end());
+ start = line.len();
+ return Some(word);
+ }
+
+ None
+ }))
+}
+
+struct WordWrapContext {
+ // Mechanism we use to split words.
+ word_separator: WordSeparator,
+ // The limit of characters per-line.
+ line_length: Option,
+
+ // Current length of a line being rendered.
+ current_line_length: usize,
+
+ // True if there's a dangling space from previously written
+ // word we may choose to ignore.
+ letfover_space: bool,
+
+ // True if we wrap across newlines and don't preserve them.
+ ignore_newlines: bool,
+}
+
+fn default_word_separator(ignore_newlines: bool) -> WordSeparator {
+ if ignore_newlines {
+ WordSeparator::Custom(find_words_ascii_space_and_newline)
+ } else {
+ WordSeparator::AsciiSpace
+ }
+}
+
+impl WordWrapContext {
+ fn new(cfg: &WordWrapConfig) -> Self {
+ Self {
+ word_separator: default_word_separator(cfg.ignore_newlines),
+ line_length: cfg.line_length,
+ current_line_length: Default::default(),
+ letfover_space: Default::default(),
+ ignore_newlines: cfg.ignore_newlines,
+ }
+ }
+
+ fn write_unbroken(
+ &mut self,
+ out: &mut O,
+ input: &str,
+ indent: &str,
+ ) -> std::fmt::Result {
+ if self
+ .line_length
+ .map(|max| self.current_line_length + input.len() > max)
+ .unwrap_or(false)
+ {
+ write!(out, "\n{indent}")?;
+ self.current_line_length = indent.len();
+ } else if self.letfover_space {
+ write!(out, " ")?;
+ self.current_line_length += 1;
+ }
+ write!(out, "{input}")?;
+ self.current_line_length += input.len();
+ self.letfover_space = false;
+ Ok(())
+ }
+ fn write_ln(&mut self, out: &mut O, indent: &str) -> std::fmt::Result {
+ write!(out, "\n{indent}")?;
+ self.current_line_length = indent.len();
+ self.letfover_space = false;
+ Ok(())
+ }
+ fn write_words(
+ &mut self,
+ out: &mut O,
+ input: &str,
+ indent: &str,
+ ) -> std::fmt::Result {
+ // Just push the words directly if no limits.
+ if self.line_length.is_none() {
+ write!(out, "{input}")?;
+ self.current_line_length += input.len();
+ return Ok(());
+ }
+ let mut first = true;
+ for word in self.word_separator.find_words(input) {
+ // We either add an end of line or space between words.
+ let mut newline = false;
+ if self
+ .line_length
+ .map(|max| self.current_line_length + word.len() > max)
+ .unwrap_or(false)
+ {
+ // Split the line.
+ write!(out, "\n{indent}")?;
+ self.current_line_length = indent.len();
+ newline = true;
+ } else if self.letfover_space || !first {
+ write!(out, " ")?;
+ self.current_line_length += 1;
+ }
+ // Handle a scenario where we created a new line
+ // and don't want a space in it.
+ if first && newline {
+ write!(out, "{}", word.trim_start())?;
+ self.current_line_length += word.trim_start().len();
+ } else {
+ write!(out, "{}", word.word)?;
+ self.current_line_length += word.len();
+ }
+
+ first = false;
+ self.letfover_space = false;
+ }
+ // TODO - mark this as tailing so we can later decide to add it or
+ // newline.
+ // We struggle with the AST of markdown here.
+ self.letfover_space =
+ input.ends_with(' ') || (self.ignore_newlines && input.ends_with('\n'));
+ Ok(())
+ }
+
+ fn write_unbroken_ln(
+ &mut self,
+ out: &mut O,
+ input: &str,
+ indent: &str,
+ ) -> std::fmt::Result {
+ self.write_unbroken(out, input, indent)?;
+ self.write_ln(out, indent)?;
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::find_words_ascii_space_and_newline;
+ use itertools::Itertools;
+
+ fn find_words_into_vec(line: &str) -> Vec {
+ find_words_ascii_space_and_newline(line)
+ .map(|w| w.to_string())
+ .collect_vec()
+ }
+
+ #[test]
+ fn test_find_words_dont_split_markdown() {
+ assert_eq!(
+ find_words_into_vec("test\nthe words"),
+ vec!("test", "the", "words")
+ );
+ }
+}
diff --git a/crates/weaver_forge/templates/comment_format/example.go.j2 b/crates/weaver_forge/templates/comment_format/example.go.j2
index 7a50fee8..a69b80c6 100644
--- a/crates/weaver_forge/templates/comment_format/example.go.j2
+++ b/crates/weaver_forge/templates/comment_format/example.go.j2
@@ -1,10 +1,11 @@
+package test
+
{% for attributes_root_namespace in ctx %}
{{ ("Examples of comments for group " ~ attributes_root_namespace.root_namespace) | comment(format="go") }}
{% for attr in attributes_root_namespace.attributes %}
- {{ [attr.name | screaming_snake_case, attr.brief, "\n", attr.note] | comment(format="go", indent=2) }}
- const {{ attr.name | screaming_snake_case }} = ""
+{{ [attr.name | screaming_snake_case, attr.brief, "\n", attr.note] | comment(format="go", indent=0) }}
+const {{ attr.name | screaming_snake_case }} = ""
{% endfor %}
-
{% endfor %}
\ No newline at end of file
diff --git a/crates/weaver_forge/templates/comment_format/weaver.yaml b/crates/weaver_forge/templates/comment_format/weaver.yaml
index ce265626..ca855e5a 100644
--- a/crates/weaver_forge/templates/comment_format/weaver.yaml
+++ b/crates/weaver_forge/templates/comment_format/weaver.yaml
@@ -38,6 +38,10 @@ comment_formats:
trim: true
remove_trailing_dots: true
escape_square_brackets: false
+ use_go_style_list_indent: true
+ word_wrap:
+ line_length: 80
+ ignore_newlines: false
python:
format: markdown
header: '"""'