diff --git a/Cargo.lock b/Cargo.lock index 46af691a0..2c14c2b56 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,17 +3,10 @@ version = 3 [[package]] -name = "ahash" -version = "0.8.11" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" -dependencies = [ - "cfg-if", - "getrandom", - "once_cell", - "version_check", - "zerocopy", -] +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aho-corasick" @@ -96,9 +89,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.89" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" +checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8" [[package]] name = "approx" @@ -109,6 +102,24 @@ dependencies = [ "num-traits", ] +[[package]] +name = "arboard" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df099ccb16cd014ff054ac1bf392c67feeef57164b05c42f037cd40f5d4357f4" +dependencies = [ + "clipboard-win", + "core-graphics", + "image", + "log", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "parking_lot", + "windows-sys 0.48.0", + "x11rb", +] + [[package]] name = "arrayvec" version = "0.7.6" @@ -165,6 +176,12 @@ dependencies = [ "serde", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.6.0" @@ -180,6 +197,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2", +] + [[package]] name = "bstr" version = "1.10.0" @@ -197,17 +223,29 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "bytemuck" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" + [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" -version = "1.7.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "cast" @@ -226,9 +264,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.29" +version = "1.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58e804ac3194a48bb129643eb1d62fcc20d18c6b8c181704489353d13120bcd1" +checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" dependencies = [ "shlex", ] @@ -382,12 +420,46 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + [[package]] name = "cpufeatures" version = "0.2.14" @@ -397,6 +469,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + [[package]] name = "criterion" version = "0.5.1" @@ -629,6 +710,25 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "fdeflate" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8090f921a24b04994d9929e204f50b498a33ea6ba559ffaa05e04f7ee7fb5ab" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "flate2" +version = "1.0.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "float-cmp" version = "0.9.0" @@ -644,6 +744,33 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -663,6 +790,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "gethostname" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +dependencies = [ + "libc", + "windows-targets 0.48.5", +] + [[package]] name = "getrandom" version = "0.2.15" @@ -806,6 +943,19 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "image" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc144d44a31d753b02ce64093d532f55ff8dc4ebf2ffb8a63c0dda691385acae" +dependencies = [ + "bytemuck", + "byteorder-lite", + "num-traits", + "png", + "tiff", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -908,6 +1058,12 @@ dependencies = [ "jiff-tzdb", ] +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" + [[package]] name = "js-sys" version = "0.3.72" @@ -925,9 +1081,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.159" +version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" [[package]] name = "libm" @@ -941,7 +1097,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags", + "bitflags 2.6.0", "libc", ] @@ -1010,6 +1166,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "nibble_vec" version = "0.1.0" @@ -1025,7 +1191,7 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags", + "bitflags 2.6.0", "cfg-if", "libc", ] @@ -1143,6 +1309,7 @@ name = "numbat-cli" version = "1.14.0" dependencies = [ "anyhow", + "arboard", "assert_cmd", "clap", "colored", @@ -1166,23 +1333,110 @@ dependencies = [ ] [[package]] -name = "once_cell" -version = "1.20.2" +name = "objc-sys" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" [[package]] -name = "once_map" -version = "0.4.20" +name = "objc2" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed29bb6f7d6ac14023acb332a356f3891265d780e254057c866dbe7a909d2d2d" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" dependencies = [ - "ahash", - "hashbrown 0.15.0", - "parking_lot", - "stable_deref_trait", + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.6.0", + "block2", + "libc", + "objc2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-encode" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7891e71393cd1f227313c9379a26a584ff3d7e6e7159e988851f0934c993f0f8" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.6.0", + "block2", + "libc", + "objc2", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", ] +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + [[package]] name = "oorandom" version = "11.1.4" @@ -1282,6 +1536,19 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "png" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -1338,9 +1605,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.87" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] @@ -1429,7 +1696,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "bitflags", + "bitflags 2.6.0", ] [[package]] @@ -1445,9 +1712,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -1489,13 +1756,12 @@ dependencies = [ [[package]] name = "rinja" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28580fecce391f3c0e65a692e5f2b5db258ba2346ee04f355ae56473ab973dc" +checksum = "3dc4940d00595430b3d7d5a01f6222b5e5b51395d1120bdb28d854bb8abb17a5" dependencies = [ "humansize", "itoa", - "num-traits", "percent-encoding", "rinja_derive", "serde", @@ -1504,15 +1770,14 @@ dependencies = [ [[package]] name = "rinja_derive" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f1ae91455a4c82892d9513fcfa1ac8faff6c523602d0041536341882714aede" +checksum = "08d9ed0146aef6e2825f1b1515f074510549efba38d71f4554eec32eb36ba18b" dependencies = [ "basic-toml", "memchr", "mime", "mime_guess", - "once_map", "proc-macro2", "quote", "rinja_parser", @@ -1523,9 +1788,9 @@ dependencies = [ [[package]] name = "rinja_parser" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea17639e1f35032e1c67539856e498c04cd65fe2a45f55ec437ec55e4be941" +checksum = "93f9a866e2e00a7a1fb27e46e9e324a6f7c0e7edc4543cae1d38f4e4a100c610" dependencies = [ "memchr", "nom", @@ -1579,7 +1844,7 @@ version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ - "bitflags", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", @@ -1602,9 +1867,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" [[package]] name = "rustls-webpki" @@ -1619,9 +1884,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 = "rustyline" @@ -1629,7 +1894,7 @@ version = "13.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02a2d683a4ac90aeef5b1013933f6d977bd37d51ff3f4dad829d4931a7e6be86" dependencies = [ - "bitflags", + "bitflags 2.6.0", "cfg-if", "clipboard-win", "fd-lock", @@ -1686,18 +1951,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.210" +version = "1.0.213" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.213" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5" dependencies = [ "proc-macro2", "quote", @@ -1706,9 +1971,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.128" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "itoa", "memchr", @@ -1792,6 +2057,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "similar" version = "2.6.0" @@ -1810,12 +2081,6 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - [[package]] name = "static_assertions" version = "1.1.0" @@ -1842,9 +2107,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.79" +version = "2.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" dependencies = [ "proc-macro2", "quote", @@ -1888,24 +2153,35 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" [[package]] name = "thiserror" -version = "1.0.64" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "tiff" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + [[package]] name = "time" version = "0.3.36" @@ -2010,12 +2286,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicase" -version = "2.7.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] +checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" [[package]] name = "unicode-bidi" @@ -2184,6 +2457,12 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "weezl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + [[package]] name = "winapi" version = "0.3.9" @@ -2381,6 +2660,23 @@ dependencies = [ "memchr", ] +[[package]] +name = "x11rb" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" +dependencies = [ + "gethostname", + "rustix", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" + [[package]] name = "zerocopy" version = "0.7.35" diff --git a/numbat-cli/Cargo.toml b/numbat-cli/Cargo.toml index 582693364..37fdb39fd 100644 --- a/numbat-cli/Cargo.toml +++ b/numbat-cli/Cargo.toml @@ -23,6 +23,7 @@ toml = { version = "0.8.8", features = ["parse"] } serde = { version = "1.0.195", features = ["derive"] } terminal_size = "0.3.0" jiff = "0.1" +arboard = "3.4.1" [dependencies.clap] version = "4" diff --git a/numbat-cli/src/config.rs b/numbat-cli/src/config.rs index 5b98c0867..7514601c0 100644 --- a/numbat-cli/src/config.rs +++ b/numbat-cli/src/config.rs @@ -1,3 +1,4 @@ +use crate::copy_to_clipboard::NumericDisplayConfig; use clap::ValueEnum; use numbat::compact_str::CompactString; use serde::{Deserialize, Serialize}; @@ -66,6 +67,8 @@ pub struct Config { #[serde(skip_serializing)] pub load_user_init: bool, pub exchange_rates: ExchangeRateConfig, + + pub copy_result: NumericDisplayConfig, } impl Default for Config { @@ -79,6 +82,7 @@ impl Default for Config { load_user_init: true, exchange_rates: Default::default(), enter_repl: true, + copy_result: Default::default(), } } } diff --git a/numbat-cli/src/copy_to_clipboard.rs b/numbat-cli/src/copy_to_clipboard.rs new file mode 100644 index 000000000..59555e429 --- /dev/null +++ b/numbat-cli/src/copy_to_clipboard.rs @@ -0,0 +1,170 @@ +use numbat::{ + markup::Markup, num_format::CustomFormat, num_format::Grouping, pretty_dtoa::FmtFloatConfig, + pretty_print::PrettyPrint, value::Value, FloatDisplayConfigSource, RuntimeError, + UnitDisplayOptions, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, PartialEq, Eq, Default, Debug, Clone)] +#[serde(remote = "Grouping", rename_all = "kebab-case")] +pub enum IntGroupingConfig { + #[default] + Standard, + Indian, + #[serde(rename = "none")] + Posix, +} + +#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] +#[serde(rename_all = "kebab-case", default, deny_unknown_fields)] +pub struct IntDisplayConfig { + #[serde(with = "IntGroupingConfig")] + pub grouping: Grouping, + pub separator: String, + pub minus_sign: String, +} + +impl Default for IntDisplayConfig { + fn default() -> Self { + Self { + grouping: Grouping::Standard, + separator: ",".into(), + minus_sign: "-".into(), + } + } +} + +#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] +#[serde(rename_all = "kebab-case", default, deny_unknown_fields)] +pub struct FloatDisplayConfig { + pub decimal: char, + pub capitalize_e: bool, +} + +impl Default for FloatDisplayConfig { + fn default() -> Self { + Self { + decimal: '.', + capitalize_e: false, + } + } +} + +#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] +#[serde(rename_all = "kebab-case", default, deny_unknown_fields)] +pub struct UnitDisplayConfig { + fancy_exponents: bool, + multiplication_operator: char, + division_operator: char, + space_btwn_operators: bool, +} + +impl Default for UnitDisplayConfig { + fn default() -> Self { + Self { + fancy_exponents: false, + multiplication_operator: '·', + division_operator: '/', + space_btwn_operators: false, + } + } +} + +#[derive(Serialize, Deserialize, PartialEq, Eq, Default, Debug, Clone)] +#[serde(rename_all = "kebab-case", default, deny_unknown_fields)] +pub struct NumericDisplayConfig { + #[serde(serialize_with = "serialize_option_config")] + pub int_config: Option, + #[serde(serialize_with = "serialize_option_config")] + pub float_config: Option, + #[serde(serialize_with = "serialize_option_config")] + pub unit_config: Option, +} + +fn serialize_option_config( + value: &Option, + serializer: S, +) -> Result +where + S: serde::Serializer, +{ + match value { + Some(o) => o.serialize(serializer), + None => T::default().serialize(serializer), + } +} + +pub(crate) fn pretty_print_value( + v: &Value, + config: &NumericDisplayConfig, +) -> Result> { + Ok(match &v { + Value::Quantity(q) => { + let NumericDisplayConfig { + int_config, + float_config, + unit_config, + } = config; + + let float_options = float_config.as_ref().map(|c| { + let FloatDisplayConfig { + decimal, + capitalize_e, + } = *c; + + FmtFloatConfig::default() + .radix_point(decimal) + .capitalize_e(capitalize_e) + }); + + let int_options = int_config + .as_ref() + .map(|c| { + let int_config @ IntDisplayConfig { + grouping, + separator, + minus_sign, + } = c; + + CustomFormat::builder() + .grouping(*grouping) + .separator(separator) + .minus_sign(minus_sign) + .build() + .map_err(|err| { + Box::new(RuntimeError::IntegerDisplayConfig( + format!("{int_config:?}"), + err.to_string(), + )) + }) + }) + .transpose()?; + + let unit_options = unit_config.as_ref().map(|c| { + let &UnitDisplayConfig { + fancy_exponents, + multiplication_operator, + division_operator, + space_btwn_operators, + } = c; + UnitDisplayOptions { + fancy_exponents, + multiplication_operator, + division_operator, + space_btwn_operators, + } + }); + + q.pretty_print_with_options( + match float_options { + Some(o) => FloatDisplayConfigSource::UserConfig(o), + None => FloatDisplayConfigSource::Numbat(None), + }, + int_options, + unit_options, + ) + } + + v => v.pretty_print(), + }) +} diff --git a/numbat-cli/src/main.rs b/numbat-cli/src/main.rs index 3adefedaa..08be81448 100644 --- a/numbat-cli/src/main.rs +++ b/numbat-cli/src/main.rs @@ -1,12 +1,14 @@ mod ansi_formatter; mod completer; mod config; +mod copy_to_clipboard; mod highlighter; use ansi_formatter::ansi_format; use colored::control::SHOULD_COLORIZE; use completer::NumbatCompleter; use config::{ColorMode, Config, ExchangeRateFetchingPolicy, IntroBanner, PrettyPrintMode}; +use copy_to_clipboard::pretty_print_value; use highlighter::NumbatHighlighter; use itertools::Itertools; @@ -14,13 +16,15 @@ use numbat::command::{self, CommandParser, SourcelessCommandParser}; use numbat::compact_str::{CompactString, ToCompactString}; use numbat::diagnostic::ErrorDiagnostic; use numbat::help::help_markup; -use numbat::markup as m; +use numbat::markup::{Formatter, PlainTextFormatter}; use numbat::module_importer::{BuiltinModuleImporter, ChainedImporter, FileSystemImporter}; use numbat::pretty_print::PrettyPrint; use numbat::resolver::CodeSource; -use numbat::session_history::{ParseEvaluationResult, SessionHistory, SessionHistoryOptions}; +use numbat::session_history::{SessionHistory, SessionHistoryOptions}; +use numbat::value::Value; +use numbat::{markup as m, InterpreterResult}; use numbat::{Context, NumbatError}; -use numbat::{InterpreterSettings, NameResolutionError}; +use numbat::{InterpreterSettings, RuntimeError}; use anyhow::{bail, Context as AnyhowContext, Result}; use clap::Parser; @@ -98,7 +102,7 @@ struct Args { struct ParseEvaluationOutcome { control_flow: ControlFlow, - result: ParseEvaluationResult, + result: Result, } #[derive(Debug, Clone, Copy, PartialEq)] @@ -352,9 +356,21 @@ impl Cli { interactive: bool, ) -> Result<()> { let mut session_history = SessionHistory::default(); + let mut last_value = None::; + let mut clipboard = match arboard::Clipboard::new() { + Ok(cb) => Some(cb), + Err(_) => { + println!( + "error: could not initialize the clipboard, so + `copy` functionality will be disabled this session" + ); + None + } + }; loop { let readline = rl.readline(&self.config.prompt); + match readline { Ok(line) => { if !line.trim().is_empty() { @@ -408,6 +424,44 @@ impl Cli { }; println!("{}", ansi_format(&m, false)); } + command::Command::Copy => { + let Some(clipboard) = &mut clipboard else { + println!( + "error: as the clipboard could not \ + be initialized, `copy` functionality is \ + disabled for this session" + ); + continue; + }; + match &last_value { + Some(v) => { + let m = match pretty_print_value( + v, + &self.config.copy_result, + ) { + Ok(m) => m, + Err(err) => { + self.print_diagnostic(*err); + continue; + } + }; + + let text = PlainTextFormatter.format(&m, false); + if let Err(e) = clipboard.set_text(text) { + self.print_diagnostic( + RuntimeError::ClipboardError(e.to_string()), + ); + continue; + } + + println!( + "{} was copied to the clipboard", + ansi_format(&m, false) + ); + } + None => println!("error: no value to copy"), + } + } command::Command::Clear => rl.clear_screen()?, command::Command::Save { dst } => { let save_result = session_history.save( @@ -466,7 +520,15 @@ impl Cli { } } - session_history.push(CompactString::from(line), result); + match result { + Ok(result) => { + session_history.push(CompactString::from(line), Ok(())); + if let InterpreterResult::Value(value) = result { + last_value = Some(value); + } + } + Err(_) => session_history.push(CompactString::from(line), Err(())), + } } } Err(ReadlineError::Interrupted) => {} @@ -510,12 +572,7 @@ impl Cli { PrettyPrintMode::Auto => interactive, }; - let parse_eval_result = match &interpretation_result { - Ok(_) => Ok(()), - Err(_) => Err(()), - }; - - let control_flow = match interpretation_result.map_err(|b| *b) { + let (control_flow, result) = match interpretation_result.map_err(|b| *b) { Ok((statements, interpreter_result)) => { if interactive || pretty_print { println!(); @@ -551,32 +608,23 @@ impl Cli { println!(); } - ControlFlow::Continue(()) - } - Err(NumbatError::ResolverError(e)) => { - self.print_diagnostic(e); - execution_mode.exit_status_in_case_of_error() + (ControlFlow::Continue(()), Ok(interpreter_result)) } - Err(NumbatError::NameResolutionError( - e @ (NameResolutionError::IdentifierClash { .. } - | NameResolutionError::ReservedIdentifier(_)), - )) => { - self.print_diagnostic(e); - execution_mode.exit_status_in_case_of_error() - } - Err(NumbatError::TypeCheckError(e)) => { - self.print_diagnostic(e); - execution_mode.exit_status_in_case_of_error() - } - Err(NumbatError::RuntimeError(e)) => { - self.print_diagnostic(e); - execution_mode.exit_status_in_case_of_error() + Err(err) => { + match err { + NumbatError::ResolverError(e) => self.print_diagnostic(e), + NumbatError::NameResolutionError(e) => self.print_diagnostic(e), + NumbatError::TypeCheckError(e) => self.print_diagnostic(e), + NumbatError::RuntimeError(e) => self.print_diagnostic(e), + } + + (execution_mode.exit_status_in_case_of_error(), Err(())) } }; ParseEvaluationOutcome { control_flow, - result: parse_eval_result, + result, } } diff --git a/numbat/src/arithmetic.rs b/numbat/src/arithmetic.rs index 2d6b683f4..798402f8e 100644 --- a/numbat/src/arithmetic.rs +++ b/numbat/src/arithmetic.rs @@ -17,29 +17,39 @@ pub trait Power { } pub fn pretty_exponent(e: &Exponent) -> CompactString { - if e == &Ratio::from_integer(5) { - CompactString::const_new("⁵") + CompactString::const_new(if e == &Ratio::from_integer(5) { + "⁵" } else if e == &Ratio::from_integer(4) { - CompactString::const_new("⁴") + "⁴" } else if e == &Ratio::from_integer(3) { - CompactString::const_new("³") + "³" } else if e == &Ratio::from_integer(2) { - CompactString::const_new("²") - } else if e == &Ratio::from_integer(1) { - CompactString::const_new("") - } else if e == &Ratio::from_integer(-1) { - CompactString::const_new("⁻¹") + "²" + } + // 1 handled by ugly_exponent + else if e == &Ratio::from_integer(-1) { + "⁻¹" } else if e == &Ratio::from_integer(-2) { - CompactString::const_new("⁻²") + "⁻²" } else if e == &Ratio::from_integer(-3) { - CompactString::const_new("⁻³") + "⁻³" } else if e == &Ratio::from_integer(-4) { - CompactString::const_new("⁻⁴") + "⁻⁴" } else if e == &Ratio::from_integer(-5) { - CompactString::const_new("⁻⁵") + "⁻⁵" + } else { + return ugly_exponent(e); + }) +} + +pub fn ugly_exponent(e: &Exponent) -> CompactString { + if e == &Ratio::from_integer(1) { + CompactString::const_new("") } else if e.is_positive() && e.is_integer() { format_compact!("^{e}") } else { format_compact!("^({e})") } } + +// pub trait diff --git a/numbat/src/command.rs b/numbat/src/command.rs index 197a71d9b..3dc5ba0a2 100644 --- a/numbat/src/command.rs +++ b/numbat/src/command.rs @@ -23,6 +23,7 @@ enum CommandKind { Help, Info, List, + Copy, Clear, Save, Quit(QuitAlias), @@ -37,6 +38,7 @@ impl FromStr for CommandKind { "help" | "?" => Help, "info" => Info, "list" => List, + "copy" => Copy, "clear" => Clear, "save" => Save, "quit" => Quit(QuitAlias::Quit), @@ -51,6 +53,7 @@ pub enum Command<'a> { Help, Info { item: &'a str }, List { items: Option }, + Copy, Clear, Save { dst: &'a str }, Quit, @@ -212,6 +215,10 @@ impl<'a> CommandParser<'a> { Command::Quit } + CommandKind::Copy => { + self.ensure_zero_args("copy", "")?; + Command::Copy + } CommandKind::Info => { let err_msg = "`info` requires exactly one argument, the item to get info on"; let Some(item) = self.inner.args.next() else { @@ -248,6 +255,7 @@ impl<'a> CommandParser<'a> { Command::List { items } } + CommandKind::Save => { let Some(dst) = self.inner.args.next() else { return Ok(Command::Save { dst: "history.nbt" }); diff --git a/numbat/src/interpreter/mod.rs b/numbat/src/interpreter/mod.rs index 208287473..099f9becf 100644 --- a/numbat/src/interpreter/mod.rs +++ b/numbat/src/interpreter/mod.rs @@ -64,6 +64,11 @@ pub enum RuntimeError { #[error("Could not write to file: {0:?}")] FileWrite(std::path::PathBuf), + + #[error("Invalid integer display config: {0}. Originated from: {1}")] + IntegerDisplayConfig(String, String), + #[error("Could not access the clipboard. Original error: {0}")] + ClipboardError(String), } #[derive(Debug, PartialEq, Eq)] diff --git a/numbat/src/lib.rs b/numbat/src/lib.rs index d70994103..8f4631f5b 100644 --- a/numbat/src/lib.rs +++ b/numbat/src/lib.rs @@ -82,12 +82,18 @@ pub use registry::BaseRepresentationFactor; pub use typed_ast::Statement; pub use typed_ast::Type; use unit::BaseUnitAndFactor; +use unit::UnitFactor; use unit_registry::UnitMetadata; use crate::prefix_parser::PrefixParserResult; use crate::unicode_input::UNICODE_INPUT; +pub use number::FloatDisplayConfigSource; +pub use quantity::UnitDisplayOptions; + pub use compact_str; +pub use num_format; +pub use pretty_dtoa; #[derive(Debug, Clone, Error)] pub enum NumbatError { @@ -430,6 +436,7 @@ impl Context { 'x', '/', true, + None:: _>, Some(m::FormatType::Unit), ); } else { diff --git a/numbat/src/number.rs b/numbat/src/number.rs index 8e119fca5..3a41a4002 100644 --- a/numbat/src/number.rs +++ b/numbat/src/number.rs @@ -33,13 +33,17 @@ impl Number { /// Pretty prints with default options pub fn pretty_print(self) -> CompactString { - self.pretty_print_with_options(None) + self.pretty_print_with_options(FloatDisplayConfigSource::Numbat(None), None) } /// Pretty prints with the given options if options is not None. /// If options is None, default options will be used. /// If options is not None, float-based format handling is used and integer-based format handling is skipped. - pub fn pretty_print_with_options(self, options: Option) -> CompactString { + pub fn pretty_print_with_options( + self, + float_options: FloatDisplayConfigSource, + int_options: Option, + ) -> CompactString { let number = self.0; // 64-bit floats can accurately represent integers up to 2^52 [1], @@ -48,19 +52,24 @@ impl Number { // [1] https://stackoverflow.com/a/43656339 // // Skip special format handling for integers if options is not None. - if options.is_none() && self.is_integer() && self.0.abs() < 1e15 { + if !matches!(float_options, FloatDisplayConfigSource::Numbat(Some(_))) + && self.is_integer() + && self.0.abs() < 1e15 + { use num_format::{CustomFormat, Grouping, ToFormattedString}; - let format = CustomFormat::builder() - .grouping(if self.0.abs() >= 100_000.0 { - Grouping::Standard - } else { - Grouping::Posix - }) - .minus_sign("-") - .separator("_") - .build() - .unwrap(); + let format = int_options.unwrap_or_else(|| { + CustomFormat::builder() + .grouping(if self.0.abs() >= 100_000.0 { + Grouping::Standard + } else { + Grouping::Posix + }) + .minus_sign("-") + .separator("_") + .build() + .unwrap() + }); // TODO: this is pretty wasteful. formatted numbers should be small enough // to fit in a CompactString without first going to the heap @@ -72,16 +81,19 @@ impl Number { } else { use pretty_dtoa::dtoa; - let config = if let Some(options) = options { - options - } else { + let float_options = match float_options { + FloatDisplayConfigSource::Numbat(opts) => opts, + FloatDisplayConfigSource::UserConfig(opts) => Some(opts), + }; + + let config = float_options.unwrap_or_else(|| { FmtFloatConfig::default() .max_significant_digits(6) .add_point_zero(false) .lower_e_break(-6) .upper_e_break(6) .round() - }; + }); // TODO: wasteful to go through a String here let formatted_number = dtoa(number, config).to_compact_string(); @@ -159,6 +171,11 @@ impl std::iter::Product for Number { } } +pub enum FloatDisplayConfigSource { + Numbat(Option), + UserConfig(FmtFloatConfig), +} + #[test] fn test_pretty_print() { assert_eq!(Number::from_f64(1.).pretty_print(), "1"); diff --git a/numbat/src/prefix.rs b/numbat/src/prefix.rs index 53020d39a..76e375385 100644 --- a/numbat/src/prefix.rs +++ b/numbat/src/prefix.rs @@ -105,90 +105,90 @@ impl Prefix { } pub fn as_string_short(&self) -> CompactString { - match self { - Prefix::Metric(-30) => CompactString::const_new("q"), - Prefix::Metric(-27) => CompactString::const_new("r"), - Prefix::Metric(-24) => CompactString::const_new("y"), - Prefix::Metric(-21) => CompactString::const_new("z"), - Prefix::Metric(-18) => CompactString::const_new("a"), - Prefix::Metric(-15) => CompactString::const_new("f"), - Prefix::Metric(-12) => CompactString::const_new("p"), - Prefix::Metric(-9) => CompactString::const_new("n"), - Prefix::Metric(-6) => CompactString::const_new("µ"), - Prefix::Metric(-3) => CompactString::const_new("m"), - Prefix::Metric(-2) => CompactString::const_new("c"), - Prefix::Metric(-1) => CompactString::const_new("d"), - Prefix::Metric(0) => CompactString::const_new(""), - Prefix::Metric(1) => CompactString::const_new("da"), - Prefix::Metric(2) => CompactString::const_new("h"), - Prefix::Metric(3) => CompactString::const_new("k"), - Prefix::Metric(6) => CompactString::const_new("M"), - Prefix::Metric(9) => CompactString::const_new("G"), - Prefix::Metric(12) => CompactString::const_new("T"), - Prefix::Metric(15) => CompactString::const_new("P"), - Prefix::Metric(18) => CompactString::const_new("E"), - Prefix::Metric(21) => CompactString::const_new("Z"), - Prefix::Metric(24) => CompactString::const_new("Y"), - Prefix::Metric(27) => CompactString::const_new("R"), - Prefix::Metric(30) => CompactString::const_new("Q"), - - Prefix::Metric(n) => format_compact!(""), - - Prefix::Binary(0) => CompactString::const_new(""), - Prefix::Binary(10) => CompactString::const_new("Ki"), - Prefix::Binary(20) => CompactString::const_new("Mi"), - Prefix::Binary(30) => CompactString::const_new("Gi"), - Prefix::Binary(40) => CompactString::const_new("Ti"), - Prefix::Binary(50) => CompactString::const_new("Pi"), - Prefix::Binary(60) => CompactString::const_new("Ei"), - Prefix::Binary(70) => CompactString::const_new("Zi"), - Prefix::Binary(80) => CompactString::const_new("Yi"), - - Prefix::Binary(n) => format_compact!(""), - } + CompactString::const_new(match self { + Prefix::Metric(-30) => "q", + Prefix::Metric(-27) => "r", + Prefix::Metric(-24) => "y", + Prefix::Metric(-21) => "z", + Prefix::Metric(-18) => "a", + Prefix::Metric(-15) => "f", + Prefix::Metric(-12) => "p", + Prefix::Metric(-9) => "n", + Prefix::Metric(-6) => "µ", + Prefix::Metric(-3) => "m", + Prefix::Metric(-2) => "c", + Prefix::Metric(-1) => "d", + Prefix::Metric(0) => "", + Prefix::Metric(1) => "da", + Prefix::Metric(2) => "h", + Prefix::Metric(3) => "k", + Prefix::Metric(6) => "M", + Prefix::Metric(9) => "G", + Prefix::Metric(12) => "T", + Prefix::Metric(15) => "P", + Prefix::Metric(18) => "E", + Prefix::Metric(21) => "Z", + Prefix::Metric(24) => "Y", + Prefix::Metric(27) => "R", + Prefix::Metric(30) => "Q", + + Prefix::Metric(n) => return format_compact!(""), + + Prefix::Binary(0) => "", + Prefix::Binary(10) => "Ki", + Prefix::Binary(20) => "Mi", + Prefix::Binary(30) => "Gi", + Prefix::Binary(40) => "Ti", + Prefix::Binary(50) => "Pi", + Prefix::Binary(60) => "Ei", + Prefix::Binary(70) => "Zi", + Prefix::Binary(80) => "Yi", + + Prefix::Binary(n) => return format_compact!(""), + }) } pub fn as_string_long(&self) -> CompactString { - match self { - Prefix::Metric(-30) => CompactString::const_new("quecto"), - Prefix::Metric(-27) => CompactString::const_new("ronto"), - Prefix::Metric(-24) => CompactString::const_new("yocto"), - Prefix::Metric(-21) => CompactString::const_new("zepto"), - Prefix::Metric(-18) => CompactString::const_new("atto"), - Prefix::Metric(-15) => CompactString::const_new("femto"), - Prefix::Metric(-12) => CompactString::const_new("pico"), - Prefix::Metric(-9) => CompactString::const_new("nano"), - Prefix::Metric(-6) => CompactString::const_new("micro"), - Prefix::Metric(-3) => CompactString::const_new("milli"), - Prefix::Metric(-2) => CompactString::const_new("centi"), - Prefix::Metric(-1) => CompactString::const_new("deci"), - Prefix::Metric(0) => CompactString::const_new(""), - Prefix::Metric(1) => CompactString::const_new("deca"), - Prefix::Metric(2) => CompactString::const_new("hecto"), - Prefix::Metric(3) => CompactString::const_new("kilo"), - Prefix::Metric(6) => CompactString::const_new("mega"), - Prefix::Metric(9) => CompactString::const_new("giga"), - Prefix::Metric(12) => CompactString::const_new("tera"), - Prefix::Metric(15) => CompactString::const_new("peta"), - Prefix::Metric(18) => CompactString::const_new("exa"), - Prefix::Metric(21) => CompactString::const_new("zetta"), - Prefix::Metric(24) => CompactString::const_new("yotta"), - Prefix::Metric(27) => CompactString::const_new("ronna"), - Prefix::Metric(30) => CompactString::const_new("quetta"), - - Prefix::Metric(n) => format_compact!(""), - - Prefix::Binary(0) => CompactString::const_new(""), - Prefix::Binary(10) => CompactString::const_new("kibi"), - Prefix::Binary(20) => CompactString::const_new("mebi"), - Prefix::Binary(30) => CompactString::const_new("gibi"), - Prefix::Binary(40) => CompactString::const_new("tebi"), - Prefix::Binary(50) => CompactString::const_new("pebi"), - Prefix::Binary(60) => CompactString::const_new("exbi"), - Prefix::Binary(70) => CompactString::const_new("zebi"), - Prefix::Binary(80) => CompactString::const_new("yobi"), - - Prefix::Binary(n) => format_compact!(""), - } + CompactString::const_new(match self { + Prefix::Metric(-30) => "quecto", + Prefix::Metric(-27) => "ronto", + Prefix::Metric(-24) => "yocto", + Prefix::Metric(-21) => "zepto", + Prefix::Metric(-18) => "atto", + Prefix::Metric(-15) => "femto", + Prefix::Metric(-12) => "pico", + Prefix::Metric(-9) => "nano", + Prefix::Metric(-6) => "micro", + Prefix::Metric(-3) => "milli", + Prefix::Metric(-2) => "centi", + Prefix::Metric(-1) => "deci", + Prefix::Metric(0) => "", + Prefix::Metric(1) => "deca", + Prefix::Metric(2) => "hecto", + Prefix::Metric(3) => "kilo", + Prefix::Metric(6) => "mega", + Prefix::Metric(9) => "giga", + Prefix::Metric(12) => "tera", + Prefix::Metric(15) => "peta", + Prefix::Metric(18) => "exa", + Prefix::Metric(21) => "zetta", + Prefix::Metric(24) => "yotta", + Prefix::Metric(27) => "ronna", + Prefix::Metric(30) => "quetta", + + Prefix::Metric(n) => return format_compact!(""), + + Prefix::Binary(0) => "", + Prefix::Binary(10) => "kibi", + Prefix::Binary(20) => "mebi", + Prefix::Binary(30) => "gibi", + Prefix::Binary(40) => "tebi", + Prefix::Binary(50) => "pebi", + Prefix::Binary(60) => "exbi", + Prefix::Binary(70) => "zebi", + Prefix::Binary(80) => "yobi", + + Prefix::Binary(n) => return format_compact!(""), + }) } } diff --git a/numbat/src/product.rs b/numbat/src/product.rs index 7050f3a29..732fac755 100644 --- a/numbat/src/product.rs +++ b/numbat/src/product.rs @@ -32,6 +32,7 @@ impl CompactString>, format_type: Option, ) -> m::Markup where @@ -52,7 +53,12 @@ impl _>, None, ), false, diff --git a/numbat/src/quantity.rs b/numbat/src/quantity.rs index ffb9bc2bd..a92ba5327 100644 --- a/numbat/src/quantity.rs +++ b/numbat/src/quantity.rs @@ -1,7 +1,9 @@ use crate::arithmetic::{Exponent, Power, Rational}; +use crate::markup::{Formatter, PlainTextFormatter}; use crate::number::Number; use crate::pretty_print::PrettyPrint; use crate::unit::{is_multiple_of, Unit, UnitFactor}; +use crate::FloatDisplayConfigSource; use compact_str::{format_compact, CompactString, ToCompactString}; use itertools::Itertools; @@ -343,19 +345,51 @@ impl PartialOrd for Quantity { impl PrettyPrint for Quantity { fn pretty_print(&self) -> crate::markup::Markup { - self.pretty_print_with_options(None) + self.pretty_print_with_options(FloatDisplayConfigSource::Numbat(None), None, None) } } impl Quantity { /// Pretty prints with the given options. /// If options is None, default options will be used. - fn pretty_print_with_options(&self, options: Option) -> crate::markup::Markup { + pub fn pretty_print_with_options( + &self, + float_options: FloatDisplayConfigSource, + int_options: Option, + unit_options: Option, + ) -> crate::markup::Markup { use crate::markup; - let formatted_number = self.unsafe_value().pretty_print_with_options(options); + let formatted_number = self + .unsafe_value() + .pretty_print_with_options(float_options, int_options); - let unit_str = format_compact!("{}", self.unit()); + let unit_str = if let Some(UnitDisplayOptions { + fancy_exponents, + multiplication_operator, + division_operator, + space_btwn_operators, + }) = unit_options + { + let m = self.unit().pretty_print_with( + |f| f.exponent, + multiplication_operator, + division_operator, + space_btwn_operators, + Some(|unit: &UnitFactor| { + if fancy_exponents { + format_compact!("{unit}") + } else { + unit.to_ugly_string() + } + }), + None, + ); + + PlainTextFormatter.format(&m, false) + } else { + format_compact!("{}", self.unit()) + }; markup::value(formatted_number) + if unit_str == "°" || unit_str == "′" || unit_str == "″" || unit_str.is_empty() { @@ -375,7 +409,7 @@ impl Quantity { .add_point_zero(false) .force_no_e_notation() .round(); - self.pretty_print_with_options(Some(options)) + self.pretty_print_with_options(FloatDisplayConfigSource::Numbat(Some(options)), None, None) } pub fn unsafe_value_as_string(&self) -> CompactString { @@ -393,6 +427,13 @@ impl std::fmt::Display for Quantity { } } +pub struct UnitDisplayOptions { + pub fancy_exponents: bool, + pub multiplication_operator: char, + pub division_operator: char, + pub space_btwn_operators: bool, +} + #[cfg(test)] mod tests { use compact_str::CompactString; diff --git a/numbat/src/registry.rs b/numbat/src/registry.rs index 8299c8978..acc520731 100644 --- a/numbat/src/registry.rs +++ b/numbat/src/registry.rs @@ -75,7 +75,14 @@ impl PrettyPrint for BaseRepresentation { if self.iter().count() == 0 { crate::markup::type_identifier("Scalar") } else { - self.pretty_print_with(|f| f.1, '×', '/', true, None) + self.pretty_print_with( + |f| f.1, + '×', + '/', + true, + None:: _>, + None, + ) } } } diff --git a/numbat/src/unit.rs b/numbat/src/unit.rs index e586d8d40..05a856224 100644 --- a/numbat/src/unit.rs +++ b/numbat/src/unit.rs @@ -1,11 +1,11 @@ use std::{fmt::Display, ops::Div}; -use compact_str::{CompactString, ToCompactString}; +use compact_str::{format_compact, CompactString, ToCompactString}; use itertools::Itertools; use num_traits::{ToPrimitive, Zero}; use crate::{ - arithmetic::{pretty_exponent, Exponent, Power, Rational}, + arithmetic::{pretty_exponent, ugly_exponent, Exponent, Power, Rational}, number::Number, prefix::Prefix, prefix_parser::AcceptsPrefix, @@ -173,6 +173,26 @@ pub struct UnitFactor { pub exponent: Exponent, } +impl UnitFactor { + // TODO: unify this implementation with `Display::fmt` (they're nearly identical) + /// Get this unit as a String, but without fancy Unicode formatting of exponents. + /// All exponents look like `^1`, `^-2`, etc. + pub fn to_ugly_string(&self) -> CompactString { + let prefix = if self.unit_id.canonical_name.accepts_prefix.short { + self.prefix.as_string_short() + } else { + self.prefix.as_string_long() + }; + + format_compact!( + "{}{}{}", + prefix, + self.unit_id.canonical_name.name, + ugly_exponent(&self.exponent) + ) + } +} + impl Canonicalize for UnitFactor { type MergeKey = (Prefix, UnitIdentifier);