зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1688784 - Bump mp4parse-rust version. r=jbauman,kinetik
Bumps version to 3011a2b923c8b0f1b392bcdd008cd8b95ffd846b This is done to fix a bug where parsing bad metadata in the userdata (udta) would be fatal when we should instead just fail the userdata. This also updates some mp4parse-rust dependencies which results in some removals. Differential Revision: https://phabricator.services.mozilla.com/D102997
This commit is contained in:
Родитель
cab4dac2a5
Коммит
9bdd5b4443
|
@ -30,7 +30,7 @@ tag = "v0.4.21"
|
||||||
[source."https://github.com/mozilla/mp4parse-rust"]
|
[source."https://github.com/mozilla/mp4parse-rust"]
|
||||||
git = "https://github.com/mozilla/mp4parse-rust"
|
git = "https://github.com/mozilla/mp4parse-rust"
|
||||||
replace-with = "vendored-sources"
|
replace-with = "vendored-sources"
|
||||||
rev = "3d9efdc868ce8c5767cea28708fa6512c0ab6d17"
|
rev = "3011a2b923c8b0f1b392bcdd008cd8b95ffd846b"
|
||||||
|
|
||||||
[source."https://github.com/mozilla/application-services"]
|
[source."https://github.com/mozilla/application-services"]
|
||||||
git = "https://github.com/mozilla/application-services"
|
git = "https://github.com/mozilla/application-services"
|
||||||
|
|
|
@ -223,7 +223,7 @@ dependencies = [
|
||||||
"bindgen",
|
"bindgen",
|
||||||
"cranelift-codegen",
|
"cranelift-codegen",
|
||||||
"cranelift-wasm",
|
"cranelift-wasm",
|
||||||
"env_logger 0.8.2",
|
"env_logger",
|
||||||
"log",
|
"log",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
@ -1310,19 +1310,6 @@ dependencies = [
|
||||||
"packed_simd",
|
"packed_simd",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "env_logger"
|
|
||||||
version = "0.7.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
|
|
||||||
dependencies = [
|
|
||||||
"atty",
|
|
||||||
"humantime 1.3.0",
|
|
||||||
"log",
|
|
||||||
"regex",
|
|
||||||
"termcolor",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "env_logger"
|
name = "env_logger"
|
||||||
version = "0.8.2"
|
version = "0.8.2"
|
||||||
|
@ -1330,7 +1317,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f26ecb66b4bdca6c1409b40fb255eefc2bd4f6d135dab3c3124f80ffa2a9661e"
|
checksum = "f26ecb66b4bdca6c1409b40fb255eefc2bd4f6d135dab3c3124f80ffa2a9661e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atty",
|
"atty",
|
||||||
"humantime 2.0.1",
|
"humantime",
|
||||||
"log",
|
"log",
|
||||||
"regex",
|
"regex",
|
||||||
"termcolor",
|
"termcolor",
|
||||||
|
@ -1786,7 +1773,7 @@ name = "gecko_logger"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"app_services_logger",
|
"app_services_logger",
|
||||||
"env_logger 0.8.2",
|
"env_logger",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
]
|
]
|
||||||
|
@ -2385,15 +2372,6 @@ version = "1.3.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e8734b0cfd3bc3e101ec59100e101c2eecd19282202e87808b3037b442777a83"
|
checksum = "e8734b0cfd3bc3e101ec59100e101c2eecd19282202e87808b3037b442777a83"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "humantime"
|
|
||||||
version = "1.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
|
|
||||||
dependencies = [
|
|
||||||
"quick-error",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "humantime"
|
name = "humantime"
|
||||||
version = "2.0.1"
|
version = "2.0.1"
|
||||||
|
@ -2579,7 +2557,7 @@ version = "0.1.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bindgen",
|
"bindgen",
|
||||||
"cmake",
|
"cmake",
|
||||||
"env_logger 0.8.2",
|
"env_logger",
|
||||||
"glob",
|
"glob",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -3330,12 +3308,12 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mp4parse"
|
name = "mp4parse"
|
||||||
version = "0.11.4"
|
version = "0.11.5"
|
||||||
source = "git+https://github.com/mozilla/mp4parse-rust?rev=3d9efdc868ce8c5767cea28708fa6512c0ab6d17#3d9efdc868ce8c5767cea28708fa6512c0ab6d17"
|
source = "git+https://github.com/mozilla/mp4parse-rust?rev=3011a2b923c8b0f1b392bcdd008cd8b95ffd846b#3011a2b923c8b0f1b392bcdd008cd8b95ffd846b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitreader",
|
"bitreader",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"env_logger 0.7.1",
|
"env_logger",
|
||||||
"fallible_collections",
|
"fallible_collections",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
"log",
|
"log",
|
||||||
|
@ -3349,8 +3327,8 @@ version = "0.1.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mp4parse_capi"
|
name = "mp4parse_capi"
|
||||||
version = "0.11.4"
|
version = "0.11.5"
|
||||||
source = "git+https://github.com/mozilla/mp4parse-rust?rev=3d9efdc868ce8c5767cea28708fa6512c0ab6d17#3d9efdc868ce8c5767cea28708fa6512c0ab6d17"
|
source = "git+https://github.com/mozilla/mp4parse-rust?rev=3011a2b923c8b0f1b392bcdd008cd8b95ffd846b#3011a2b923c8b0f1b392bcdd008cd8b95ffd846b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"fallible_collections",
|
"fallible_collections",
|
||||||
|
@ -3395,7 +3373,7 @@ version = "0.4.21"
|
||||||
source = "git+https://github.com/mozilla/neqo?tag=v0.4.21#0e6040cd6385c038f8b7f604bcb09ad74d29e0d6"
|
source = "git+https://github.com/mozilla/neqo?tag=v0.4.21#0e6040cd6385c038f8b7f604bcb09ad74d29e0d6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"env_logger 0.8.2",
|
"env_logger",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
"qlog",
|
"qlog",
|
||||||
|
@ -3634,11 +3612,11 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.10"
|
version = "0.2.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4"
|
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg 0.1.6",
|
"autocfg 1.0.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3711,7 +3689,7 @@ version = "0.1.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"core-foundation",
|
"core-foundation",
|
||||||
"env_logger 0.8.2",
|
"env_logger",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libloading 0.5.2",
|
"libloading 0.5.2",
|
||||||
"log",
|
"log",
|
||||||
|
@ -3981,7 +3959,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "50ce7c785e06e3a9e6f546c1a30d3d59111a31a21bc294fb1496241a572c9a00"
|
checksum = "50ce7c785e06e3a9e6f546c1a30d3d59111a31a21bc294fb1496241a572c9a00"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.10.1",
|
"base64 0.10.1",
|
||||||
"humantime 2.0.1",
|
"humantime",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"line-wrap",
|
"line-wrap",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -4804,7 +4782,7 @@ name = "smoosh"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"env_logger 0.8.2",
|
"env_logger",
|
||||||
"jsparagus",
|
"jsparagus",
|
||||||
"log",
|
"log",
|
||||||
]
|
]
|
||||||
|
@ -5004,7 +4982,7 @@ dependencies = [
|
||||||
"atomic_refcell",
|
"atomic_refcell",
|
||||||
"cssparser",
|
"cssparser",
|
||||||
"cstr",
|
"cstr",
|
||||||
"env_logger 0.8.2",
|
"env_logger",
|
||||||
"geckoservo",
|
"geckoservo",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
{"files":{"CHANGELOG.md":"7c044d74477515ab39287a4caff27eb96daebaed8b9f9b6a1d1c081a7b42d4a7","Cargo.lock":"b1394b6c58241027832cc714a0754902d82aa1f6923ab478c318739462e565ca","Cargo.toml":"2961879155d753ba90ecd98c17875c82007a6973c95867e86bc1ec5bd4f5db41","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb","README.md":"0bf17650e07b88f1486f033643c1e82517caa69410e6faeaa352782d9231d63e","examples/custom_default_format.rs":"ae18cd0e765cf1f16568f9879925861d6f004481f955b58af5ed8fd04b0fca99","examples/custom_format.rs":"b0f41b7a3e6fe7582871281f4244c62c66b0d724bfc678907f67185a784e82b4","examples/custom_logger.rs":"6eeef506681a46925117e8f89395cdf4fea60a0d1f6a420e51768e790272dcde","examples/default.rs":"7ed1c6a8a8fe457a86676bd3a75c07d4ec7fb54147cf2825c9d299a5878a24cd","examples/direct_logger.rs":"ee20c25379c396e5e74e963290a4d8773a86f3fe10193f61fb1efd1c7271faf4","examples/filters_from_code.rs":"7f007b0dfa5a3964f839134824dc3684bf2f3c3d7b4c36c580cd029df5f9308b","src/filter/mod.rs":"5da7e51e7b77efdd4d2b5445e5d0264be2c897909d0f86eb553e16611307aed2","src/filter/regex.rs":"bdf875bac25e089e1e462f5dd01a88678067c24118ecd6268561c6a6af39747d","src/filter/string.rs":"fac54d51189fc0b5d2bff334b7a7e465177b431e3428299e345e1f90062d832e","src/fmt/humantime/extern_impl.rs":"f3087b29eedb8b4d5573621ad206e48a2eac72a77277be3b0e631d7dc9fb7a2e","src/fmt/humantime/mod.rs":"f4111c26cf2ffb85c1d639bd7674d55af7e1736e7e98c52f7be3070046a3253f","src/fmt/humantime/shim_impl.rs":"cce9a252abd5952fa109a72b1dfb85a593d237e22606b2b608a32c69184560e9","src/fmt/mod.rs":"4ab11971a73eb5fe9b40f0bca6dfc404321dd9e2ffcf87d911408e7183dc8362","src/fmt/writer/atty.rs":"69d9dd26c430000cd2d40f9c68b2e77cd492fec22921dd2c16864301252583e0","src/fmt/writer/mod.rs":"1e0feb4dee3ee86c4c24f49566673e99ec85765869105a07a2fc7436d7640cfe","src/fmt/writer/termcolor/extern_impl.rs":"89e9f2e66b914ddc960ad9a4355265a5db5d7be410b139cf2b54ca99207374a7","src/fmt/writer/termcolor/mod.rs":"a790f9391a50cd52be6823e3e55942de13a8d12e23d63765342ae9e8dd6d091c","src/fmt/writer/termcolor/shim_impl.rs":"d93786671d6a89fc2912f77f04b8cb0b82d67277d255d15ac31bfc1bc4464e30","src/lib.rs":"3cbc4f4d3fe51c43fc45a2f435c141f0de5b40b65ba0d2c7d16bb58c04d10898","tests/init-twice-retains-filter.rs":"be5cd2132342d89ede1f5c4266173bb3c4d51cc22a1847f133d299a1c5430ccb","tests/log-in-log.rs":"29fecc65c1e0d1c22d79c97e7ca843ad44a91f27934148d7a05c48899a3f39d8","tests/log_tls_dtors.rs":"7320667d774a9b05037f7bf273fb2574dec0705707692a9cd2f46f4cd5bc68dd","tests/regexp_filter.rs":"a84263c995b534b6479a1d0abadf63f4f0264958ff86d9173d6b2139b82c4dc5"},"package":"44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"}
|
|
|
@ -1,3 +0,0 @@
|
||||||
Changes to this crate are tracked via [GitHub Releases][releases].
|
|
||||||
|
|
||||||
[releases]: https://github.com/sebasmagri/env_logger/releases
|
|
|
@ -1,212 +0,0 @@
|
||||||
# This file is automatically @generated by Cargo.
|
|
||||||
# It is not intended for manual editing.
|
|
||||||
[[package]]
|
|
||||||
name = "aho-corasick"
|
|
||||||
version = "0.6.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"memchr 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "atty"
|
|
||||||
version = "0.2.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cfg-if"
|
|
||||||
version = "0.1.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "env_logger"
|
|
||||||
version = "0.7.1"
|
|
||||||
dependencies = [
|
|
||||||
"atty 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "humantime"
|
|
||||||
version = "1.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lazy_static"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "libc"
|
|
||||||
version = "0.2.40"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "log"
|
|
||||||
version = "0.4.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "memchr"
|
|
||||||
version = "2.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "quick-error"
|
|
||||||
version = "1.2.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "redox_syscall"
|
|
||||||
version = "0.1.37"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "redox_termios"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"memchr 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex-syntax"
|
|
||||||
version = "0.6.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "termcolor"
|
|
||||||
version = "1.0.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "termion"
|
|
||||||
version = "1.5.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "thread_local"
|
|
||||||
version = "0.3.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ucd-util"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "utf8-ranges"
|
|
||||||
version = "1.0.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "version_check"
|
|
||||||
version = "0.1.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi"
|
|
||||||
version = "0.3.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi-i686-pc-windows-gnu"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi-util"
|
|
||||||
version = "0.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi-x86_64-pc-windows-gnu"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wincolor"
|
|
||||||
version = "1.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[metadata]
|
|
||||||
"checksum aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1e9a933f4e58658d7b12defcf96dc5c720f20832deebe3e0a19efd3b6aaeeb9e"
|
|
||||||
"checksum atty 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "af80143d6f7608d746df1520709e5d141c96f240b0e62b0aa41bdfb53374d9d4"
|
|
||||||
"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4"
|
|
||||||
"checksum humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
|
|
||||||
"checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d"
|
|
||||||
"checksum libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "6fd41f331ac7c5b8ac259b8bf82c75c0fb2e469bbf37d2becbba9a6a2221965b"
|
|
||||||
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
|
|
||||||
"checksum memchr 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0a3eb002f0535929f1199681417029ebea04aadc0c7a4224b46be99c7f5d6a16"
|
|
||||||
"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
|
|
||||||
"checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd"
|
|
||||||
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
|
|
||||||
"checksum regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "37e7cbbd370869ce2e8dff25c7018702d10b21a20ef7135316f8daecd6c25b7f"
|
|
||||||
"checksum regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8c2f35eedad5295fdf00a63d7d4b238135723f92b434ec06774dad15c7ab0861"
|
|
||||||
"checksum termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4096add70612622289f2fdcdbd5086dc81c1e2675e6ae58d6c4f62a16c6d7f2f"
|
|
||||||
"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
|
|
||||||
"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
|
|
||||||
"checksum ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd2be2d6639d0f8fe6cdda291ad456e23629558d466e2789d2c3e9892bda285d"
|
|
||||||
"checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737"
|
|
||||||
"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
|
|
||||||
"checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3"
|
|
||||||
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
|
||||||
"checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9"
|
|
||||||
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
|
||||||
"checksum wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "561ed901ae465d6185fa7864d63fbd5720d0ef718366c9a4dc83cf6170d7e9ba"
|
|
|
@ -1,62 +0,0 @@
|
||||||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
|
||||||
#
|
|
||||||
# When uploading crates to the registry Cargo will automatically
|
|
||||||
# "normalize" Cargo.toml files for maximal compatibility
|
|
||||||
# with all versions of Cargo and also rewrite `path` dependencies
|
|
||||||
# to registry (e.g., crates.io) dependencies
|
|
||||||
#
|
|
||||||
# If you believe there's an error in this file please file an
|
|
||||||
# issue against the rust-lang/cargo repository. If you're
|
|
||||||
# editing this file be aware that the upstream Cargo.toml
|
|
||||||
# will likely look very different (and much more reasonable)
|
|
||||||
|
|
||||||
[package]
|
|
||||||
edition = "2018"
|
|
||||||
name = "env_logger"
|
|
||||||
version = "0.7.1"
|
|
||||||
authors = ["The Rust Project Developers"]
|
|
||||||
description = "A logging implementation for `log` which is configured via an environment\nvariable.\n"
|
|
||||||
documentation = "https://docs.rs/env_logger"
|
|
||||||
readme = "README.md"
|
|
||||||
keywords = ["logging", "log", "logger"]
|
|
||||||
categories = ["development-tools::debugging"]
|
|
||||||
license = "MIT/Apache-2.0"
|
|
||||||
repository = "https://github.com/sebasmagri/env_logger/"
|
|
||||||
|
|
||||||
[[test]]
|
|
||||||
name = "regexp_filter"
|
|
||||||
harness = false
|
|
||||||
|
|
||||||
[[test]]
|
|
||||||
name = "log-in-log"
|
|
||||||
harness = false
|
|
||||||
|
|
||||||
[[test]]
|
|
||||||
name = "log_tls_dtors"
|
|
||||||
harness = false
|
|
||||||
|
|
||||||
[[test]]
|
|
||||||
name = "init-twice-retains-filter"
|
|
||||||
harness = false
|
|
||||||
[dependencies.atty]
|
|
||||||
version = "0.2.5"
|
|
||||||
optional = true
|
|
||||||
|
|
||||||
[dependencies.humantime]
|
|
||||||
version = "1.3"
|
|
||||||
optional = true
|
|
||||||
|
|
||||||
[dependencies.log]
|
|
||||||
version = "0.4.8"
|
|
||||||
features = ["std"]
|
|
||||||
|
|
||||||
[dependencies.regex]
|
|
||||||
version = "1.0.3"
|
|
||||||
optional = true
|
|
||||||
|
|
||||||
[dependencies.termcolor]
|
|
||||||
version = "1.0.2"
|
|
||||||
optional = true
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["termcolor", "atty", "humantime", "regex"]
|
|
|
@ -1,201 +0,0 @@
|
||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
|
@ -1,25 +0,0 @@
|
||||||
Copyright (c) 2014 The Rust Project Developers
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any
|
|
||||||
person obtaining a copy of this software and associated
|
|
||||||
documentation files (the "Software"), to deal in the
|
|
||||||
Software without restriction, including without
|
|
||||||
limitation the rights to use, copy, modify, merge,
|
|
||||||
publish, distribute, sublicense, and/or sell copies of
|
|
||||||
the Software, and to permit persons to whom the Software
|
|
||||||
is furnished to do so, subject to the following
|
|
||||||
conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice
|
|
||||||
shall be included in all copies or substantial portions
|
|
||||||
of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
|
||||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
|
||||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
|
||||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
||||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
|
||||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
||||||
DEALINGS IN THE SOFTWARE.
|
|
|
@ -1,150 +0,0 @@
|
||||||
env_logger [![Build Status](https://travis-ci.org/sebasmagri/env_logger.svg?branch=master)](https://travis-ci.org/sebasmagri/env_logger) [![Maintenance](https://img.shields.io/badge/maintenance-actively%20maintained-brightgreen.svg)](https://github.com/sebasmagri/env_logger) [![crates.io](https://img.shields.io/crates/v/env_logger.svg)](https://crates.io/crates/env_logger) [![Documentation](https://img.shields.io/badge/docs-current-blue.svg)](https://docs.rs/env_logger)
|
|
||||||
==========
|
|
||||||
|
|
||||||
Implements a logger that can be configured via environment variables.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### In libraries
|
|
||||||
|
|
||||||
`env_logger` makes sense when used in executables (binary projects). Libraries should use the [`log`](https://doc.rust-lang.org/log) crate instead.
|
|
||||||
|
|
||||||
### In executables
|
|
||||||
|
|
||||||
It must be added along with `log` to the project dependencies:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[dependencies]
|
|
||||||
log = "0.4.0"
|
|
||||||
env_logger = "0.7.1"
|
|
||||||
```
|
|
||||||
|
|
||||||
`env_logger` must be initialized as early as possible in the project. After it's initialized, you can use the `log` macros to do actual logging.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
#[macro_use]
|
|
||||||
extern crate log;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
env_logger::init();
|
|
||||||
|
|
||||||
info!("starting up");
|
|
||||||
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Then when running the executable, specify a value for the `RUST_LOG`
|
|
||||||
environment variable that corresponds with the log messages you want to show.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ RUST_LOG=info ./main
|
|
||||||
[2018-11-03T06:09:06Z INFO default] starting up
|
|
||||||
```
|
|
||||||
|
|
||||||
`env_logger` can be configured in other ways besides an environment variable. See [the examples](https://github.com/sebasmagri/env_logger/tree/master/examples) for more approaches.
|
|
||||||
|
|
||||||
### In tests
|
|
||||||
|
|
||||||
Tests can use the `env_logger` crate to see log messages generated during that test:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[dependencies]
|
|
||||||
log = "0.4.0"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
env_logger = "0.7.1"
|
|
||||||
```
|
|
||||||
|
|
||||||
```rust
|
|
||||||
#[macro_use]
|
|
||||||
extern crate log;
|
|
||||||
|
|
||||||
fn add_one(num: i32) -> i32 {
|
|
||||||
info!("add_one called with {}", num);
|
|
||||||
num + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
fn init() {
|
|
||||||
let _ = env_logger::builder().is_test(true).try_init();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn it_adds_one() {
|
|
||||||
init();
|
|
||||||
|
|
||||||
info!("can log from the test too");
|
|
||||||
assert_eq!(3, add_one(2));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn it_handles_negative_numbers() {
|
|
||||||
init();
|
|
||||||
|
|
||||||
info!("logging from another test");
|
|
||||||
assert_eq!(-7, add_one(-8));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Assuming the module under test is called `my_lib`, running the tests with the
|
|
||||||
`RUST_LOG` filtering to info messages from this module looks like:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ RUST_LOG=my_lib=info cargo test
|
|
||||||
Running target/debug/my_lib-...
|
|
||||||
|
|
||||||
running 2 tests
|
|
||||||
[INFO my_lib::tests] logging from another test
|
|
||||||
[INFO my_lib] add_one called with -8
|
|
||||||
test tests::it_handles_negative_numbers ... ok
|
|
||||||
[INFO my_lib::tests] can log from the test too
|
|
||||||
[INFO my_lib] add_one called with 2
|
|
||||||
test tests::it_adds_one ... ok
|
|
||||||
|
|
||||||
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that `env_logger::try_init()` needs to be called in each test in which you
|
|
||||||
want to enable logging. Additionally, the default behavior of tests to
|
|
||||||
run in parallel means that logging output may be interleaved with test output.
|
|
||||||
Either run tests in a single thread by specifying `RUST_TEST_THREADS=1` or by
|
|
||||||
running one test by specifying its name as an argument to the test binaries as
|
|
||||||
directed by the `cargo test` help docs:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ RUST_LOG=my_lib=info cargo test it_adds_one
|
|
||||||
Running target/debug/my_lib-...
|
|
||||||
|
|
||||||
running 1 test
|
|
||||||
[INFO my_lib::tests] can log from the test too
|
|
||||||
[INFO my_lib] add_one called with 2
|
|
||||||
test tests::it_adds_one ... ok
|
|
||||||
|
|
||||||
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuring log target
|
|
||||||
|
|
||||||
By default, `env_logger` logs to stderr. If you want to log to stdout instead,
|
|
||||||
you can use the `Builder` to change the log target:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use std::env;
|
|
||||||
use env_logger::{Builder, Target};
|
|
||||||
|
|
||||||
let mut builder = Builder::from_default_env();
|
|
||||||
builder.target(Target::Stdout);
|
|
||||||
|
|
||||||
builder.init();
|
|
||||||
```
|
|
||||||
|
|
||||||
## Stability of the default format
|
|
||||||
|
|
||||||
The default format won't optimise for long-term stability, and explicitly makes no guarantees about the stability of its output across major, minor or patch version bumps during `0.x`.
|
|
||||||
|
|
||||||
If you want to capture or interpret the output of `env_logger` programmatically then you should use a custom format.
|
|
|
@ -1,41 +0,0 @@
|
||||||
/*!
|
|
||||||
Disabling parts of the default format.
|
|
||||||
|
|
||||||
Before running this example, try setting the `MY_LOG_LEVEL` environment variable to `info`:
|
|
||||||
|
|
||||||
```no_run,shell
|
|
||||||
$ export MY_LOG_LEVEL='info'
|
|
||||||
```
|
|
||||||
|
|
||||||
Also try setting the `MY_LOG_STYLE` environment variable to `never` to disable colors
|
|
||||||
or `auto` to enable them:
|
|
||||||
|
|
||||||
```no_run,shell
|
|
||||||
$ export MY_LOG_STYLE=never
|
|
||||||
```
|
|
||||||
|
|
||||||
If you want to control the logging output completely, see the `custom_logger` example.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate log;
|
|
||||||
|
|
||||||
use env_logger::{Builder, Env};
|
|
||||||
|
|
||||||
fn init_logger() {
|
|
||||||
let env = Env::default()
|
|
||||||
.filter("MY_LOG_LEVEL")
|
|
||||||
.write_style("MY_LOG_STYLE");
|
|
||||||
|
|
||||||
let mut builder = Builder::from_env(env);
|
|
||||||
|
|
||||||
builder.format_level(false).format_timestamp_nanos();
|
|
||||||
|
|
||||||
builder.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
init_logger();
|
|
||||||
|
|
||||||
info!("a log from `MyLogger`");
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
/*!
|
|
||||||
Changing the default logging format.
|
|
||||||
|
|
||||||
Before running this example, try setting the `MY_LOG_LEVEL` environment variable to `info`:
|
|
||||||
|
|
||||||
```no_run,shell
|
|
||||||
$ export MY_LOG_LEVEL='info'
|
|
||||||
```
|
|
||||||
|
|
||||||
Also try setting the `MY_LOG_STYLE` environment variable to `never` to disable colors
|
|
||||||
or `auto` to enable them:
|
|
||||||
|
|
||||||
```no_run,shell
|
|
||||||
$ export MY_LOG_STYLE=never
|
|
||||||
```
|
|
||||||
|
|
||||||
If you want to control the logging output completely, see the `custom_logger` example.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#[cfg(all(feature = "termcolor", feature = "humantime"))]
|
|
||||||
fn main() {
|
|
||||||
use env_logger::{fmt, Builder, Env};
|
|
||||||
use std::io::Write;
|
|
||||||
|
|
||||||
fn init_logger() {
|
|
||||||
let env = Env::default()
|
|
||||||
.filter("MY_LOG_LEVEL")
|
|
||||||
.write_style("MY_LOG_STYLE");
|
|
||||||
|
|
||||||
Builder::from_env(env)
|
|
||||||
.format(|buf, record| {
|
|
||||||
let mut style = buf.style();
|
|
||||||
style.set_bg(fmt::Color::Yellow).set_bold(true);
|
|
||||||
|
|
||||||
let timestamp = buf.timestamp();
|
|
||||||
|
|
||||||
writeln!(
|
|
||||||
buf,
|
|
||||||
"My formatted log ({}): {}",
|
|
||||||
timestamp,
|
|
||||||
style.value(record.args())
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
init_logger();
|
|
||||||
|
|
||||||
log::info!("a log from `MyLogger`");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(all(feature = "termcolor", feature = "humantime")))]
|
|
||||||
fn main() {}
|
|
|
@ -1,60 +0,0 @@
|
||||||
/*!
|
|
||||||
Using `env_logger` to drive a custom logger.
|
|
||||||
|
|
||||||
Before running this example, try setting the `MY_LOG_LEVEL` environment variable to `info`:
|
|
||||||
|
|
||||||
```no_run,shell
|
|
||||||
$ export MY_LOG_LEVEL='info'
|
|
||||||
```
|
|
||||||
|
|
||||||
If you only want to change the way logs are formatted, look at the `custom_format` example.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate log;
|
|
||||||
|
|
||||||
use env_logger::filter::Filter;
|
|
||||||
use log::{Log, Metadata, Record, SetLoggerError};
|
|
||||||
|
|
||||||
struct MyLogger {
|
|
||||||
inner: Filter,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MyLogger {
|
|
||||||
fn new() -> MyLogger {
|
|
||||||
use env_logger::filter::Builder;
|
|
||||||
let mut builder = Builder::from_env("MY_LOG_LEVEL");
|
|
||||||
|
|
||||||
MyLogger {
|
|
||||||
inner: builder.build(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init() -> Result<(), SetLoggerError> {
|
|
||||||
let logger = Self::new();
|
|
||||||
|
|
||||||
log::set_max_level(logger.inner.filter());
|
|
||||||
log::set_boxed_logger(Box::new(logger))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Log for MyLogger {
|
|
||||||
fn enabled(&self, metadata: &Metadata) -> bool {
|
|
||||||
self.inner.enabled(metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn log(&self, record: &Record) {
|
|
||||||
// Check if the record is matched by the logger before logging
|
|
||||||
if self.inner.matches(record) {
|
|
||||||
println!("{} - {}", record.level(), record.args());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(&self) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
MyLogger::init().unwrap();
|
|
||||||
|
|
||||||
info!("a log from `MyLogger`");
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
/*!
|
|
||||||
Using `env_logger`.
|
|
||||||
|
|
||||||
Before running this example, try setting the `MY_LOG_LEVEL` environment variable to `info`:
|
|
||||||
|
|
||||||
```no_run,shell
|
|
||||||
$ export MY_LOG_LEVEL='info'
|
|
||||||
```
|
|
||||||
|
|
||||||
Also try setting the `MY_LOG_STYLE` environment variable to `never` to disable colors
|
|
||||||
or `auto` to enable them:
|
|
||||||
|
|
||||||
```no_run,shell
|
|
||||||
$ export MY_LOG_STYLE=never
|
|
||||||
```
|
|
||||||
*/
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate log;
|
|
||||||
|
|
||||||
use env_logger::Env;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
// The `Env` lets us tweak what the environment
|
|
||||||
// variables to read are and what the default
|
|
||||||
// value is if they're missing
|
|
||||||
let env = Env::default()
|
|
||||||
.filter_or("MY_LOG_LEVEL", "trace")
|
|
||||||
.write_style_or("MY_LOG_STYLE", "always");
|
|
||||||
|
|
||||||
env_logger::init_from_env(env);
|
|
||||||
|
|
||||||
trace!("some trace log");
|
|
||||||
debug!("some debug log");
|
|
||||||
info!("some information log");
|
|
||||||
warn!("some warning log");
|
|
||||||
error!("some error log");
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
/*!
|
|
||||||
Using `env_logger::Logger` and the `log::Log` trait directly.
|
|
||||||
|
|
||||||
This example doesn't rely on environment variables, or having a static logger installed.
|
|
||||||
*/
|
|
||||||
|
|
||||||
fn record() -> log::Record<'static> {
|
|
||||||
let error_metadata = log::MetadataBuilder::new()
|
|
||||||
.target("myApp")
|
|
||||||
.level(log::Level::Error)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
log::Record::builder()
|
|
||||||
.metadata(error_metadata)
|
|
||||||
.args(format_args!("Error!"))
|
|
||||||
.line(Some(433))
|
|
||||||
.file(Some("app.rs"))
|
|
||||||
.module_path(Some("server"))
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
use log::Log;
|
|
||||||
|
|
||||||
let stylish_logger = env_logger::Builder::new()
|
|
||||||
.filter(None, log::LevelFilter::Error)
|
|
||||||
.write_style(env_logger::WriteStyle::Always)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let unstylish_logger = env_logger::Builder::new()
|
|
||||||
.filter(None, log::LevelFilter::Error)
|
|
||||||
.write_style(env_logger::WriteStyle::Never)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
stylish_logger.log(&record());
|
|
||||||
unstylish_logger.log(&record());
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
/*!
|
|
||||||
Specify logging filters in code instead of using an environment variable.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate log;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
env_logger::builder()
|
|
||||||
.filter_level(log::LevelFilter::Trace)
|
|
||||||
.init();
|
|
||||||
|
|
||||||
trace!("some trace log");
|
|
||||||
debug!("some debug log");
|
|
||||||
info!("some information log");
|
|
||||||
warn!("some warning log");
|
|
||||||
error!("some error log");
|
|
||||||
}
|
|
|
@ -1,616 +0,0 @@
|
||||||
//! Filtering for log records.
|
|
||||||
//!
|
|
||||||
//! This module contains the log filtering used by `env_logger` to match records.
|
|
||||||
//! You can use the `Filter` type in your own logger implementation to use the same
|
|
||||||
//! filter parsing and matching as `env_logger`. For more details about the format
|
|
||||||
//! for directive strings see [Enabling Logging].
|
|
||||||
//!
|
|
||||||
//! ## Using `env_logger` in your own logger
|
|
||||||
//!
|
|
||||||
//! You can use `env_logger`'s filtering functionality with your own logger.
|
|
||||||
//! Call [`Builder::parse`] to parse directives from a string when constructing
|
|
||||||
//! your logger. Call [`Filter::matches`] to check whether a record should be
|
|
||||||
//! logged based on the parsed filters when log records are received.
|
|
||||||
//!
|
|
||||||
//! ```
|
|
||||||
//! extern crate log;
|
|
||||||
//! extern crate env_logger;
|
|
||||||
//! use env_logger::filter::Filter;
|
|
||||||
//! use log::{Log, Metadata, Record};
|
|
||||||
//!
|
|
||||||
//! struct MyLogger {
|
|
||||||
//! filter: Filter
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! impl MyLogger {
|
|
||||||
//! fn new() -> MyLogger {
|
|
||||||
//! use env_logger::filter::Builder;
|
|
||||||
//! let mut builder = Builder::new();
|
|
||||||
//!
|
|
||||||
//! // Parse a directives string from an environment variable
|
|
||||||
//! if let Ok(ref filter) = std::env::var("MY_LOG_LEVEL") {
|
|
||||||
//! builder.parse(filter);
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! MyLogger {
|
|
||||||
//! filter: builder.build()
|
|
||||||
//! }
|
|
||||||
//! }
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! impl Log for MyLogger {
|
|
||||||
//! fn enabled(&self, metadata: &Metadata) -> bool {
|
|
||||||
//! self.filter.enabled(metadata)
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! fn log(&self, record: &Record) {
|
|
||||||
//! // Check if the record is matched by the filter
|
|
||||||
//! if self.filter.matches(record) {
|
|
||||||
//! println!("{:?}", record);
|
|
||||||
//! }
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! fn flush(&self) {}
|
|
||||||
//! }
|
|
||||||
//! # fn main() {}
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! [Enabling Logging]: ../index.html#enabling-logging
|
|
||||||
//! [`Builder::parse`]: struct.Builder.html#method.parse
|
|
||||||
//! [`Filter::matches`]: struct.Filter.html#method.matches
|
|
||||||
|
|
||||||
use log::{Level, LevelFilter, Metadata, Record};
|
|
||||||
use std::env;
|
|
||||||
use std::fmt;
|
|
||||||
use std::mem;
|
|
||||||
|
|
||||||
#[cfg(feature = "regex")]
|
|
||||||
#[path = "regex.rs"]
|
|
||||||
mod inner;
|
|
||||||
|
|
||||||
#[cfg(not(feature = "regex"))]
|
|
||||||
#[path = "string.rs"]
|
|
||||||
mod inner;
|
|
||||||
|
|
||||||
/// A log filter.
|
|
||||||
///
|
|
||||||
/// This struct can be used to determine whether or not a log record
|
|
||||||
/// should be written to the output.
|
|
||||||
/// Use the [`Builder`] type to parse and construct a `Filter`.
|
|
||||||
///
|
|
||||||
/// [`Builder`]: struct.Builder.html
|
|
||||||
pub struct Filter {
|
|
||||||
directives: Vec<Directive>,
|
|
||||||
filter: Option<inner::Filter>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A builder for a log filter.
|
|
||||||
///
|
|
||||||
/// It can be used to parse a set of directives from a string before building
|
|
||||||
/// a [`Filter`] instance.
|
|
||||||
///
|
|
||||||
/// ## Example
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// #[macro_use]
|
|
||||||
/// extern crate log;
|
|
||||||
/// extern crate env_logger;
|
|
||||||
///
|
|
||||||
/// use std::env;
|
|
||||||
/// use std::io;
|
|
||||||
/// use env_logger::filter::Builder;
|
|
||||||
///
|
|
||||||
/// fn main() {
|
|
||||||
/// let mut builder = Builder::new();
|
|
||||||
///
|
|
||||||
/// // Parse a logging filter from an environment variable.
|
|
||||||
/// if let Ok(rust_log) = env::var("RUST_LOG") {
|
|
||||||
/// builder.parse(&rust_log);
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// let filter = builder.build();
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// [`Filter`]: struct.Filter.html
|
|
||||||
pub struct Builder {
|
|
||||||
directives: Vec<Directive>,
|
|
||||||
filter: Option<inner::Filter>,
|
|
||||||
built: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct Directive {
|
|
||||||
name: Option<String>,
|
|
||||||
level: LevelFilter,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Filter {
|
|
||||||
/// Returns the maximum `LevelFilter` that this filter instance is
|
|
||||||
/// configured to output.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// extern crate log;
|
|
||||||
/// extern crate env_logger;
|
|
||||||
///
|
|
||||||
/// use log::LevelFilter;
|
|
||||||
/// use env_logger::filter::Builder;
|
|
||||||
///
|
|
||||||
/// fn main() {
|
|
||||||
/// let mut builder = Builder::new();
|
|
||||||
/// builder.filter(Some("module1"), LevelFilter::Info);
|
|
||||||
/// builder.filter(Some("module2"), LevelFilter::Error);
|
|
||||||
///
|
|
||||||
/// let filter = builder.build();
|
|
||||||
/// assert_eq!(filter.filter(), LevelFilter::Info);
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub fn filter(&self) -> LevelFilter {
|
|
||||||
self.directives
|
|
||||||
.iter()
|
|
||||||
.map(|d| d.level)
|
|
||||||
.max()
|
|
||||||
.unwrap_or(LevelFilter::Off)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks if this record matches the configured filter.
|
|
||||||
pub fn matches(&self, record: &Record) -> bool {
|
|
||||||
if !self.enabled(record.metadata()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(filter) = self.filter.as_ref() {
|
|
||||||
if !filter.is_match(&*record.args().to_string()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Determines if a log message with the specified metadata would be logged.
|
|
||||||
pub fn enabled(&self, metadata: &Metadata) -> bool {
|
|
||||||
let level = metadata.level();
|
|
||||||
let target = metadata.target();
|
|
||||||
|
|
||||||
enabled(&self.directives, level, target)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Builder {
|
|
||||||
/// Initializes the filter builder with defaults.
|
|
||||||
pub fn new() -> Builder {
|
|
||||||
Builder {
|
|
||||||
directives: Vec::new(),
|
|
||||||
filter: None,
|
|
||||||
built: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Initializes the filter builder from an environment.
|
|
||||||
pub fn from_env(env: &str) -> Builder {
|
|
||||||
let mut builder = Builder::new();
|
|
||||||
|
|
||||||
if let Ok(s) = env::var(env) {
|
|
||||||
builder.parse(&s);
|
|
||||||
}
|
|
||||||
|
|
||||||
builder
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a directive to the filter for a specific module.
|
|
||||||
pub fn filter_module(&mut self, module: &str, level: LevelFilter) -> &mut Self {
|
|
||||||
self.filter(Some(module), level)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a directive to the filter for all modules.
|
|
||||||
pub fn filter_level(&mut self, level: LevelFilter) -> &mut Self {
|
|
||||||
self.filter(None, level)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a directive to the filter.
|
|
||||||
///
|
|
||||||
/// The given module (if any) will log at most the specified level provided.
|
|
||||||
/// If no module is provided then the filter will apply to all log messages.
|
|
||||||
pub fn filter(&mut self, module: Option<&str>, level: LevelFilter) -> &mut Self {
|
|
||||||
self.directives.push(Directive {
|
|
||||||
name: module.map(|s| s.to_string()),
|
|
||||||
level,
|
|
||||||
});
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parses the directives string.
|
|
||||||
///
|
|
||||||
/// See the [Enabling Logging] section for more details.
|
|
||||||
///
|
|
||||||
/// [Enabling Logging]: ../index.html#enabling-logging
|
|
||||||
pub fn parse(&mut self, filters: &str) -> &mut Self {
|
|
||||||
let (directives, filter) = parse_spec(filters);
|
|
||||||
|
|
||||||
self.filter = filter;
|
|
||||||
|
|
||||||
for directive in directives {
|
|
||||||
self.directives.push(directive);
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Build a log filter.
|
|
||||||
pub fn build(&mut self) -> Filter {
|
|
||||||
assert!(!self.built, "attempt to re-use consumed builder");
|
|
||||||
self.built = true;
|
|
||||||
|
|
||||||
if self.directives.is_empty() {
|
|
||||||
// Adds the default filter if none exist
|
|
||||||
self.directives.push(Directive {
|
|
||||||
name: None,
|
|
||||||
level: LevelFilter::Error,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Sort the directives by length of their name, this allows a
|
|
||||||
// little more efficient lookup at runtime.
|
|
||||||
self.directives.sort_by(|a, b| {
|
|
||||||
let alen = a.name.as_ref().map(|a| a.len()).unwrap_or(0);
|
|
||||||
let blen = b.name.as_ref().map(|b| b.len()).unwrap_or(0);
|
|
||||||
alen.cmp(&blen)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Filter {
|
|
||||||
directives: mem::replace(&mut self.directives, Vec::new()),
|
|
||||||
filter: mem::replace(&mut self.filter, None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Builder {
|
|
||||||
fn default() -> Self {
|
|
||||||
Builder::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for Filter {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
f.debug_struct("Filter")
|
|
||||||
.field("filter", &self.filter)
|
|
||||||
.field("directives", &self.directives)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for Builder {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
if self.built {
|
|
||||||
f.debug_struct("Filter").field("built", &true).finish()
|
|
||||||
} else {
|
|
||||||
f.debug_struct("Filter")
|
|
||||||
.field("filter", &self.filter)
|
|
||||||
.field("directives", &self.directives)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse a logging specification string (e.g: "crate1,crate2::mod3,crate3::x=error/foo")
|
|
||||||
/// and return a vector with log directives.
|
|
||||||
fn parse_spec(spec: &str) -> (Vec<Directive>, Option<inner::Filter>) {
|
|
||||||
let mut dirs = Vec::new();
|
|
||||||
|
|
||||||
let mut parts = spec.split('/');
|
|
||||||
let mods = parts.next();
|
|
||||||
let filter = parts.next();
|
|
||||||
if parts.next().is_some() {
|
|
||||||
eprintln!(
|
|
||||||
"warning: invalid logging spec '{}', \
|
|
||||||
ignoring it (too many '/'s)",
|
|
||||||
spec
|
|
||||||
);
|
|
||||||
return (dirs, None);
|
|
||||||
}
|
|
||||||
mods.map(|m| {
|
|
||||||
for s in m.split(',') {
|
|
||||||
if s.len() == 0 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let mut parts = s.split('=');
|
|
||||||
let (log_level, name) =
|
|
||||||
match (parts.next(), parts.next().map(|s| s.trim()), parts.next()) {
|
|
||||||
(Some(part0), None, None) => {
|
|
||||||
// if the single argument is a log-level string or number,
|
|
||||||
// treat that as a global fallback
|
|
||||||
match part0.parse() {
|
|
||||||
Ok(num) => (num, None),
|
|
||||||
Err(_) => (LevelFilter::max(), Some(part0)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(Some(part0), Some(""), None) => (LevelFilter::max(), Some(part0)),
|
|
||||||
(Some(part0), Some(part1), None) => match part1.parse() {
|
|
||||||
Ok(num) => (num, Some(part0)),
|
|
||||||
_ => {
|
|
||||||
eprintln!(
|
|
||||||
"warning: invalid logging spec '{}', \
|
|
||||||
ignoring it",
|
|
||||||
part1
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
eprintln!(
|
|
||||||
"warning: invalid logging spec '{}', \
|
|
||||||
ignoring it",
|
|
||||||
s
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
dirs.push(Directive {
|
|
||||||
name: name.map(|s| s.to_string()),
|
|
||||||
level: log_level,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let filter = filter.map_or(None, |filter| match inner::Filter::new(filter) {
|
|
||||||
Ok(re) => Some(re),
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("warning: invalid regex filter - {}", e);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return (dirs, filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check whether a level and target are enabled by the set of directives.
|
|
||||||
fn enabled(directives: &[Directive], level: Level, target: &str) -> bool {
|
|
||||||
// Search for the longest match, the vector is assumed to be pre-sorted.
|
|
||||||
for directive in directives.iter().rev() {
|
|
||||||
match directive.name {
|
|
||||||
Some(ref name) if !target.starts_with(&**name) => {}
|
|
||||||
Some(..) | None => return level <= directive.level,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use log::{Level, LevelFilter};
|
|
||||||
|
|
||||||
use super::{enabled, parse_spec, Builder, Directive, Filter};
|
|
||||||
|
|
||||||
fn make_logger_filter(dirs: Vec<Directive>) -> Filter {
|
|
||||||
let mut logger = Builder::new().build();
|
|
||||||
logger.directives = dirs;
|
|
||||||
logger
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn filter_info() {
|
|
||||||
let logger = Builder::new().filter(None, LevelFilter::Info).build();
|
|
||||||
assert!(enabled(&logger.directives, Level::Info, "crate1"));
|
|
||||||
assert!(!enabled(&logger.directives, Level::Debug, "crate1"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn filter_beginning_longest_match() {
|
|
||||||
let logger = Builder::new()
|
|
||||||
.filter(Some("crate2"), LevelFilter::Info)
|
|
||||||
.filter(Some("crate2::mod"), LevelFilter::Debug)
|
|
||||||
.filter(Some("crate1::mod1"), LevelFilter::Warn)
|
|
||||||
.build();
|
|
||||||
assert!(enabled(&logger.directives, Level::Debug, "crate2::mod1"));
|
|
||||||
assert!(!enabled(&logger.directives, Level::Debug, "crate2"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_default() {
|
|
||||||
let logger = Builder::new().parse("info,crate1::mod1=warn").build();
|
|
||||||
assert!(enabled(&logger.directives, Level::Warn, "crate1::mod1"));
|
|
||||||
assert!(enabled(&logger.directives, Level::Info, "crate2::mod2"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn match_full_path() {
|
|
||||||
let logger = make_logger_filter(vec![
|
|
||||||
Directive {
|
|
||||||
name: Some("crate2".to_string()),
|
|
||||||
level: LevelFilter::Info,
|
|
||||||
},
|
|
||||||
Directive {
|
|
||||||
name: Some("crate1::mod1".to_string()),
|
|
||||||
level: LevelFilter::Warn,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
assert!(enabled(&logger.directives, Level::Warn, "crate1::mod1"));
|
|
||||||
assert!(!enabled(&logger.directives, Level::Info, "crate1::mod1"));
|
|
||||||
assert!(enabled(&logger.directives, Level::Info, "crate2"));
|
|
||||||
assert!(!enabled(&logger.directives, Level::Debug, "crate2"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn no_match() {
|
|
||||||
let logger = make_logger_filter(vec![
|
|
||||||
Directive {
|
|
||||||
name: Some("crate2".to_string()),
|
|
||||||
level: LevelFilter::Info,
|
|
||||||
},
|
|
||||||
Directive {
|
|
||||||
name: Some("crate1::mod1".to_string()),
|
|
||||||
level: LevelFilter::Warn,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
assert!(!enabled(&logger.directives, Level::Warn, "crate3"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn match_beginning() {
|
|
||||||
let logger = make_logger_filter(vec![
|
|
||||||
Directive {
|
|
||||||
name: Some("crate2".to_string()),
|
|
||||||
level: LevelFilter::Info,
|
|
||||||
},
|
|
||||||
Directive {
|
|
||||||
name: Some("crate1::mod1".to_string()),
|
|
||||||
level: LevelFilter::Warn,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
assert!(enabled(&logger.directives, Level::Info, "crate2::mod1"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn match_beginning_longest_match() {
|
|
||||||
let logger = make_logger_filter(vec![
|
|
||||||
Directive {
|
|
||||||
name: Some("crate2".to_string()),
|
|
||||||
level: LevelFilter::Info,
|
|
||||||
},
|
|
||||||
Directive {
|
|
||||||
name: Some("crate2::mod".to_string()),
|
|
||||||
level: LevelFilter::Debug,
|
|
||||||
},
|
|
||||||
Directive {
|
|
||||||
name: Some("crate1::mod1".to_string()),
|
|
||||||
level: LevelFilter::Warn,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
assert!(enabled(&logger.directives, Level::Debug, "crate2::mod1"));
|
|
||||||
assert!(!enabled(&logger.directives, Level::Debug, "crate2"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn match_default() {
|
|
||||||
let logger = make_logger_filter(vec![
|
|
||||||
Directive {
|
|
||||||
name: None,
|
|
||||||
level: LevelFilter::Info,
|
|
||||||
},
|
|
||||||
Directive {
|
|
||||||
name: Some("crate1::mod1".to_string()),
|
|
||||||
level: LevelFilter::Warn,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
assert!(enabled(&logger.directives, Level::Warn, "crate1::mod1"));
|
|
||||||
assert!(enabled(&logger.directives, Level::Info, "crate2::mod2"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn zero_level() {
|
|
||||||
let logger = make_logger_filter(vec![
|
|
||||||
Directive {
|
|
||||||
name: None,
|
|
||||||
level: LevelFilter::Info,
|
|
||||||
},
|
|
||||||
Directive {
|
|
||||||
name: Some("crate1::mod1".to_string()),
|
|
||||||
level: LevelFilter::Off,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
assert!(!enabled(&logger.directives, Level::Error, "crate1::mod1"));
|
|
||||||
assert!(enabled(&logger.directives, Level::Info, "crate2::mod2"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_spec_valid() {
|
|
||||||
let (dirs, filter) = parse_spec("crate1::mod1=error,crate1::mod2,crate2=debug");
|
|
||||||
assert_eq!(dirs.len(), 3);
|
|
||||||
assert_eq!(dirs[0].name, Some("crate1::mod1".to_string()));
|
|
||||||
assert_eq!(dirs[0].level, LevelFilter::Error);
|
|
||||||
|
|
||||||
assert_eq!(dirs[1].name, Some("crate1::mod2".to_string()));
|
|
||||||
assert_eq!(dirs[1].level, LevelFilter::max());
|
|
||||||
|
|
||||||
assert_eq!(dirs[2].name, Some("crate2".to_string()));
|
|
||||||
assert_eq!(dirs[2].level, LevelFilter::Debug);
|
|
||||||
assert!(filter.is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_spec_invalid_crate() {
|
|
||||||
// test parse_spec with multiple = in specification
|
|
||||||
let (dirs, filter) = parse_spec("crate1::mod1=warn=info,crate2=debug");
|
|
||||||
assert_eq!(dirs.len(), 1);
|
|
||||||
assert_eq!(dirs[0].name, Some("crate2".to_string()));
|
|
||||||
assert_eq!(dirs[0].level, LevelFilter::Debug);
|
|
||||||
assert!(filter.is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_spec_invalid_level() {
|
|
||||||
// test parse_spec with 'noNumber' as log level
|
|
||||||
let (dirs, filter) = parse_spec("crate1::mod1=noNumber,crate2=debug");
|
|
||||||
assert_eq!(dirs.len(), 1);
|
|
||||||
assert_eq!(dirs[0].name, Some("crate2".to_string()));
|
|
||||||
assert_eq!(dirs[0].level, LevelFilter::Debug);
|
|
||||||
assert!(filter.is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_spec_string_level() {
|
|
||||||
// test parse_spec with 'warn' as log level
|
|
||||||
let (dirs, filter) = parse_spec("crate1::mod1=wrong,crate2=warn");
|
|
||||||
assert_eq!(dirs.len(), 1);
|
|
||||||
assert_eq!(dirs[0].name, Some("crate2".to_string()));
|
|
||||||
assert_eq!(dirs[0].level, LevelFilter::Warn);
|
|
||||||
assert!(filter.is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_spec_empty_level() {
|
|
||||||
// test parse_spec with '' as log level
|
|
||||||
let (dirs, filter) = parse_spec("crate1::mod1=wrong,crate2=");
|
|
||||||
assert_eq!(dirs.len(), 1);
|
|
||||||
assert_eq!(dirs[0].name, Some("crate2".to_string()));
|
|
||||||
assert_eq!(dirs[0].level, LevelFilter::max());
|
|
||||||
assert!(filter.is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_spec_global() {
|
|
||||||
// test parse_spec with no crate
|
|
||||||
let (dirs, filter) = parse_spec("warn,crate2=debug");
|
|
||||||
assert_eq!(dirs.len(), 2);
|
|
||||||
assert_eq!(dirs[0].name, None);
|
|
||||||
assert_eq!(dirs[0].level, LevelFilter::Warn);
|
|
||||||
assert_eq!(dirs[1].name, Some("crate2".to_string()));
|
|
||||||
assert_eq!(dirs[1].level, LevelFilter::Debug);
|
|
||||||
assert!(filter.is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_spec_valid_filter() {
|
|
||||||
let (dirs, filter) = parse_spec("crate1::mod1=error,crate1::mod2,crate2=debug/abc");
|
|
||||||
assert_eq!(dirs.len(), 3);
|
|
||||||
assert_eq!(dirs[0].name, Some("crate1::mod1".to_string()));
|
|
||||||
assert_eq!(dirs[0].level, LevelFilter::Error);
|
|
||||||
|
|
||||||
assert_eq!(dirs[1].name, Some("crate1::mod2".to_string()));
|
|
||||||
assert_eq!(dirs[1].level, LevelFilter::max());
|
|
||||||
|
|
||||||
assert_eq!(dirs[2].name, Some("crate2".to_string()));
|
|
||||||
assert_eq!(dirs[2].level, LevelFilter::Debug);
|
|
||||||
assert!(filter.is_some() && filter.unwrap().to_string() == "abc");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_spec_invalid_crate_filter() {
|
|
||||||
let (dirs, filter) = parse_spec("crate1::mod1=error=warn,crate2=debug/a.c");
|
|
||||||
assert_eq!(dirs.len(), 1);
|
|
||||||
assert_eq!(dirs[0].name, Some("crate2".to_string()));
|
|
||||||
assert_eq!(dirs[0].level, LevelFilter::Debug);
|
|
||||||
assert!(filter.is_some() && filter.unwrap().to_string() == "a.c");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_spec_empty_with_filter() {
|
|
||||||
let (dirs, filter) = parse_spec("crate1/a*c");
|
|
||||||
assert_eq!(dirs.len(), 1);
|
|
||||||
assert_eq!(dirs[0].name, Some("crate1".to_string()));
|
|
||||||
assert_eq!(dirs[0].level, LevelFilter::max());
|
|
||||||
assert!(filter.is_some() && filter.unwrap().to_string() == "a*c");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
extern crate regex;
|
|
||||||
|
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
use self::regex::Regex;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Filter {
|
|
||||||
inner: Regex,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Filter {
|
|
||||||
pub fn new(spec: &str) -> Result<Filter, String> {
|
|
||||||
match Regex::new(spec) {
|
|
||||||
Ok(r) => Ok(Filter { inner: r }),
|
|
||||||
Err(e) => Err(e.to_string()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_match(&self, s: &str) -> bool {
|
|
||||||
self.inner.is_match(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Filter {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
self.inner.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Filter {
|
|
||||||
inner: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Filter {
|
|
||||||
pub fn new(spec: &str) -> Result<Filter, String> {
|
|
||||||
Ok(Filter {
|
|
||||||
inner: spec.to_string(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_match(&self, s: &str) -> bool {
|
|
||||||
s.contains(&self.inner)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Filter {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
self.inner.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,118 +0,0 @@
|
||||||
use std::fmt;
|
|
||||||
use std::time::SystemTime;
|
|
||||||
|
|
||||||
use humantime::{
|
|
||||||
format_rfc3339_micros, format_rfc3339_millis, format_rfc3339_nanos, format_rfc3339_seconds,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::fmt::{Formatter, TimestampPrecision};
|
|
||||||
|
|
||||||
pub(in crate::fmt) mod glob {
|
|
||||||
pub use super::*;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Formatter {
|
|
||||||
/// Get a [`Timestamp`] for the current date and time in UTC.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// Include the current timestamp with the log record:
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use std::io::Write;
|
|
||||||
///
|
|
||||||
/// let mut builder = env_logger::Builder::new();
|
|
||||||
///
|
|
||||||
/// builder.format(|buf, record| {
|
|
||||||
/// let ts = buf.timestamp();
|
|
||||||
///
|
|
||||||
/// writeln!(buf, "{}: {}: {}", ts, record.level(), record.args())
|
|
||||||
/// });
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// [`Timestamp`]: struct.Timestamp.html
|
|
||||||
pub fn timestamp(&self) -> Timestamp {
|
|
||||||
Timestamp {
|
|
||||||
time: SystemTime::now(),
|
|
||||||
precision: TimestampPrecision::Seconds,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a [`Timestamp`] for the current date and time in UTC with full
|
|
||||||
/// second precision.
|
|
||||||
pub fn timestamp_seconds(&self) -> Timestamp {
|
|
||||||
Timestamp {
|
|
||||||
time: SystemTime::now(),
|
|
||||||
precision: TimestampPrecision::Seconds,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a [`Timestamp`] for the current date and time in UTC with
|
|
||||||
/// millisecond precision.
|
|
||||||
pub fn timestamp_millis(&self) -> Timestamp {
|
|
||||||
Timestamp {
|
|
||||||
time: SystemTime::now(),
|
|
||||||
precision: TimestampPrecision::Millis,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a [`Timestamp`] for the current date and time in UTC with
|
|
||||||
/// microsecond precision.
|
|
||||||
pub fn timestamp_micros(&self) -> Timestamp {
|
|
||||||
Timestamp {
|
|
||||||
time: SystemTime::now(),
|
|
||||||
precision: TimestampPrecision::Micros,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a [`Timestamp`] for the current date and time in UTC with
|
|
||||||
/// nanosecond precision.
|
|
||||||
pub fn timestamp_nanos(&self) -> Timestamp {
|
|
||||||
Timestamp {
|
|
||||||
time: SystemTime::now(),
|
|
||||||
precision: TimestampPrecision::Nanos,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An [RFC3339] formatted timestamp.
|
|
||||||
///
|
|
||||||
/// The timestamp implements [`Display`] and can be written to a [`Formatter`].
|
|
||||||
///
|
|
||||||
/// [RFC3339]: https://www.ietf.org/rfc/rfc3339.txt
|
|
||||||
/// [`Display`]: https://doc.rust-lang.org/stable/std/fmt/trait.Display.html
|
|
||||||
/// [`Formatter`]: struct.Formatter.html
|
|
||||||
pub struct Timestamp {
|
|
||||||
time: SystemTime,
|
|
||||||
precision: TimestampPrecision,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for Timestamp {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
/// A `Debug` wrapper for `Timestamp` that uses the `Display` implementation.
|
|
||||||
struct TimestampValue<'a>(&'a Timestamp);
|
|
||||||
|
|
||||||
impl<'a> fmt::Debug for TimestampValue<'a> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
fmt::Display::fmt(&self.0, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
f.debug_tuple("Timestamp")
|
|
||||||
.field(&TimestampValue(&self))
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Timestamp {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
let formatter = match self.precision {
|
|
||||||
TimestampPrecision::Seconds => format_rfc3339_seconds,
|
|
||||||
TimestampPrecision::Millis => format_rfc3339_millis,
|
|
||||||
TimestampPrecision::Micros => format_rfc3339_micros,
|
|
||||||
TimestampPrecision::Nanos => format_rfc3339_nanos,
|
|
||||||
};
|
|
||||||
|
|
||||||
formatter(self.time).fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
/*
|
|
||||||
This internal module contains the timestamp implementation.
|
|
||||||
|
|
||||||
Its public API is available when the `humantime` crate is available.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "humantime", path = "extern_impl.rs")]
|
|
||||||
#[cfg_attr(not(feature = "humantime"), path = "shim_impl.rs")]
|
|
||||||
mod imp;
|
|
||||||
|
|
||||||
pub(in crate::fmt) use self::imp::*;
|
|
|
@ -1,5 +0,0 @@
|
||||||
/*
|
|
||||||
Timestamps aren't available when we don't have a `humantime` dependency.
|
|
||||||
*/
|
|
||||||
|
|
||||||
pub(in crate::fmt) mod glob {}
|
|
|
@ -1,489 +0,0 @@
|
||||||
//! Formatting for log records.
|
|
||||||
//!
|
|
||||||
//! This module contains a [`Formatter`] that can be used to format log records
|
|
||||||
//! into without needing temporary allocations. Usually you won't need to worry
|
|
||||||
//! about the contents of this module and can use the `Formatter` like an ordinary
|
|
||||||
//! [`Write`].
|
|
||||||
//!
|
|
||||||
//! # Formatting log records
|
|
||||||
//!
|
|
||||||
//! The format used to print log records can be customised using the [`Builder::format`]
|
|
||||||
//! method.
|
|
||||||
//! Custom formats can apply different color and weight to printed values using
|
|
||||||
//! [`Style`] builders.
|
|
||||||
//!
|
|
||||||
//! ```
|
|
||||||
//! use std::io::Write;
|
|
||||||
//!
|
|
||||||
//! let mut builder = env_logger::Builder::new();
|
|
||||||
//!
|
|
||||||
//! builder.format(|buf, record| {
|
|
||||||
//! writeln!(buf, "{}: {}",
|
|
||||||
//! record.level(),
|
|
||||||
//! record.args())
|
|
||||||
//! });
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! [`Formatter`]: struct.Formatter.html
|
|
||||||
//! [`Style`]: struct.Style.html
|
|
||||||
//! [`Builder::format`]: ../struct.Builder.html#method.format
|
|
||||||
//! [`Write`]: https://doc.rust-lang.org/stable/std/io/trait.Write.html
|
|
||||||
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::fmt::Display;
|
|
||||||
use std::io::prelude::*;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::{fmt, io, mem};
|
|
||||||
|
|
||||||
use log::Record;
|
|
||||||
|
|
||||||
mod humantime;
|
|
||||||
pub(crate) mod writer;
|
|
||||||
|
|
||||||
pub use self::humantime::glob::*;
|
|
||||||
pub use self::writer::glob::*;
|
|
||||||
|
|
||||||
use self::writer::{Buffer, Writer};
|
|
||||||
|
|
||||||
pub(crate) mod glob {
|
|
||||||
pub use super::{Target, TimestampPrecision, WriteStyle};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Formatting precision of timestamps.
|
|
||||||
///
|
|
||||||
/// Seconds give precision of full seconds, milliseconds give thousands of a
|
|
||||||
/// second (3 decimal digits), microseconds are millionth of a second (6 decimal
|
|
||||||
/// digits) and nanoseconds are billionth of a second (9 decimal digits).
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
pub enum TimestampPrecision {
|
|
||||||
/// Full second precision (0 decimal digits)
|
|
||||||
Seconds,
|
|
||||||
/// Millisecond precision (3 decimal digits)
|
|
||||||
Millis,
|
|
||||||
/// Microsecond precision (6 decimal digits)
|
|
||||||
Micros,
|
|
||||||
/// Nanosecond precision (9 decimal digits)
|
|
||||||
Nanos,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The default timestamp precision is seconds.
|
|
||||||
impl Default for TimestampPrecision {
|
|
||||||
fn default() -> Self {
|
|
||||||
TimestampPrecision::Seconds
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A formatter to write logs into.
|
|
||||||
///
|
|
||||||
/// `Formatter` implements the standard [`Write`] trait for writing log records.
|
|
||||||
/// It also supports terminal colors, through the [`style`] method.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// Use the [`writeln`] macro to format a log record.
|
|
||||||
/// An instance of a `Formatter` is passed to an `env_logger` format as `buf`:
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use std::io::Write;
|
|
||||||
///
|
|
||||||
/// let mut builder = env_logger::Builder::new();
|
|
||||||
///
|
|
||||||
/// builder.format(|buf, record| writeln!(buf, "{}: {}", record.level(), record.args()));
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// [`Write`]: https://doc.rust-lang.org/stable/std/io/trait.Write.html
|
|
||||||
/// [`writeln`]: https://doc.rust-lang.org/stable/std/macro.writeln.html
|
|
||||||
/// [`style`]: #method.style
|
|
||||||
pub struct Formatter {
|
|
||||||
buf: Rc<RefCell<Buffer>>,
|
|
||||||
write_style: WriteStyle,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Formatter {
|
|
||||||
pub(crate) fn new(writer: &Writer) -> Self {
|
|
||||||
Formatter {
|
|
||||||
buf: Rc::new(RefCell::new(writer.buffer())),
|
|
||||||
write_style: writer.write_style(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn write_style(&self) -> WriteStyle {
|
|
||||||
self.write_style
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn print(&self, writer: &Writer) -> io::Result<()> {
|
|
||||||
writer.print(&self.buf.borrow())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn clear(&mut self) {
|
|
||||||
self.buf.borrow_mut().clear()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Write for Formatter {
|
|
||||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
|
||||||
self.buf.borrow_mut().write(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(&mut self) -> io::Result<()> {
|
|
||||||
self.buf.borrow_mut().flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for Formatter {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
f.debug_struct("Formatter").finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct Builder {
|
|
||||||
pub format_timestamp: Option<TimestampPrecision>,
|
|
||||||
pub format_module_path: bool,
|
|
||||||
pub format_level: bool,
|
|
||||||
pub format_indent: Option<usize>,
|
|
||||||
#[allow(unknown_lints, bare_trait_objects)]
|
|
||||||
pub custom_format: Option<Box<Fn(&mut Formatter, &Record) -> io::Result<()> + Sync + Send>>,
|
|
||||||
built: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Builder {
|
|
||||||
fn default() -> Self {
|
|
||||||
Builder {
|
|
||||||
format_timestamp: Some(Default::default()),
|
|
||||||
format_module_path: true,
|
|
||||||
format_level: true,
|
|
||||||
format_indent: Some(4),
|
|
||||||
custom_format: None,
|
|
||||||
built: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Builder {
|
|
||||||
/// Convert the format into a callable function.
|
|
||||||
///
|
|
||||||
/// If the `custom_format` is `Some`, then any `default_format` switches are ignored.
|
|
||||||
/// If the `custom_format` is `None`, then a default format is returned.
|
|
||||||
/// Any `default_format` switches set to `false` won't be written by the format.
|
|
||||||
#[allow(unknown_lints, bare_trait_objects)]
|
|
||||||
pub fn build(&mut self) -> Box<Fn(&mut Formatter, &Record) -> io::Result<()> + Sync + Send> {
|
|
||||||
assert!(!self.built, "attempt to re-use consumed builder");
|
|
||||||
|
|
||||||
let built = mem::replace(
|
|
||||||
self,
|
|
||||||
Builder {
|
|
||||||
built: true,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Some(fmt) = built.custom_format {
|
|
||||||
fmt
|
|
||||||
} else {
|
|
||||||
Box::new(move |buf, record| {
|
|
||||||
let fmt = DefaultFormat {
|
|
||||||
timestamp: built.format_timestamp,
|
|
||||||
module_path: built.format_module_path,
|
|
||||||
level: built.format_level,
|
|
||||||
written_header_value: false,
|
|
||||||
indent: built.format_indent,
|
|
||||||
buf,
|
|
||||||
};
|
|
||||||
|
|
||||||
fmt.write(record)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "termcolor")]
|
|
||||||
type SubtleStyle = StyledValue<'static, &'static str>;
|
|
||||||
#[cfg(not(feature = "termcolor"))]
|
|
||||||
type SubtleStyle = &'static str;
|
|
||||||
|
|
||||||
/// The default format.
|
|
||||||
///
|
|
||||||
/// This format needs to work with any combination of crate features.
|
|
||||||
struct DefaultFormat<'a> {
|
|
||||||
timestamp: Option<TimestampPrecision>,
|
|
||||||
module_path: bool,
|
|
||||||
level: bool,
|
|
||||||
written_header_value: bool,
|
|
||||||
indent: Option<usize>,
|
|
||||||
buf: &'a mut Formatter,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> DefaultFormat<'a> {
|
|
||||||
fn write(mut self, record: &Record) -> io::Result<()> {
|
|
||||||
self.write_timestamp()?;
|
|
||||||
self.write_level(record)?;
|
|
||||||
self.write_module_path(record)?;
|
|
||||||
self.finish_header()?;
|
|
||||||
|
|
||||||
self.write_args(record)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn subtle_style(&self, text: &'static str) -> SubtleStyle {
|
|
||||||
#[cfg(feature = "termcolor")]
|
|
||||||
{
|
|
||||||
self.buf
|
|
||||||
.style()
|
|
||||||
.set_color(Color::Black)
|
|
||||||
.set_intense(true)
|
|
||||||
.into_value(text)
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "termcolor"))]
|
|
||||||
{
|
|
||||||
text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_header_value<T>(&mut self, value: T) -> io::Result<()>
|
|
||||||
where
|
|
||||||
T: Display,
|
|
||||||
{
|
|
||||||
if !self.written_header_value {
|
|
||||||
self.written_header_value = true;
|
|
||||||
|
|
||||||
let open_brace = self.subtle_style("[");
|
|
||||||
write!(self.buf, "{}{}", open_brace, value)
|
|
||||||
} else {
|
|
||||||
write!(self.buf, " {}", value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_level(&mut self, record: &Record) -> io::Result<()> {
|
|
||||||
if !self.level {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let level = {
|
|
||||||
#[cfg(feature = "termcolor")]
|
|
||||||
{
|
|
||||||
self.buf.default_styled_level(record.level())
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "termcolor"))]
|
|
||||||
{
|
|
||||||
record.level()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.write_header_value(format_args!("{:<5}", level))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_timestamp(&mut self) -> io::Result<()> {
|
|
||||||
#[cfg(feature = "humantime")]
|
|
||||||
{
|
|
||||||
use self::TimestampPrecision::*;
|
|
||||||
let ts = match self.timestamp {
|
|
||||||
None => return Ok(()),
|
|
||||||
Some(Seconds) => self.buf.timestamp_seconds(),
|
|
||||||
Some(Millis) => self.buf.timestamp_millis(),
|
|
||||||
Some(Micros) => self.buf.timestamp_micros(),
|
|
||||||
Some(Nanos) => self.buf.timestamp_nanos(),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.write_header_value(ts)
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "humantime"))]
|
|
||||||
{
|
|
||||||
// Trick the compiler to think we have used self.timestamp
|
|
||||||
// Workaround for "field is never used: `timestamp`" compiler nag.
|
|
||||||
let _ = self.timestamp;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_module_path(&mut self, record: &Record) -> io::Result<()> {
|
|
||||||
if !self.module_path {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(module_path) = record.module_path() {
|
|
||||||
self.write_header_value(module_path)
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn finish_header(&mut self) -> io::Result<()> {
|
|
||||||
if self.written_header_value {
|
|
||||||
let close_brace = self.subtle_style("]");
|
|
||||||
write!(self.buf, "{} ", close_brace)
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_args(&mut self, record: &Record) -> io::Result<()> {
|
|
||||||
match self.indent {
|
|
||||||
// Fast path for no indentation
|
|
||||||
None => writeln!(self.buf, "{}", record.args()),
|
|
||||||
|
|
||||||
Some(indent_count) => {
|
|
||||||
// Create a wrapper around the buffer only if we have to actually indent the message
|
|
||||||
|
|
||||||
struct IndentWrapper<'a, 'b: 'a> {
|
|
||||||
fmt: &'a mut DefaultFormat<'b>,
|
|
||||||
indent_count: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, 'b> Write for IndentWrapper<'a, 'b> {
|
|
||||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
|
||||||
let mut first = true;
|
|
||||||
for chunk in buf.split(|&x| x == b'\n') {
|
|
||||||
if !first {
|
|
||||||
write!(self.fmt.buf, "\n{:width$}", "", width = self.indent_count)?;
|
|
||||||
}
|
|
||||||
self.fmt.buf.write_all(chunk)?;
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(buf.len())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(&mut self) -> io::Result<()> {
|
|
||||||
self.fmt.buf.flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The explicit scope here is just to make older versions of Rust happy
|
|
||||||
{
|
|
||||||
let mut wrapper = IndentWrapper {
|
|
||||||
fmt: self,
|
|
||||||
indent_count,
|
|
||||||
};
|
|
||||||
write!(wrapper, "{}", record.args())?;
|
|
||||||
}
|
|
||||||
|
|
||||||
writeln!(self.buf)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
use log::{Level, Record};
|
|
||||||
|
|
||||||
fn write(fmt: DefaultFormat) -> String {
|
|
||||||
let buf = fmt.buf.buf.clone();
|
|
||||||
|
|
||||||
let record = Record::builder()
|
|
||||||
.args(format_args!("log\nmessage"))
|
|
||||||
.level(Level::Info)
|
|
||||||
.file(Some("test.rs"))
|
|
||||||
.line(Some(144))
|
|
||||||
.module_path(Some("test::path"))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
fmt.write(&record).expect("failed to write record");
|
|
||||||
|
|
||||||
let buf = buf.borrow();
|
|
||||||
String::from_utf8(buf.bytes().to_vec()).expect("failed to read record")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn format_with_header() {
|
|
||||||
let writer = writer::Builder::new()
|
|
||||||
.write_style(WriteStyle::Never)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let mut f = Formatter::new(&writer);
|
|
||||||
|
|
||||||
let written = write(DefaultFormat {
|
|
||||||
timestamp: None,
|
|
||||||
module_path: true,
|
|
||||||
level: true,
|
|
||||||
written_header_value: false,
|
|
||||||
indent: None,
|
|
||||||
buf: &mut f,
|
|
||||||
});
|
|
||||||
|
|
||||||
assert_eq!("[INFO test::path] log\nmessage\n", written);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn format_no_header() {
|
|
||||||
let writer = writer::Builder::new()
|
|
||||||
.write_style(WriteStyle::Never)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let mut f = Formatter::new(&writer);
|
|
||||||
|
|
||||||
let written = write(DefaultFormat {
|
|
||||||
timestamp: None,
|
|
||||||
module_path: false,
|
|
||||||
level: false,
|
|
||||||
written_header_value: false,
|
|
||||||
indent: None,
|
|
||||||
buf: &mut f,
|
|
||||||
});
|
|
||||||
|
|
||||||
assert_eq!("log\nmessage\n", written);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn format_indent_spaces() {
|
|
||||||
let writer = writer::Builder::new()
|
|
||||||
.write_style(WriteStyle::Never)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let mut f = Formatter::new(&writer);
|
|
||||||
|
|
||||||
let written = write(DefaultFormat {
|
|
||||||
timestamp: None,
|
|
||||||
module_path: true,
|
|
||||||
level: true,
|
|
||||||
written_header_value: false,
|
|
||||||
indent: Some(4),
|
|
||||||
buf: &mut f,
|
|
||||||
});
|
|
||||||
|
|
||||||
assert_eq!("[INFO test::path] log\n message\n", written);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn format_indent_zero_spaces() {
|
|
||||||
let writer = writer::Builder::new()
|
|
||||||
.write_style(WriteStyle::Never)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let mut f = Formatter::new(&writer);
|
|
||||||
|
|
||||||
let written = write(DefaultFormat {
|
|
||||||
timestamp: None,
|
|
||||||
module_path: true,
|
|
||||||
level: true,
|
|
||||||
written_header_value: false,
|
|
||||||
indent: Some(0),
|
|
||||||
buf: &mut f,
|
|
||||||
});
|
|
||||||
|
|
||||||
assert_eq!("[INFO test::path] log\nmessage\n", written);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn format_indent_spaces_no_header() {
|
|
||||||
let writer = writer::Builder::new()
|
|
||||||
.write_style(WriteStyle::Never)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let mut f = Formatter::new(&writer);
|
|
||||||
|
|
||||||
let written = write(DefaultFormat {
|
|
||||||
timestamp: None,
|
|
||||||
module_path: false,
|
|
||||||
level: false,
|
|
||||||
written_header_value: false,
|
|
||||||
indent: Some(4),
|
|
||||||
buf: &mut f,
|
|
||||||
});
|
|
||||||
|
|
||||||
assert_eq!("log\n message\n", written);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
/*
|
|
||||||
This internal module contains the terminal detection implementation.
|
|
||||||
|
|
||||||
If the `atty` crate is available then we use it to detect whether we're
|
|
||||||
attached to a particular TTY. If the `atty` crate is not available we
|
|
||||||
assume we're not attached to anything. This effectively prevents styles
|
|
||||||
from being printed.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#[cfg(feature = "atty")]
|
|
||||||
mod imp {
|
|
||||||
use atty;
|
|
||||||
|
|
||||||
pub(in crate::fmt) fn is_stdout() -> bool {
|
|
||||||
atty::is(atty::Stream::Stdout)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(in crate::fmt) fn is_stderr() -> bool {
|
|
||||||
atty::is(atty::Stream::Stderr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "atty"))]
|
|
||||||
mod imp {
|
|
||||||
pub(in crate::fmt) fn is_stdout() -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(in crate::fmt) fn is_stderr() -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(in crate::fmt) use self::imp::*;
|
|
|
@ -1,201 +0,0 @@
|
||||||
mod atty;
|
|
||||||
mod termcolor;
|
|
||||||
|
|
||||||
use self::atty::{is_stderr, is_stdout};
|
|
||||||
use self::termcolor::BufferWriter;
|
|
||||||
use std::{fmt, io};
|
|
||||||
|
|
||||||
pub(in crate::fmt) mod glob {
|
|
||||||
pub use super::termcolor::glob::*;
|
|
||||||
pub use super::*;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(in crate::fmt) use self::termcolor::Buffer;
|
|
||||||
|
|
||||||
/// Log target, either `stdout` or `stderr`.
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
||||||
pub enum Target {
|
|
||||||
/// Logs will be sent to standard output.
|
|
||||||
Stdout,
|
|
||||||
/// Logs will be sent to standard error.
|
|
||||||
Stderr,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Target {
|
|
||||||
fn default() -> Self {
|
|
||||||
Target::Stderr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether or not to print styles to the target.
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
||||||
pub enum WriteStyle {
|
|
||||||
/// Try to print styles, but don't force the issue.
|
|
||||||
Auto,
|
|
||||||
/// Try very hard to print styles.
|
|
||||||
Always,
|
|
||||||
/// Never print styles.
|
|
||||||
Never,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for WriteStyle {
|
|
||||||
fn default() -> Self {
|
|
||||||
WriteStyle::Auto
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A terminal target with color awareness.
|
|
||||||
pub(crate) struct Writer {
|
|
||||||
inner: BufferWriter,
|
|
||||||
write_style: WriteStyle,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Writer {
|
|
||||||
pub fn write_style(&self) -> WriteStyle {
|
|
||||||
self.write_style
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(in crate::fmt) fn buffer(&self) -> Buffer {
|
|
||||||
self.inner.buffer()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(in crate::fmt) fn print(&self, buf: &Buffer) -> io::Result<()> {
|
|
||||||
self.inner.print(buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A builder for a terminal writer.
|
|
||||||
///
|
|
||||||
/// The target and style choice can be configured before building.
|
|
||||||
pub(crate) struct Builder {
|
|
||||||
target: Target,
|
|
||||||
write_style: WriteStyle,
|
|
||||||
is_test: bool,
|
|
||||||
built: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Builder {
|
|
||||||
/// Initialize the writer builder with defaults.
|
|
||||||
pub(crate) fn new() -> Self {
|
|
||||||
Builder {
|
|
||||||
target: Default::default(),
|
|
||||||
write_style: Default::default(),
|
|
||||||
is_test: false,
|
|
||||||
built: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the target to write to.
|
|
||||||
pub(crate) fn target(&mut self, target: Target) -> &mut Self {
|
|
||||||
self.target = target;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parses a style choice string.
|
|
||||||
///
|
|
||||||
/// See the [Disabling colors] section for more details.
|
|
||||||
///
|
|
||||||
/// [Disabling colors]: ../index.html#disabling-colors
|
|
||||||
pub(crate) fn parse_write_style(&mut self, write_style: &str) -> &mut Self {
|
|
||||||
self.write_style(parse_write_style(write_style))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether or not to print style characters when writing.
|
|
||||||
pub(crate) fn write_style(&mut self, write_style: WriteStyle) -> &mut Self {
|
|
||||||
self.write_style = write_style;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether or not to capture logs for `cargo test`.
|
|
||||||
pub(crate) fn is_test(&mut self, is_test: bool) -> &mut Self {
|
|
||||||
self.is_test = is_test;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Build a terminal writer.
|
|
||||||
pub(crate) fn build(&mut self) -> Writer {
|
|
||||||
assert!(!self.built, "attempt to re-use consumed builder");
|
|
||||||
self.built = true;
|
|
||||||
|
|
||||||
let color_choice = match self.write_style {
|
|
||||||
WriteStyle::Auto => {
|
|
||||||
if match self.target {
|
|
||||||
Target::Stderr => is_stderr(),
|
|
||||||
Target::Stdout => is_stdout(),
|
|
||||||
} {
|
|
||||||
WriteStyle::Auto
|
|
||||||
} else {
|
|
||||||
WriteStyle::Never
|
|
||||||
}
|
|
||||||
}
|
|
||||||
color_choice => color_choice,
|
|
||||||
};
|
|
||||||
|
|
||||||
let writer = match self.target {
|
|
||||||
Target::Stderr => BufferWriter::stderr(self.is_test, color_choice),
|
|
||||||
Target::Stdout => BufferWriter::stdout(self.is_test, color_choice),
|
|
||||||
};
|
|
||||||
|
|
||||||
Writer {
|
|
||||||
inner: writer,
|
|
||||||
write_style: self.write_style,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Builder {
|
|
||||||
fn default() -> Self {
|
|
||||||
Builder::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for Builder {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
f.debug_struct("Logger")
|
|
||||||
.field("target", &self.target)
|
|
||||||
.field("write_style", &self.write_style)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for Writer {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
f.debug_struct("Writer").finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_write_style(spec: &str) -> WriteStyle {
|
|
||||||
match spec {
|
|
||||||
"auto" => WriteStyle::Auto,
|
|
||||||
"always" => WriteStyle::Always,
|
|
||||||
"never" => WriteStyle::Never,
|
|
||||||
_ => Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_write_style_valid() {
|
|
||||||
let inputs = vec![
|
|
||||||
("auto", WriteStyle::Auto),
|
|
||||||
("always", WriteStyle::Always),
|
|
||||||
("never", WriteStyle::Never),
|
|
||||||
];
|
|
||||||
|
|
||||||
for (input, expected) in inputs {
|
|
||||||
assert_eq!(expected, parse_write_style(input));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_write_style_invalid() {
|
|
||||||
let inputs = vec!["", "true", "false", "NEVER!!"];
|
|
||||||
|
|
||||||
for input in inputs {
|
|
||||||
assert_eq!(WriteStyle::Auto, parse_write_style(input));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,486 +0,0 @@
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::fmt;
|
|
||||||
use std::io::{self, Write};
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use log::Level;
|
|
||||||
use termcolor::{self, ColorChoice, ColorSpec, WriteColor};
|
|
||||||
|
|
||||||
use crate::fmt::{Formatter, Target, WriteStyle};
|
|
||||||
|
|
||||||
pub(in crate::fmt::writer) mod glob {
|
|
||||||
pub use super::*;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Formatter {
|
|
||||||
/// Begin a new [`Style`].
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// Create a bold, red colored style and use it to print the log level:
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use std::io::Write;
|
|
||||||
/// use env_logger::fmt::Color;
|
|
||||||
///
|
|
||||||
/// let mut builder = env_logger::Builder::new();
|
|
||||||
///
|
|
||||||
/// builder.format(|buf, record| {
|
|
||||||
/// let mut level_style = buf.style();
|
|
||||||
///
|
|
||||||
/// level_style.set_color(Color::Red).set_bold(true);
|
|
||||||
///
|
|
||||||
/// writeln!(buf, "{}: {}",
|
|
||||||
/// level_style.value(record.level()),
|
|
||||||
/// record.args())
|
|
||||||
/// });
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// [`Style`]: struct.Style.html
|
|
||||||
pub fn style(&self) -> Style {
|
|
||||||
Style {
|
|
||||||
buf: self.buf.clone(),
|
|
||||||
spec: ColorSpec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the default [`Style`] for the given level.
|
|
||||||
///
|
|
||||||
/// The style can be used to print other values besides the level.
|
|
||||||
pub fn default_level_style(&self, level: Level) -> Style {
|
|
||||||
let mut level_style = self.style();
|
|
||||||
match level {
|
|
||||||
Level::Trace => level_style.set_color(Color::Black).set_intense(true),
|
|
||||||
Level::Debug => level_style.set_color(Color::White),
|
|
||||||
Level::Info => level_style.set_color(Color::Green),
|
|
||||||
Level::Warn => level_style.set_color(Color::Yellow),
|
|
||||||
Level::Error => level_style.set_color(Color::Red).set_bold(true),
|
|
||||||
};
|
|
||||||
level_style
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a printable [`Style`] for the given level.
|
|
||||||
///
|
|
||||||
/// The style can only be used to print the level.
|
|
||||||
pub fn default_styled_level(&self, level: Level) -> StyledValue<'static, Level> {
|
|
||||||
self.default_level_style(level).into_value(level)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(in crate::fmt::writer) struct BufferWriter {
|
|
||||||
inner: termcolor::BufferWriter,
|
|
||||||
test_target: Option<Target>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(in crate::fmt) struct Buffer {
|
|
||||||
inner: termcolor::Buffer,
|
|
||||||
test_target: Option<Target>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BufferWriter {
|
|
||||||
pub(in crate::fmt::writer) fn stderr(is_test: bool, write_style: WriteStyle) -> Self {
|
|
||||||
BufferWriter {
|
|
||||||
inner: termcolor::BufferWriter::stderr(write_style.into_color_choice()),
|
|
||||||
test_target: if is_test { Some(Target::Stderr) } else { None },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(in crate::fmt::writer) fn stdout(is_test: bool, write_style: WriteStyle) -> Self {
|
|
||||||
BufferWriter {
|
|
||||||
inner: termcolor::BufferWriter::stdout(write_style.into_color_choice()),
|
|
||||||
test_target: if is_test { Some(Target::Stdout) } else { None },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(in crate::fmt::writer) fn buffer(&self) -> Buffer {
|
|
||||||
Buffer {
|
|
||||||
inner: self.inner.buffer(),
|
|
||||||
test_target: self.test_target,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(in crate::fmt::writer) fn print(&self, buf: &Buffer) -> io::Result<()> {
|
|
||||||
if let Some(target) = self.test_target {
|
|
||||||
// This impl uses the `eprint` and `print` macros
|
|
||||||
// instead of `termcolor`'s buffer.
|
|
||||||
// This is so their output can be captured by `cargo test`
|
|
||||||
let log = String::from_utf8_lossy(buf.bytes());
|
|
||||||
|
|
||||||
match target {
|
|
||||||
Target::Stderr => eprint!("{}", log),
|
|
||||||
Target::Stdout => print!("{}", log),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
self.inner.print(&buf.inner)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Buffer {
|
|
||||||
pub(in crate::fmt) fn clear(&mut self) {
|
|
||||||
self.inner.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(in crate::fmt) fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
|
||||||
self.inner.write(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(in crate::fmt) fn flush(&mut self) -> io::Result<()> {
|
|
||||||
self.inner.flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(in crate::fmt) fn bytes(&self) -> &[u8] {
|
|
||||||
self.inner.as_slice()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> {
|
|
||||||
// Ignore styles for test captured logs because they can't be printed
|
|
||||||
if self.test_target.is_none() {
|
|
||||||
self.inner.set_color(spec)
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reset(&mut self) -> io::Result<()> {
|
|
||||||
// Ignore styles for test captured logs because they can't be printed
|
|
||||||
if self.test_target.is_none() {
|
|
||||||
self.inner.reset()
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WriteStyle {
|
|
||||||
fn into_color_choice(self) -> ColorChoice {
|
|
||||||
match self {
|
|
||||||
WriteStyle::Always => ColorChoice::Always,
|
|
||||||
WriteStyle::Auto => ColorChoice::Auto,
|
|
||||||
WriteStyle::Never => ColorChoice::Never,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A set of styles to apply to the terminal output.
|
|
||||||
///
|
|
||||||
/// Call [`Formatter::style`] to get a `Style` and use the builder methods to
|
|
||||||
/// set styling properties, like [color] and [weight].
|
|
||||||
/// To print a value using the style, wrap it in a call to [`value`] when the log
|
|
||||||
/// record is formatted.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// Create a bold, red colored style and use it to print the log level:
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use std::io::Write;
|
|
||||||
/// use env_logger::fmt::Color;
|
|
||||||
///
|
|
||||||
/// let mut builder = env_logger::Builder::new();
|
|
||||||
///
|
|
||||||
/// builder.format(|buf, record| {
|
|
||||||
/// let mut level_style = buf.style();
|
|
||||||
///
|
|
||||||
/// level_style.set_color(Color::Red).set_bold(true);
|
|
||||||
///
|
|
||||||
/// writeln!(buf, "{}: {}",
|
|
||||||
/// level_style.value(record.level()),
|
|
||||||
/// record.args())
|
|
||||||
/// });
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Styles can be re-used to output multiple values:
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use std::io::Write;
|
|
||||||
/// use env_logger::fmt::Color;
|
|
||||||
///
|
|
||||||
/// let mut builder = env_logger::Builder::new();
|
|
||||||
///
|
|
||||||
/// builder.format(|buf, record| {
|
|
||||||
/// let mut bold = buf.style();
|
|
||||||
///
|
|
||||||
/// bold.set_bold(true);
|
|
||||||
///
|
|
||||||
/// writeln!(buf, "{}: {} {}",
|
|
||||||
/// bold.value(record.level()),
|
|
||||||
/// bold.value("some bold text"),
|
|
||||||
/// record.args())
|
|
||||||
/// });
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// [`Formatter::style`]: struct.Formatter.html#method.style
|
|
||||||
/// [color]: #method.set_color
|
|
||||||
/// [weight]: #method.set_bold
|
|
||||||
/// [`value`]: #method.value
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Style {
|
|
||||||
buf: Rc<RefCell<Buffer>>,
|
|
||||||
spec: ColorSpec,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A value that can be printed using the given styles.
|
|
||||||
///
|
|
||||||
/// It is the result of calling [`Style::value`].
|
|
||||||
///
|
|
||||||
/// [`Style::value`]: struct.Style.html#method.value
|
|
||||||
pub struct StyledValue<'a, T> {
|
|
||||||
style: Cow<'a, Style>,
|
|
||||||
value: T,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Style {
|
|
||||||
/// Set the text color.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// Create a style with red text:
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use std::io::Write;
|
|
||||||
/// use env_logger::fmt::Color;
|
|
||||||
///
|
|
||||||
/// let mut builder = env_logger::Builder::new();
|
|
||||||
///
|
|
||||||
/// builder.format(|buf, record| {
|
|
||||||
/// let mut style = buf.style();
|
|
||||||
///
|
|
||||||
/// style.set_color(Color::Red);
|
|
||||||
///
|
|
||||||
/// writeln!(buf, "{}", style.value(record.args()))
|
|
||||||
/// });
|
|
||||||
/// ```
|
|
||||||
pub fn set_color(&mut self, color: Color) -> &mut Style {
|
|
||||||
self.spec.set_fg(color.into_termcolor());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the text weight.
|
|
||||||
///
|
|
||||||
/// If `yes` is true then text will be written in bold.
|
|
||||||
/// If `yes` is false then text will be written in the default weight.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// Create a style with bold text:
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use std::io::Write;
|
|
||||||
///
|
|
||||||
/// let mut builder = env_logger::Builder::new();
|
|
||||||
///
|
|
||||||
/// builder.format(|buf, record| {
|
|
||||||
/// let mut style = buf.style();
|
|
||||||
///
|
|
||||||
/// style.set_bold(true);
|
|
||||||
///
|
|
||||||
/// writeln!(buf, "{}", style.value(record.args()))
|
|
||||||
/// });
|
|
||||||
/// ```
|
|
||||||
pub fn set_bold(&mut self, yes: bool) -> &mut Style {
|
|
||||||
self.spec.set_bold(yes);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the text intensity.
|
|
||||||
///
|
|
||||||
/// If `yes` is true then text will be written in a brighter color.
|
|
||||||
/// If `yes` is false then text will be written in the default color.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// Create a style with intense text:
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use std::io::Write;
|
|
||||||
///
|
|
||||||
/// let mut builder = env_logger::Builder::new();
|
|
||||||
///
|
|
||||||
/// builder.format(|buf, record| {
|
|
||||||
/// let mut style = buf.style();
|
|
||||||
///
|
|
||||||
/// style.set_intense(true);
|
|
||||||
///
|
|
||||||
/// writeln!(buf, "{}", style.value(record.args()))
|
|
||||||
/// });
|
|
||||||
/// ```
|
|
||||||
pub fn set_intense(&mut self, yes: bool) -> &mut Style {
|
|
||||||
self.spec.set_intense(yes);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the background color.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// Create a style with a yellow background:
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use std::io::Write;
|
|
||||||
/// use env_logger::fmt::Color;
|
|
||||||
///
|
|
||||||
/// let mut builder = env_logger::Builder::new();
|
|
||||||
///
|
|
||||||
/// builder.format(|buf, record| {
|
|
||||||
/// let mut style = buf.style();
|
|
||||||
///
|
|
||||||
/// style.set_bg(Color::Yellow);
|
|
||||||
///
|
|
||||||
/// writeln!(buf, "{}", style.value(record.args()))
|
|
||||||
/// });
|
|
||||||
/// ```
|
|
||||||
pub fn set_bg(&mut self, color: Color) -> &mut Style {
|
|
||||||
self.spec.set_bg(color.into_termcolor());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrap a value in the style.
|
|
||||||
///
|
|
||||||
/// The same `Style` can be used to print multiple different values.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// Create a bold, red colored style and use it to print the log level:
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use std::io::Write;
|
|
||||||
/// use env_logger::fmt::Color;
|
|
||||||
///
|
|
||||||
/// let mut builder = env_logger::Builder::new();
|
|
||||||
///
|
|
||||||
/// builder.format(|buf, record| {
|
|
||||||
/// let mut style = buf.style();
|
|
||||||
///
|
|
||||||
/// style.set_color(Color::Red).set_bold(true);
|
|
||||||
///
|
|
||||||
/// writeln!(buf, "{}: {}",
|
|
||||||
/// style.value(record.level()),
|
|
||||||
/// record.args())
|
|
||||||
/// });
|
|
||||||
/// ```
|
|
||||||
pub fn value<T>(&self, value: T) -> StyledValue<T> {
|
|
||||||
StyledValue {
|
|
||||||
style: Cow::Borrowed(self),
|
|
||||||
value,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrap a value in the style by taking ownership of it.
|
|
||||||
pub(crate) fn into_value<T>(&mut self, value: T) -> StyledValue<'static, T> {
|
|
||||||
StyledValue {
|
|
||||||
style: Cow::Owned(self.clone()),
|
|
||||||
value,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T> StyledValue<'a, T> {
|
|
||||||
fn write_fmt<F>(&self, f: F) -> fmt::Result
|
|
||||||
where
|
|
||||||
F: FnOnce() -> fmt::Result,
|
|
||||||
{
|
|
||||||
self.style
|
|
||||||
.buf
|
|
||||||
.borrow_mut()
|
|
||||||
.set_color(&self.style.spec)
|
|
||||||
.map_err(|_| fmt::Error)?;
|
|
||||||
|
|
||||||
// Always try to reset the terminal style, even if writing failed
|
|
||||||
let write = f();
|
|
||||||
let reset = self.style.buf.borrow_mut().reset().map_err(|_| fmt::Error);
|
|
||||||
|
|
||||||
write.and(reset)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for Style {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
f.debug_struct("Style").field("spec", &self.spec).finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! impl_styled_value_fmt {
|
|
||||||
($($fmt_trait:path),*) => {
|
|
||||||
$(
|
|
||||||
impl<'a, T: $fmt_trait> $fmt_trait for StyledValue<'a, T> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter)->fmt::Result {
|
|
||||||
self.write_fmt(|| T::fmt(&self.value, f))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)*
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_styled_value_fmt!(
|
|
||||||
fmt::Debug,
|
|
||||||
fmt::Display,
|
|
||||||
fmt::Pointer,
|
|
||||||
fmt::Octal,
|
|
||||||
fmt::Binary,
|
|
||||||
fmt::UpperHex,
|
|
||||||
fmt::LowerHex,
|
|
||||||
fmt::UpperExp,
|
|
||||||
fmt::LowerExp
|
|
||||||
);
|
|
||||||
|
|
||||||
// The `Color` type is copied from https://github.com/BurntSushi/ripgrep/tree/master/termcolor
|
|
||||||
|
|
||||||
/// The set of available colors for the terminal foreground/background.
|
|
||||||
///
|
|
||||||
/// The `Ansi256` and `Rgb` colors will only output the correct codes when
|
|
||||||
/// paired with the `Ansi` `WriteColor` implementation.
|
|
||||||
///
|
|
||||||
/// The `Ansi256` and `Rgb` color types are not supported when writing colors
|
|
||||||
/// on Windows using the console. If they are used on Windows, then they are
|
|
||||||
/// silently ignored and no colors will be emitted.
|
|
||||||
///
|
|
||||||
/// This set may expand over time.
|
|
||||||
///
|
|
||||||
/// This type has a `FromStr` impl that can parse colors from their human
|
|
||||||
/// readable form. The format is as follows:
|
|
||||||
///
|
|
||||||
/// 1. Any of the explicitly listed colors in English. They are matched
|
|
||||||
/// case insensitively.
|
|
||||||
/// 2. A single 8-bit integer, in either decimal or hexadecimal format.
|
|
||||||
/// 3. A triple of 8-bit integers separated by a comma, where each integer is
|
|
||||||
/// in decimal or hexadecimal format.
|
|
||||||
///
|
|
||||||
/// Hexadecimal numbers are written with a `0x` prefix.
|
|
||||||
#[allow(missing_docs)]
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
||||||
pub enum Color {
|
|
||||||
Black,
|
|
||||||
Blue,
|
|
||||||
Green,
|
|
||||||
Red,
|
|
||||||
Cyan,
|
|
||||||
Magenta,
|
|
||||||
Yellow,
|
|
||||||
White,
|
|
||||||
Ansi256(u8),
|
|
||||||
Rgb(u8, u8, u8),
|
|
||||||
#[doc(hidden)]
|
|
||||||
__Nonexhaustive,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Color {
|
|
||||||
fn into_termcolor(self) -> Option<termcolor::Color> {
|
|
||||||
match self {
|
|
||||||
Color::Black => Some(termcolor::Color::Black),
|
|
||||||
Color::Blue => Some(termcolor::Color::Blue),
|
|
||||||
Color::Green => Some(termcolor::Color::Green),
|
|
||||||
Color::Red => Some(termcolor::Color::Red),
|
|
||||||
Color::Cyan => Some(termcolor::Color::Cyan),
|
|
||||||
Color::Magenta => Some(termcolor::Color::Magenta),
|
|
||||||
Color::Yellow => Some(termcolor::Color::Yellow),
|
|
||||||
Color::White => Some(termcolor::Color::White),
|
|
||||||
Color::Ansi256(value) => Some(termcolor::Color::Ansi256(value)),
|
|
||||||
Color::Rgb(r, g, b) => Some(termcolor::Color::Rgb(r, g, b)),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
/*
|
|
||||||
This internal module contains the style and terminal writing implementation.
|
|
||||||
|
|
||||||
Its public API is available when the `termcolor` crate is available.
|
|
||||||
The terminal printing is shimmed when the `termcolor` crate is not available.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "termcolor", path = "extern_impl.rs")]
|
|
||||||
#[cfg_attr(not(feature = "termcolor"), path = "shim_impl.rs")]
|
|
||||||
mod imp;
|
|
||||||
|
|
||||||
pub(in crate::fmt) use self::imp::*;
|
|
|
@ -1,63 +0,0 @@
|
||||||
use std::io;
|
|
||||||
|
|
||||||
use crate::fmt::{Target, WriteStyle};
|
|
||||||
|
|
||||||
pub(in crate::fmt::writer) mod glob {}
|
|
||||||
|
|
||||||
pub(in crate::fmt::writer) struct BufferWriter {
|
|
||||||
target: Target,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(in crate::fmt) struct Buffer(Vec<u8>);
|
|
||||||
|
|
||||||
impl BufferWriter {
|
|
||||||
pub(in crate::fmt::writer) fn stderr(_is_test: bool, _write_style: WriteStyle) -> Self {
|
|
||||||
BufferWriter {
|
|
||||||
target: Target::Stderr,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(in crate::fmt::writer) fn stdout(_is_test: bool, _write_style: WriteStyle) -> Self {
|
|
||||||
BufferWriter {
|
|
||||||
target: Target::Stdout,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(in crate::fmt::writer) fn buffer(&self) -> Buffer {
|
|
||||||
Buffer(Vec::new())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(in crate::fmt::writer) fn print(&self, buf: &Buffer) -> io::Result<()> {
|
|
||||||
// This impl uses the `eprint` and `print` macros
|
|
||||||
// instead of using the streams directly.
|
|
||||||
// This is so their output can be captured by `cargo test`
|
|
||||||
let log = String::from_utf8_lossy(&buf.0);
|
|
||||||
|
|
||||||
match self.target {
|
|
||||||
Target::Stderr => eprint!("{}", log),
|
|
||||||
Target::Stdout => print!("{}", log),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Buffer {
|
|
||||||
pub(in crate::fmt) fn clear(&mut self) {
|
|
||||||
self.0.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(in crate::fmt) fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
|
||||||
self.0.extend(buf);
|
|
||||||
Ok(buf.len())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(in crate::fmt) fn flush(&mut self) -> io::Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
pub(in crate::fmt) fn bytes(&self) -> &[u8] {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,40 +0,0 @@
|
||||||
extern crate env_logger;
|
|
||||||
extern crate log;
|
|
||||||
|
|
||||||
use std::env;
|
|
||||||
use std::process;
|
|
||||||
use std::str;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
if env::var("YOU_ARE_TESTING_NOW").is_ok() {
|
|
||||||
// Init from the env (which should set the max level to `Debug`)
|
|
||||||
env_logger::init();
|
|
||||||
|
|
||||||
assert_eq!(log::LevelFilter::Debug, log::max_level());
|
|
||||||
|
|
||||||
// Init again using a different max level
|
|
||||||
// This shouldn't clobber the level that was previously set
|
|
||||||
env_logger::Builder::new()
|
|
||||||
.parse_filters("info")
|
|
||||||
.try_init()
|
|
||||||
.unwrap_err();
|
|
||||||
|
|
||||||
assert_eq!(log::LevelFilter::Debug, log::max_level());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let exe = env::current_exe().unwrap();
|
|
||||||
let out = process::Command::new(exe)
|
|
||||||
.env("YOU_ARE_TESTING_NOW", "1")
|
|
||||||
.env("RUST_LOG", "debug")
|
|
||||||
.output()
|
|
||||||
.unwrap_or_else(|e| panic!("Unable to start child process: {}", e));
|
|
||||||
if out.status.success() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("test failed: {}", out.status);
|
|
||||||
println!("--- stdout\n{}", str::from_utf8(&out.stdout).unwrap());
|
|
||||||
println!("--- stderr\n{}", str::from_utf8(&out.stderr).unwrap());
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
#[macro_use]
|
|
||||||
extern crate log;
|
|
||||||
extern crate env_logger;
|
|
||||||
|
|
||||||
use std::env;
|
|
||||||
use std::fmt;
|
|
||||||
use std::process;
|
|
||||||
use std::str;
|
|
||||||
|
|
||||||
struct Foo;
|
|
||||||
|
|
||||||
impl fmt::Display for Foo {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
info!("test");
|
|
||||||
f.write_str("bar")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
env_logger::init();
|
|
||||||
if env::var("YOU_ARE_TESTING_NOW").is_ok() {
|
|
||||||
return info!("{}", Foo);
|
|
||||||
}
|
|
||||||
|
|
||||||
let exe = env::current_exe().unwrap();
|
|
||||||
let out = process::Command::new(exe)
|
|
||||||
.env("YOU_ARE_TESTING_NOW", "1")
|
|
||||||
.env("RUST_LOG", "debug")
|
|
||||||
.output()
|
|
||||||
.unwrap_or_else(|e| panic!("Unable to start child process: {}", e));
|
|
||||||
if out.status.success() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("test failed: {}", out.status);
|
|
||||||
println!("--- stdout\n{}", str::from_utf8(&out.stdout).unwrap());
|
|
||||||
println!("--- stderr\n{}", str::from_utf8(&out.stderr).unwrap());
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
#[macro_use]
|
|
||||||
extern crate log;
|
|
||||||
extern crate env_logger;
|
|
||||||
|
|
||||||
use std::env;
|
|
||||||
use std::process;
|
|
||||||
use std::str;
|
|
||||||
use std::thread;
|
|
||||||
|
|
||||||
struct DropMe;
|
|
||||||
|
|
||||||
impl Drop for DropMe {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
debug!("Dropping now");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run() {
|
|
||||||
// Use multiple thread local values to increase the chance that our TLS
|
|
||||||
// value will get destroyed after the FORMATTER key in the library
|
|
||||||
thread_local! {
|
|
||||||
static DROP_ME_0: DropMe = DropMe;
|
|
||||||
static DROP_ME_1: DropMe = DropMe;
|
|
||||||
static DROP_ME_2: DropMe = DropMe;
|
|
||||||
static DROP_ME_3: DropMe = DropMe;
|
|
||||||
static DROP_ME_4: DropMe = DropMe;
|
|
||||||
static DROP_ME_5: DropMe = DropMe;
|
|
||||||
static DROP_ME_6: DropMe = DropMe;
|
|
||||||
static DROP_ME_7: DropMe = DropMe;
|
|
||||||
static DROP_ME_8: DropMe = DropMe;
|
|
||||||
static DROP_ME_9: DropMe = DropMe;
|
|
||||||
}
|
|
||||||
DROP_ME_0.with(|_| {});
|
|
||||||
DROP_ME_1.with(|_| {});
|
|
||||||
DROP_ME_2.with(|_| {});
|
|
||||||
DROP_ME_3.with(|_| {});
|
|
||||||
DROP_ME_4.with(|_| {});
|
|
||||||
DROP_ME_5.with(|_| {});
|
|
||||||
DROP_ME_6.with(|_| {});
|
|
||||||
DROP_ME_7.with(|_| {});
|
|
||||||
DROP_ME_8.with(|_| {});
|
|
||||||
DROP_ME_9.with(|_| {});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
env_logger::init();
|
|
||||||
if env::var("YOU_ARE_TESTING_NOW").is_ok() {
|
|
||||||
// Run on a separate thread because TLS values on the main thread
|
|
||||||
// won't have their destructors run if pthread is used.
|
|
||||||
// https://doc.rust-lang.org/std/thread/struct.LocalKey.html#platform-specific-behavior
|
|
||||||
thread::spawn(run).join().unwrap();
|
|
||||||
} else {
|
|
||||||
let exe = env::current_exe().unwrap();
|
|
||||||
let out = process::Command::new(exe)
|
|
||||||
.env("YOU_ARE_TESTING_NOW", "1")
|
|
||||||
.env("RUST_LOG", "debug")
|
|
||||||
.output()
|
|
||||||
.unwrap_or_else(|e| panic!("Unable to start child process: {}", e));
|
|
||||||
if !out.status.success() {
|
|
||||||
println!("test failed: {}", out.status);
|
|
||||||
println!("--- stdout\n{}", str::from_utf8(&out.stdout).unwrap());
|
|
||||||
println!("--- stderr\n{}", str::from_utf8(&out.stderr).unwrap());
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
#[macro_use]
|
|
||||||
extern crate log;
|
|
||||||
extern crate env_logger;
|
|
||||||
|
|
||||||
use std::env;
|
|
||||||
use std::process;
|
|
||||||
use std::str;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
if env::var("LOG_REGEXP_TEST").ok() == Some(String::from("1")) {
|
|
||||||
child_main();
|
|
||||||
} else {
|
|
||||||
parent_main()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn child_main() {
|
|
||||||
env_logger::init();
|
|
||||||
info!("XYZ Message");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_child(rust_log: String) -> bool {
|
|
||||||
let exe = env::current_exe().unwrap();
|
|
||||||
let out = process::Command::new(exe)
|
|
||||||
.env("LOG_REGEXP_TEST", "1")
|
|
||||||
.env("RUST_LOG", rust_log)
|
|
||||||
.output()
|
|
||||||
.unwrap_or_else(|e| panic!("Unable to start child process: {}", e));
|
|
||||||
str::from_utf8(out.stderr.as_ref())
|
|
||||||
.unwrap()
|
|
||||||
.contains("XYZ Message")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_message_printed(rust_log: &str) {
|
|
||||||
if !run_child(rust_log.to_string()) {
|
|
||||||
panic!("RUST_LOG={} should allow the test log message", rust_log)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_message_not_printed(rust_log: &str) {
|
|
||||||
if run_child(rust_log.to_string()) {
|
|
||||||
panic!(
|
|
||||||
"RUST_LOG={} should not allow the test log message",
|
|
||||||
rust_log
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parent_main() {
|
|
||||||
// test normal log severity levels
|
|
||||||
assert_message_printed("info");
|
|
||||||
assert_message_not_printed("warn");
|
|
||||||
|
|
||||||
// test of regular expression filters
|
|
||||||
assert_message_printed("info/XYZ");
|
|
||||||
assert_message_not_printed("info/XXX");
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
{"files":{"Cargo.toml":"ae7f884d13f5d849afd58b5db15510396832df7f648b0095e43bfdfa600b2ba3","LICENSE-APACHE":"c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08","LICENSE-MIT":"f6deca8261a8f4a3403dc74c725c46051157fd36c27cd4b100277eb1f303ad11","README.md":"e4bb65f28ddffb11d7eb337e9585947651f2fc11a5e4290f0ca126e21c582c1e","benches/datetime_format.rs":"a44c7ffb3c9515e92828564df8f91a86470343a2fa7e1a08bc2bc7397ba2591c","benches/datetime_parse.rs":"336a241755ccfa546d1840e56a40218c4f98f22d7d274829c5102ec280f2d963","bulk.yaml":"17c2548388e0cd3a63473021a2f1e4ddedee082d79d9167cb31ad06a1890d3fc","src/date.rs":"ea9924869a2484e333df2a845009593dd0062bb3b3e1a5ef9cefc05ec0a41196","src/duration.rs":"a5d4756451f495a3d5df0c872caa6304dd81ae075f0e204c8145c11c7c8f698b","src/lib.rs":"59dcc8a1ed89ecde199d59a7f71e550b461f87858b4becffb0a183994bafdd7c","src/wrapper.rs":"5bbdaf43256b445a8ca3a6b3eded9dd4fa8fb4dadf3683a5bac9648d91117396","vagga.yaml":"59761e5138a3015ef5654fec095ac8f1587cd23529b37822dd540b866fe4cd5b"},"package":"df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"}
|
|
|
@ -1,37 +0,0 @@
|
||||||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
|
||||||
#
|
|
||||||
# When uploading crates to the registry Cargo will automatically
|
|
||||||
# "normalize" Cargo.toml files for maximal compatibility
|
|
||||||
# with all versions of Cargo and also rewrite `path` dependencies
|
|
||||||
# to registry (e.g., crates.io) dependencies
|
|
||||||
#
|
|
||||||
# If you believe there's an error in this file please file an
|
|
||||||
# issue against the rust-lang/cargo repository. If you're
|
|
||||||
# editing this file be aware that the upstream Cargo.toml
|
|
||||||
# will likely look very different (and much more reasonable)
|
|
||||||
|
|
||||||
[package]
|
|
||||||
name = "humantime"
|
|
||||||
version = "1.3.0"
|
|
||||||
authors = ["Paul Colomiets <paul@colomiets.name>"]
|
|
||||||
description = " A parser and formatter for std::time::{Duration, SystemTime}\n"
|
|
||||||
homepage = "https://github.com/tailhook/humantime"
|
|
||||||
documentation = "https://docs.rs/humantime"
|
|
||||||
readme = "README.md"
|
|
||||||
keywords = ["time", "human", "human-friendly", "parser", "duration"]
|
|
||||||
categories = ["date-and-time"]
|
|
||||||
license = "MIT/Apache-2.0"
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
name = "humantime"
|
|
||||||
path = "src/lib.rs"
|
|
||||||
[dependencies.quick-error]
|
|
||||||
version = "1.0.0"
|
|
||||||
[dev-dependencies.chrono]
|
|
||||||
version = "0.4.0"
|
|
||||||
|
|
||||||
[dev-dependencies.rand]
|
|
||||||
version = "0.4.2"
|
|
||||||
|
|
||||||
[dev-dependencies.time]
|
|
||||||
version = "0.1.39"
|
|
|
@ -1,202 +0,0 @@
|
||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright {yyyy} {name of copyright owner}
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
Copyright (c) 2016 The humantime Developers
|
|
||||||
|
|
||||||
Includes parts of http date with the following copyright:
|
|
||||||
Copyright (c) 2016 Pyfisch
|
|
||||||
|
|
||||||
Includes portions of musl libc with the following copyright:
|
|
||||||
Copyright © 2005-2013 Rich Felker
|
|
||||||
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
|
@ -1,68 +0,0 @@
|
||||||
Human Time
|
|
||||||
==========
|
|
||||||
|
|
||||||
**Status: stable**
|
|
||||||
|
|
||||||
[Documentation](https://docs.rs/humantime) |
|
|
||||||
[Github](https://github.com/tailhook/humantime) |
|
|
||||||
[Crate](https://crates.io/crates/humantime)
|
|
||||||
|
|
||||||
|
|
||||||
Features:
|
|
||||||
|
|
||||||
* Parses durations in free form like `15days 2min 2s`
|
|
||||||
* Formats durations in similar form `2years 2min 12us`
|
|
||||||
* Parses and formats timestamp in `rfc3339` format: `2018-01-01T12:53:00Z`
|
|
||||||
* Parses timestamps in a weaker format: `2018-01-01 12:53:00`
|
|
||||||
|
|
||||||
Timestamp parsing/formatting is super-fast because format is basically
|
|
||||||
fixed.
|
|
||||||
|
|
||||||
Here are some micro-benchmarks:
|
|
||||||
|
|
||||||
```
|
|
||||||
test result: ok. 0 passed; 0 failed; 26 ignored; 0 measured; 0 filtered out
|
|
||||||
|
|
||||||
Running target/release/deps/datetime_format-8facb4ac832d9770
|
|
||||||
|
|
||||||
running 2 tests
|
|
||||||
test rfc3339_chrono ... bench: 737 ns/iter (+/- 37)
|
|
||||||
test rfc3339_humantime_seconds ... bench: 73 ns/iter (+/- 2)
|
|
||||||
|
|
||||||
test result: ok. 0 passed; 0 failed; 0 ignored; 2 measured; 0 filtered out
|
|
||||||
|
|
||||||
Running target/release/deps/datetime_parse-342628f877d7867c
|
|
||||||
|
|
||||||
running 6 tests
|
|
||||||
test datetime_utc_parse_millis ... bench: 228 ns/iter (+/- 11)
|
|
||||||
test datetime_utc_parse_nanos ... bench: 236 ns/iter (+/- 10)
|
|
||||||
test datetime_utc_parse_seconds ... bench: 204 ns/iter (+/- 18)
|
|
||||||
test rfc3339_humantime_millis ... bench: 28 ns/iter (+/- 1)
|
|
||||||
test rfc3339_humantime_nanos ... bench: 36 ns/iter (+/- 2)
|
|
||||||
test rfc3339_humantime_seconds ... bench: 24 ns/iter (+/- 1)
|
|
||||||
|
|
||||||
test result: ok. 0 passed; 0 failed; 0 ignored; 6 measured; 0 filtered out
|
|
||||||
```
|
|
||||||
|
|
||||||
See [humantime-serde] for serde integration (previous crate [serde-humantime] looks unmaintained).
|
|
||||||
|
|
||||||
[serde-humantime]: https://docs.rs/serde-humantime/0.1.1/serde_humantime/
|
|
||||||
[humantime-serde]: https://docs.rs/humantime-serde
|
|
||||||
|
|
||||||
License
|
|
||||||
=======
|
|
||||||
|
|
||||||
Licensed under either of
|
|
||||||
|
|
||||||
* Apache License, Version 2.0, (./LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
|
|
||||||
* MIT license (./LICENSE-MIT or http://opensource.org/licenses/MIT)
|
|
||||||
|
|
||||||
at your option.
|
|
||||||
|
|
||||||
Contribution
|
|
||||||
------------
|
|
||||||
|
|
||||||
Unless you explicitly state otherwise, any contribution intentionally
|
|
||||||
submitted for inclusion in the work by you, as defined in the Apache-2.0
|
|
||||||
license, shall be dual licensed as above, without any additional terms or
|
|
||||||
conditions.
|
|
|
@ -1,58 +0,0 @@
|
||||||
#![feature(test)]
|
|
||||||
extern crate chrono;
|
|
||||||
extern crate humantime;
|
|
||||||
extern crate test;
|
|
||||||
|
|
||||||
use std::io::Write;
|
|
||||||
use std::time::{Duration, UNIX_EPOCH};
|
|
||||||
use humantime::format_rfc3339;
|
|
||||||
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn rfc3339_humantime_seconds(b: &mut test::Bencher) {
|
|
||||||
let time = UNIX_EPOCH + Duration::new(1483228799, 0);
|
|
||||||
let mut buf = Vec::with_capacity(100);
|
|
||||||
b.iter(|| {
|
|
||||||
buf.truncate(0);
|
|
||||||
write!(&mut buf, "{}", format_rfc3339(time)).unwrap()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn rfc3339_chrono(b: &mut test::Bencher) {
|
|
||||||
use chrono::{DateTime, NaiveDateTime, Utc};
|
|
||||||
use chrono::format::Item;
|
|
||||||
use chrono::format::Item::*;
|
|
||||||
use chrono::format::Numeric::*;
|
|
||||||
use chrono::format::Fixed::*;
|
|
||||||
use chrono::format::Pad::*;
|
|
||||||
|
|
||||||
let time = DateTime::<Utc>::from_utc(
|
|
||||||
NaiveDateTime::from_timestamp(1483228799, 0), Utc);
|
|
||||||
let mut buf = Vec::with_capacity(100);
|
|
||||||
|
|
||||||
// formatting code from env_logger
|
|
||||||
const ITEMS: &'static [Item<'static>] = {
|
|
||||||
&[
|
|
||||||
Numeric(Year, Zero),
|
|
||||||
Literal("-"),
|
|
||||||
Numeric(Month, Zero),
|
|
||||||
Literal("-"),
|
|
||||||
Numeric(Day, Zero),
|
|
||||||
Literal("T"),
|
|
||||||
Numeric(Hour, Zero),
|
|
||||||
Literal(":"),
|
|
||||||
Numeric(Minute, Zero),
|
|
||||||
Literal(":"),
|
|
||||||
Numeric(Second, Zero),
|
|
||||||
Fixed(TimezoneOffsetZ),
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
b.iter(|| {
|
|
||||||
buf.truncate(0);
|
|
||||||
write!(&mut buf, "{}", time.format_with_items(ITEMS.iter().cloned()))
|
|
||||||
.unwrap()
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
#![feature(test)]
|
|
||||||
extern crate chrono;
|
|
||||||
extern crate humantime;
|
|
||||||
extern crate test;
|
|
||||||
|
|
||||||
use chrono::{DateTime};
|
|
||||||
use humantime::parse_rfc3339;
|
|
||||||
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn rfc3339_humantime_seconds(b: &mut test::Bencher) {
|
|
||||||
b.iter(|| {
|
|
||||||
parse_rfc3339("2018-02-13T23:08:32Z").unwrap()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn datetime_utc_parse_seconds(b: &mut test::Bencher) {
|
|
||||||
b.iter(|| {
|
|
||||||
DateTime::parse_from_rfc3339("2018-02-13T23:08:32Z").unwrap()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn rfc3339_humantime_millis(b: &mut test::Bencher) {
|
|
||||||
b.iter(|| {
|
|
||||||
parse_rfc3339("2018-02-13T23:08:32.123Z").unwrap()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn datetime_utc_parse_millis(b: &mut test::Bencher) {
|
|
||||||
b.iter(|| {
|
|
||||||
DateTime::parse_from_rfc3339("2018-02-13T23:08:32.123Z").unwrap()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn rfc3339_humantime_nanos(b: &mut test::Bencher) {
|
|
||||||
b.iter(|| {
|
|
||||||
parse_rfc3339("2018-02-13T23:08:32.123456983Z").unwrap()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn datetime_utc_parse_nanos(b: &mut test::Bencher) {
|
|
||||||
b.iter(|| {
|
|
||||||
DateTime::parse_from_rfc3339("2018-02-13T23:08:32.123456983Z").unwrap()
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
minimum-bulk: v0.4.5
|
|
||||||
|
|
||||||
versions:
|
|
||||||
|
|
||||||
- file: Cargo.toml
|
|
||||||
block-start: ^\[package\]
|
|
||||||
block-end: ^\[.*\]
|
|
||||||
regex: ^version\s*=\s*"(\S+)"
|
|
|
@ -1,614 +0,0 @@
|
||||||
use std::fmt;
|
|
||||||
use std::str;
|
|
||||||
use std::time::{SystemTime, Duration, UNIX_EPOCH};
|
|
||||||
|
|
||||||
#[cfg(target_os="cloudabi")]
|
|
||||||
mod max {
|
|
||||||
pub const SECONDS: u64 = ::std::u64::MAX / 1_000_000_000;
|
|
||||||
#[allow(unused)]
|
|
||||||
pub const TIMESTAMP: &'static str = "2554-07-21T23:34:33Z";
|
|
||||||
}
|
|
||||||
#[cfg(all(
|
|
||||||
target_pointer_width="32",
|
|
||||||
not(target_os="cloudabi"),
|
|
||||||
not(target_os="windows"),
|
|
||||||
not(all(target_arch="wasm32", not(target_os="emscripten")))
|
|
||||||
))]
|
|
||||||
mod max {
|
|
||||||
pub const SECONDS: u64 = ::std::i32::MAX as u64;
|
|
||||||
#[allow(unused)]
|
|
||||||
pub const TIMESTAMP: &'static str = "2038-01-19T03:14:07Z";
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(
|
|
||||||
target_pointer_width="64",
|
|
||||||
target_os="windows",
|
|
||||||
all(target_arch="wasm32", not(target_os="emscripten")),
|
|
||||||
))]
|
|
||||||
mod max {
|
|
||||||
pub const SECONDS: u64 = 253402300800-1; // last second of year 9999
|
|
||||||
#[allow(unused)]
|
|
||||||
pub const TIMESTAMP: &'static str = "9999-12-31T23:59:59Z";
|
|
||||||
}
|
|
||||||
|
|
||||||
quick_error! {
|
|
||||||
/// Error parsing datetime (timestamp)
|
|
||||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
|
||||||
pub enum Error {
|
|
||||||
/// Numeric component is out of range
|
|
||||||
OutOfRange {
|
|
||||||
display("numeric component is out of range")
|
|
||||||
}
|
|
||||||
/// Bad character where digit is expected
|
|
||||||
InvalidDigit {
|
|
||||||
display("bad character where digit is expected")
|
|
||||||
}
|
|
||||||
/// Other formatting errors
|
|
||||||
InvalidFormat {
|
|
||||||
display("timestamp format is invalid")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
enum Precision {
|
|
||||||
Smart,
|
|
||||||
Seconds,
|
|
||||||
Millis,
|
|
||||||
Micros,
|
|
||||||
Nanos,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A wrapper type that allows you to Display a SystemTime
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Rfc3339Timestamp(SystemTime, Precision);
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn two_digits(b1: u8, b2: u8) -> Result<u64, Error> {
|
|
||||||
if b1 < b'0' || b2 < b'0' || b1 > b'9' || b2 > b'9' {
|
|
||||||
return Err(Error::InvalidDigit);
|
|
||||||
}
|
|
||||||
Ok(((b1 - b'0')*10 + (b2 - b'0')) as u64)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse RFC3339 timestamp `2018-02-14T00:28:07Z`
|
|
||||||
///
|
|
||||||
/// Supported feature: any precision of fractional
|
|
||||||
/// digits `2018-02-14T00:28:07.133Z`.
|
|
||||||
///
|
|
||||||
/// Unsupported feature: localized timestamps. Only UTC is supported.
|
|
||||||
pub fn parse_rfc3339(s: &str) -> Result<SystemTime, Error> {
|
|
||||||
if s.len() < "2018-02-14T00:28:07Z".len() {
|
|
||||||
return Err(Error::InvalidFormat);
|
|
||||||
}
|
|
||||||
let b = s.as_bytes();
|
|
||||||
if b[10] != b'T' || b[b.len()-1] != b'Z' {
|
|
||||||
return Err(Error::InvalidFormat);
|
|
||||||
}
|
|
||||||
return parse_rfc3339_weak(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse RFC3339-like timestamp `2018-02-14 00:28:07`
|
|
||||||
///
|
|
||||||
/// Supported features:
|
|
||||||
///
|
|
||||||
/// 1. Any precision of fractional digits `2018-02-14 00:28:07.133`.
|
|
||||||
/// 2. Supports timestamp with or without either of `T` or `Z`
|
|
||||||
/// 3. Anything valid for `parse_3339` is valid for this function
|
|
||||||
///
|
|
||||||
/// Unsupported feature: localized timestamps. Only UTC is supported, even if
|
|
||||||
/// `Z` is not specified.
|
|
||||||
///
|
|
||||||
/// This function is intended to use for parsing human input. Whereas
|
|
||||||
/// `parse_rfc3339` is for strings generated programmatically.
|
|
||||||
pub fn parse_rfc3339_weak(s: &str) -> Result<SystemTime, Error> {
|
|
||||||
if s.len() < "2018-02-14T00:28:07".len() {
|
|
||||||
return Err(Error::InvalidFormat);
|
|
||||||
}
|
|
||||||
let b = s.as_bytes(); // for careless slicing
|
|
||||||
if b[4] != b'-' || b[7] != b'-' || (b[10] != b'T' && b[10] != b' ') ||
|
|
||||||
b[13] != b':' || b[16] != b':'
|
|
||||||
{
|
|
||||||
return Err(Error::InvalidFormat);
|
|
||||||
}
|
|
||||||
let year = two_digits(b[0], b[1])? * 100 + two_digits(b[2], b[3])?;
|
|
||||||
let month = two_digits(b[5], b[6])?;
|
|
||||||
let day = two_digits(b[8], b[9])?;
|
|
||||||
let hour = two_digits(b[11], b[12])?;
|
|
||||||
let minute = two_digits(b[14], b[15])?;
|
|
||||||
let mut second = two_digits(b[17], b[18])?;
|
|
||||||
|
|
||||||
if year < 1970 || hour > 23 || minute > 59 || second > 60 {
|
|
||||||
return Err(Error::OutOfRange);
|
|
||||||
}
|
|
||||||
// TODO(tailhook) should we check that leaps second is only on midnight ?
|
|
||||||
if second == 60 {
|
|
||||||
second = 59
|
|
||||||
};
|
|
||||||
let leap_years = ((year - 1) - 1968) / 4 - ((year - 1) - 1900) / 100 +
|
|
||||||
((year - 1) - 1600) / 400;
|
|
||||||
let leap = is_leap_year(year);
|
|
||||||
let (mut ydays, mdays) = match month {
|
|
||||||
1 => (0, 31),
|
|
||||||
2 if leap => (31, 29),
|
|
||||||
2 => (31, 28),
|
|
||||||
3 => (59, 31),
|
|
||||||
4 => (90, 30),
|
|
||||||
5 => (120, 31),
|
|
||||||
6 => (151, 30),
|
|
||||||
7 => (181, 31),
|
|
||||||
8 => (212, 31),
|
|
||||||
9 => (243, 30),
|
|
||||||
10 => (273, 31),
|
|
||||||
11 => (304, 30),
|
|
||||||
12 => (334, 31),
|
|
||||||
_ => return Err(Error::OutOfRange),
|
|
||||||
};
|
|
||||||
if day > mdays || day == 0 {
|
|
||||||
return Err(Error::OutOfRange);
|
|
||||||
}
|
|
||||||
ydays += day - 1;
|
|
||||||
if leap && month > 2 {
|
|
||||||
ydays += 1;
|
|
||||||
}
|
|
||||||
let days = (year - 1970) * 365 + leap_years + ydays;
|
|
||||||
|
|
||||||
let time = second + minute * 60 + hour * 3600;
|
|
||||||
|
|
||||||
let mut nanos = 0;
|
|
||||||
let mut mult = 100_000_000;
|
|
||||||
if b.get(19) == Some(&b'.') {
|
|
||||||
for idx in 20..b.len() {
|
|
||||||
if b[idx] == b'Z' {
|
|
||||||
if idx == b.len()-1 {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
return Err(Error::InvalidDigit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if b[idx] < b'0' || b[idx] > b'9' {
|
|
||||||
return Err(Error::InvalidDigit);
|
|
||||||
}
|
|
||||||
nanos += mult * (b[idx] - b'0') as u32;
|
|
||||||
mult /= 10;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if b.len() != 19 && (b.len() > 20 || b[19] != b'Z') {
|
|
||||||
return Err(Error::InvalidFormat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let total_seconds = time + days * 86400;
|
|
||||||
if total_seconds > max::SECONDS {
|
|
||||||
return Err(Error::OutOfRange);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(UNIX_EPOCH + Duration::new(total_seconds, nanos));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_leap_year(y: u64) -> bool {
|
|
||||||
y % 4 == 0 && (!(y % 100 == 0) || y % 400 == 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Format an RFC3339 timestamp `2018-02-14T00:28:07Z`
|
|
||||||
///
|
|
||||||
/// This function formats timestamp with smart precision: i.e. if it has no
|
|
||||||
/// fractional seconds, they aren't written at all. And up to nine digits if
|
|
||||||
/// they are.
|
|
||||||
///
|
|
||||||
/// The value is always UTC and ignores system timezone.
|
|
||||||
pub fn format_rfc3339(system_time: SystemTime) -> Rfc3339Timestamp {
|
|
||||||
return Rfc3339Timestamp(system_time, Precision::Smart);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Format an RFC3339 timestamp `2018-02-14T00:28:07Z`
|
|
||||||
///
|
|
||||||
/// This format always shows timestamp without fractional seconds.
|
|
||||||
///
|
|
||||||
/// The value is always UTC and ignores system timezone.
|
|
||||||
pub fn format_rfc3339_seconds(system_time: SystemTime) -> Rfc3339Timestamp {
|
|
||||||
return Rfc3339Timestamp(system_time, Precision::Seconds);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Format an RFC3339 timestamp `2018-02-14T00:28:07.000Z`
|
|
||||||
///
|
|
||||||
/// This format always shows milliseconds even if millisecond value is zero.
|
|
||||||
///
|
|
||||||
/// The value is always UTC and ignores system timezone.
|
|
||||||
pub fn format_rfc3339_millis(system_time: SystemTime) -> Rfc3339Timestamp {
|
|
||||||
return Rfc3339Timestamp(system_time, Precision::Millis);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Format an RFC3339 timestamp `2018-02-14T00:28:07.000000Z`
|
|
||||||
///
|
|
||||||
/// This format always shows microseconds even if microsecond value is zero.
|
|
||||||
///
|
|
||||||
/// The value is always UTC and ignores system timezone.
|
|
||||||
pub fn format_rfc3339_micros(system_time: SystemTime) -> Rfc3339Timestamp {
|
|
||||||
return Rfc3339Timestamp(system_time, Precision::Micros);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Format an RFC3339 timestamp `2018-02-14T00:28:07.000000000Z`
|
|
||||||
///
|
|
||||||
/// This format always shows nanoseconds even if nanosecond value is zero.
|
|
||||||
///
|
|
||||||
/// The value is always UTC and ignores system timezone.
|
|
||||||
pub fn format_rfc3339_nanos(system_time: SystemTime) -> Rfc3339Timestamp {
|
|
||||||
return Rfc3339Timestamp(system_time, Precision::Nanos);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Rfc3339Timestamp {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
use self::Precision::*;
|
|
||||||
|
|
||||||
let dur = self.0.duration_since(UNIX_EPOCH)
|
|
||||||
.expect("all times should be after the epoch");
|
|
||||||
let secs_since_epoch = dur.as_secs();
|
|
||||||
let nanos = dur.subsec_nanos();
|
|
||||||
|
|
||||||
if secs_since_epoch >= 253402300800 { // year 9999
|
|
||||||
return Err(fmt::Error);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 2000-03-01 (mod 400 year, immediately after feb29 */
|
|
||||||
const LEAPOCH: i64 = 11017;
|
|
||||||
const DAYS_PER_400Y: i64 = 365*400 + 97;
|
|
||||||
const DAYS_PER_100Y: i64 = 365*100 + 24;
|
|
||||||
const DAYS_PER_4Y: i64 = 365*4 + 1;
|
|
||||||
|
|
||||||
let days = (secs_since_epoch / 86400) as i64 - LEAPOCH;
|
|
||||||
let secs_of_day = secs_since_epoch % 86400;
|
|
||||||
|
|
||||||
let mut qc_cycles = days / DAYS_PER_400Y;
|
|
||||||
let mut remdays = days % DAYS_PER_400Y;
|
|
||||||
|
|
||||||
if remdays < 0 {
|
|
||||||
remdays += DAYS_PER_400Y;
|
|
||||||
qc_cycles -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut c_cycles = remdays / DAYS_PER_100Y;
|
|
||||||
if c_cycles == 4 { c_cycles -= 1; }
|
|
||||||
remdays -= c_cycles * DAYS_PER_100Y;
|
|
||||||
|
|
||||||
let mut q_cycles = remdays / DAYS_PER_4Y;
|
|
||||||
if q_cycles == 25 { q_cycles -= 1; }
|
|
||||||
remdays -= q_cycles * DAYS_PER_4Y;
|
|
||||||
|
|
||||||
let mut remyears = remdays / 365;
|
|
||||||
if remyears == 4 { remyears -= 1; }
|
|
||||||
remdays -= remyears * 365;
|
|
||||||
|
|
||||||
let mut year = 2000 +
|
|
||||||
remyears + 4*q_cycles + 100*c_cycles + 400*qc_cycles;
|
|
||||||
|
|
||||||
let months = [31,30,31,30,31,31,30,31,30,31,31,29];
|
|
||||||
let mut mon = 0;
|
|
||||||
for mon_len in months.iter() {
|
|
||||||
mon += 1;
|
|
||||||
if remdays < *mon_len {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
remdays -= *mon_len;
|
|
||||||
}
|
|
||||||
let mday = remdays+1;
|
|
||||||
let mon = if mon + 2 > 12 {
|
|
||||||
year += 1;
|
|
||||||
mon - 10
|
|
||||||
} else {
|
|
||||||
mon + 2
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut buf: [u8; 30] = [
|
|
||||||
// Too long to write as: b"0000-00-00T00:00:00.000000000Z"
|
|
||||||
b'0', b'0', b'0', b'0', b'-', b'0', b'0', b'-', b'0', b'0', b'T',
|
|
||||||
b'0', b'0', b':', b'0', b'0', b':', b'0', b'0',
|
|
||||||
b'.', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'Z',
|
|
||||||
];
|
|
||||||
buf[0] = b'0' + (year / 1000) as u8;
|
|
||||||
buf[1] = b'0' + (year / 100 % 10) as u8;
|
|
||||||
buf[2] = b'0' + (year / 10 % 10) as u8;
|
|
||||||
buf[3] = b'0' + (year % 10) as u8;
|
|
||||||
buf[5] = b'0' + (mon / 10) as u8;
|
|
||||||
buf[6] = b'0' + (mon % 10) as u8;
|
|
||||||
buf[8] = b'0' + (mday / 10) as u8;
|
|
||||||
buf[9] = b'0' + (mday % 10) as u8;
|
|
||||||
buf[11] = b'0' + (secs_of_day / 3600 / 10) as u8;
|
|
||||||
buf[12] = b'0' + (secs_of_day / 3600 % 10) as u8;
|
|
||||||
buf[14] = b'0' + (secs_of_day / 60 / 10 % 6) as u8;
|
|
||||||
buf[15] = b'0' + (secs_of_day / 60 % 10) as u8;
|
|
||||||
buf[17] = b'0' + (secs_of_day / 10 % 6) as u8;
|
|
||||||
buf[18] = b'0' + (secs_of_day % 10) as u8;
|
|
||||||
|
|
||||||
let offset = if self.1 == Seconds || nanos == 0 && self.1 == Smart {
|
|
||||||
buf[19] = b'Z';
|
|
||||||
19
|
|
||||||
} else if self.1 == Millis {
|
|
||||||
buf[20] = b'0' + (nanos / 100_000_000) as u8;
|
|
||||||
buf[21] = b'0' + (nanos / 10_000_000 % 10) as u8;
|
|
||||||
buf[22] = b'0' + (nanos / 1_000_000 % 10) as u8;
|
|
||||||
buf[23] = b'Z';
|
|
||||||
23
|
|
||||||
} else if self.1 == Micros {
|
|
||||||
buf[20] = b'0' + (nanos / 100_000_000) as u8;
|
|
||||||
buf[21] = b'0' + (nanos / 10_000_000 % 10) as u8;
|
|
||||||
buf[22] = b'0' + (nanos / 1_000_000 % 10) as u8;
|
|
||||||
buf[23] = b'0' + (nanos / 100_000 % 10) as u8;
|
|
||||||
buf[24] = b'0' + (nanos / 10_000 % 10) as u8;
|
|
||||||
buf[25] = b'0' + (nanos / 1_000 % 10) as u8;
|
|
||||||
buf[26] = b'Z';
|
|
||||||
26
|
|
||||||
} else {
|
|
||||||
buf[20] = b'0' + (nanos / 100_000_000) as u8;
|
|
||||||
buf[21] = b'0' + (nanos / 10_000_000 % 10) as u8;
|
|
||||||
buf[22] = b'0' + (nanos / 1_000_000 % 10) as u8;
|
|
||||||
buf[23] = b'0' + (nanos / 100_000 % 10) as u8;
|
|
||||||
buf[24] = b'0' + (nanos / 10_000 % 10) as u8;
|
|
||||||
buf[25] = b'0' + (nanos / 1_000 % 10) as u8;
|
|
||||||
buf[26] = b'0' + (nanos / 100 % 10) as u8;
|
|
||||||
buf[27] = b'0' + (nanos / 10 % 10) as u8;
|
|
||||||
buf[28] = b'0' + (nanos / 1 % 10) as u8;
|
|
||||||
// 29th is 'Z'
|
|
||||||
29
|
|
||||||
};
|
|
||||||
|
|
||||||
// we know our chars are all ascii
|
|
||||||
f.write_str(unsafe { str::from_utf8_unchecked(&buf[..offset+1]) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
extern crate time;
|
|
||||||
extern crate rand;
|
|
||||||
|
|
||||||
use std::str::from_utf8;
|
|
||||||
use self::rand::Rng;
|
|
||||||
use std::time::{UNIX_EPOCH, SystemTime, Duration};
|
|
||||||
use super::{parse_rfc3339, parse_rfc3339_weak, format_rfc3339};
|
|
||||||
use super::{format_rfc3339_millis, format_rfc3339_micros};
|
|
||||||
use super::{format_rfc3339_nanos};
|
|
||||||
use super::max;
|
|
||||||
|
|
||||||
fn from_sec(sec: u64) -> (String, SystemTime) {
|
|
||||||
let s = time::at_utc(time::Timespec { sec: sec as i64, nsec: 0 })
|
|
||||||
.rfc3339().to_string();
|
|
||||||
let time = UNIX_EPOCH + Duration::new(sec, 0);
|
|
||||||
return (s, time)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg(all(target_pointer_width="32", target_os="linux"))]
|
|
||||||
fn year_after_2038_fails_gracefully() {
|
|
||||||
// next second
|
|
||||||
assert_eq!(parse_rfc3339("2038-01-19T03:14:08Z").unwrap_err(),
|
|
||||||
super::Error::OutOfRange);
|
|
||||||
assert_eq!(parse_rfc3339("9999-12-31T23:59:59Z").unwrap_err(),
|
|
||||||
super::Error::OutOfRange);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn smoke_tests_parse() {
|
|
||||||
assert_eq!(parse_rfc3339("1970-01-01T00:00:00Z").unwrap(),
|
|
||||||
UNIX_EPOCH + Duration::new(0, 0));
|
|
||||||
assert_eq!(parse_rfc3339("1970-01-01T00:00:01Z").unwrap(),
|
|
||||||
UNIX_EPOCH + Duration::new(1, 0));
|
|
||||||
assert_eq!(parse_rfc3339("2018-02-13T23:08:32Z").unwrap(),
|
|
||||||
UNIX_EPOCH + Duration::new(1518563312, 0));
|
|
||||||
assert_eq!(parse_rfc3339("2012-01-01T00:00:00Z").unwrap(),
|
|
||||||
UNIX_EPOCH + Duration::new(1325376000, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn smoke_tests_format() {
|
|
||||||
assert_eq!(
|
|
||||||
format_rfc3339(UNIX_EPOCH + Duration::new(0, 0)).to_string(),
|
|
||||||
"1970-01-01T00:00:00Z");
|
|
||||||
assert_eq!(
|
|
||||||
format_rfc3339(UNIX_EPOCH + Duration::new(1, 0)).to_string(),
|
|
||||||
"1970-01-01T00:00:01Z");
|
|
||||||
assert_eq!(
|
|
||||||
format_rfc3339(UNIX_EPOCH + Duration::new(1518563312, 0)).to_string(),
|
|
||||||
"2018-02-13T23:08:32Z");
|
|
||||||
assert_eq!(
|
|
||||||
format_rfc3339(UNIX_EPOCH + Duration::new(1325376000, 0)).to_string(),
|
|
||||||
"2012-01-01T00:00:00Z");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn smoke_tests_format_millis() {
|
|
||||||
assert_eq!(
|
|
||||||
format_rfc3339_millis(UNIX_EPOCH +
|
|
||||||
Duration::new(0, 0)).to_string(),
|
|
||||||
"1970-01-01T00:00:00.000Z");
|
|
||||||
assert_eq!(
|
|
||||||
format_rfc3339_millis(UNIX_EPOCH +
|
|
||||||
Duration::new(1518563312, 123_000_000)).to_string(),
|
|
||||||
"2018-02-13T23:08:32.123Z");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn smoke_tests_format_micros() {
|
|
||||||
assert_eq!(
|
|
||||||
format_rfc3339_micros(UNIX_EPOCH +
|
|
||||||
Duration::new(0, 0)).to_string(),
|
|
||||||
"1970-01-01T00:00:00.000000Z");
|
|
||||||
assert_eq!(
|
|
||||||
format_rfc3339_micros(UNIX_EPOCH +
|
|
||||||
Duration::new(1518563312, 123_000_000)).to_string(),
|
|
||||||
"2018-02-13T23:08:32.123000Z");
|
|
||||||
assert_eq!(
|
|
||||||
format_rfc3339_micros(UNIX_EPOCH +
|
|
||||||
Duration::new(1518563312, 456_123_000)).to_string(),
|
|
||||||
"2018-02-13T23:08:32.456123Z");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn smoke_tests_format_nanos() {
|
|
||||||
assert_eq!(
|
|
||||||
format_rfc3339_nanos(UNIX_EPOCH +
|
|
||||||
Duration::new(0, 0)).to_string(),
|
|
||||||
"1970-01-01T00:00:00.000000000Z");
|
|
||||||
assert_eq!(
|
|
||||||
format_rfc3339_nanos(UNIX_EPOCH +
|
|
||||||
Duration::new(1518563312, 123_000_000)).to_string(),
|
|
||||||
"2018-02-13T23:08:32.123000000Z");
|
|
||||||
assert_eq!(
|
|
||||||
format_rfc3339_nanos(UNIX_EPOCH +
|
|
||||||
Duration::new(1518563312, 789_456_123)).to_string(),
|
|
||||||
"2018-02-13T23:08:32.789456123Z");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn upper_bound() {
|
|
||||||
let max = UNIX_EPOCH + Duration::new(max::SECONDS, 0);
|
|
||||||
assert_eq!(parse_rfc3339(&max::TIMESTAMP).unwrap(), max);
|
|
||||||
assert_eq!(format_rfc3339(max).to_string(), max::TIMESTAMP);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn leap_second() {
|
|
||||||
assert_eq!(parse_rfc3339("2016-12-31T23:59:60Z").unwrap(),
|
|
||||||
UNIX_EPOCH + Duration::new(1483228799, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn first_731_days() {
|
|
||||||
let year_start = 0; // 1970
|
|
||||||
for day in 0.. (365 * 2 + 1) { // scan leap year and non-leap year
|
|
||||||
let (s, time) = from_sec(year_start + day * 86400);
|
|
||||||
assert_eq!(parse_rfc3339(&s).unwrap(), time);
|
|
||||||
assert_eq!(format_rfc3339(time).to_string(), s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn the_731_consecutive_days() {
|
|
||||||
let year_start = 1325376000; // 2012
|
|
||||||
for day in 0.. (365 * 2 + 1) { // scan leap year and non-leap year
|
|
||||||
let (s, time) = from_sec(year_start + day * 86400);
|
|
||||||
assert_eq!(parse_rfc3339(&s).unwrap(), time);
|
|
||||||
assert_eq!(format_rfc3339(time).to_string(), s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn all_86400_seconds() {
|
|
||||||
let day_start = 1325376000;
|
|
||||||
for second in 0..86400 { // scan leap year and non-leap year
|
|
||||||
let (s, time) = from_sec(day_start + second);
|
|
||||||
assert_eq!(parse_rfc3339(&s).unwrap(), time);
|
|
||||||
assert_eq!(format_rfc3339(time).to_string(), s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn random_past() {
|
|
||||||
let upper = SystemTime::now().duration_since(UNIX_EPOCH).unwrap()
|
|
||||||
.as_secs();
|
|
||||||
for _ in 0..10000 {
|
|
||||||
let sec = rand::thread_rng().gen_range(0, upper);
|
|
||||||
let (s, time) = from_sec(sec);
|
|
||||||
assert_eq!(parse_rfc3339(&s).unwrap(), time);
|
|
||||||
assert_eq!(format_rfc3339(time).to_string(), s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn random_wide_range() {
|
|
||||||
for _ in 0..100000 {
|
|
||||||
let sec = rand::thread_rng().gen_range(0, max::SECONDS);
|
|
||||||
let (s, time) = from_sec(sec);
|
|
||||||
assert_eq!(parse_rfc3339(&s).unwrap(), time);
|
|
||||||
assert_eq!(format_rfc3339(time).to_string(), s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn milliseconds() {
|
|
||||||
assert_eq!(parse_rfc3339("1970-01-01T00:00:00.123Z").unwrap(),
|
|
||||||
UNIX_EPOCH + Duration::new(0, 123000000));
|
|
||||||
assert_eq!(format_rfc3339(UNIX_EPOCH + Duration::new(0, 123000000))
|
|
||||||
.to_string(), "1970-01-01T00:00:00.123000000Z");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[should_panic(expected="OutOfRange")]
|
|
||||||
fn zero_month() {
|
|
||||||
parse_rfc3339("1970-00-01T00:00:00Z").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[should_panic(expected="OutOfRange")]
|
|
||||||
fn big_month() {
|
|
||||||
parse_rfc3339("1970-32-01T00:00:00Z").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[should_panic(expected="OutOfRange")]
|
|
||||||
fn zero_day() {
|
|
||||||
parse_rfc3339("1970-01-00T00:00:00Z").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[should_panic(expected="OutOfRange")]
|
|
||||||
fn big_day() {
|
|
||||||
parse_rfc3339("1970-12-35T00:00:00Z").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[should_panic(expected="OutOfRange")]
|
|
||||||
fn big_day2() {
|
|
||||||
parse_rfc3339("1970-02-30T00:00:00Z").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[should_panic(expected="OutOfRange")]
|
|
||||||
fn big_second() {
|
|
||||||
parse_rfc3339("1970-12-30T00:00:78Z").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[should_panic(expected="OutOfRange")]
|
|
||||||
fn big_minute() {
|
|
||||||
parse_rfc3339("1970-12-30T00:78:00Z").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[should_panic(expected="OutOfRange")]
|
|
||||||
fn big_hour() {
|
|
||||||
parse_rfc3339("1970-12-30T24:00:00Z").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn break_data() {
|
|
||||||
for pos in 0.."2016-12-31T23:59:60Z".len() {
|
|
||||||
let mut s = b"2016-12-31T23:59:60Z".to_vec();
|
|
||||||
s[pos] = b'x';
|
|
||||||
parse_rfc3339(from_utf8(&s).unwrap()).unwrap_err();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn weak_smoke_tests() {
|
|
||||||
assert_eq!(parse_rfc3339_weak("1970-01-01 00:00:00").unwrap(),
|
|
||||||
UNIX_EPOCH + Duration::new(0, 0));
|
|
||||||
parse_rfc3339("1970-01-01 00:00:00").unwrap_err();
|
|
||||||
|
|
||||||
assert_eq!(parse_rfc3339_weak("1970-01-01 00:00:00.000123").unwrap(),
|
|
||||||
UNIX_EPOCH + Duration::new(0, 123000));
|
|
||||||
parse_rfc3339("1970-01-01 00:00:00.000123").unwrap_err();
|
|
||||||
|
|
||||||
assert_eq!(parse_rfc3339_weak("1970-01-01T00:00:00.000123").unwrap(),
|
|
||||||
UNIX_EPOCH + Duration::new(0, 123000));
|
|
||||||
parse_rfc3339("1970-01-01T00:00:00.000123").unwrap_err();
|
|
||||||
|
|
||||||
assert_eq!(parse_rfc3339_weak("1970-01-01 00:00:00.000123Z").unwrap(),
|
|
||||||
UNIX_EPOCH + Duration::new(0, 123000));
|
|
||||||
parse_rfc3339("1970-01-01 00:00:00.000123Z").unwrap_err();
|
|
||||||
|
|
||||||
assert_eq!(parse_rfc3339_weak("1970-01-01 00:00:00Z").unwrap(),
|
|
||||||
UNIX_EPOCH + Duration::new(0, 0));
|
|
||||||
parse_rfc3339("1970-01-01 00:00:00Z").unwrap_err();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,411 +0,0 @@
|
||||||
use std::fmt;
|
|
||||||
use std::str::Chars;
|
|
||||||
use std::time::Duration;
|
|
||||||
use std::error::Error as StdError;
|
|
||||||
|
|
||||||
quick_error! {
|
|
||||||
/// Error parsing human-friendly duration
|
|
||||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
|
||||||
pub enum Error {
|
|
||||||
/// Invalid character during parsing
|
|
||||||
///
|
|
||||||
/// More specifically anything that is not alphanumeric is prohibited
|
|
||||||
///
|
|
||||||
/// The field is an byte offset of the character in the string.
|
|
||||||
InvalidCharacter(offset: usize) {
|
|
||||||
display("invalid character at {}", offset)
|
|
||||||
description("invalid character")
|
|
||||||
}
|
|
||||||
/// Non-numeric value where number is expected
|
|
||||||
///
|
|
||||||
/// This usually means that either time unit is broken into words,
|
|
||||||
/// e.g. `m sec` instead of `msec`, or just number is omitted,
|
|
||||||
/// for example `2 hours min` instead of `2 hours 1 min`
|
|
||||||
///
|
|
||||||
/// The field is an byte offset of the errorneous character
|
|
||||||
/// in the string.
|
|
||||||
NumberExpected(offset: usize) {
|
|
||||||
display("expected number at {}", offset)
|
|
||||||
description("expected number")
|
|
||||||
}
|
|
||||||
/// Unit in the number is not one of allowed units
|
|
||||||
///
|
|
||||||
/// See documentation of `parse_duration` for the list of supported
|
|
||||||
/// time units.
|
|
||||||
///
|
|
||||||
/// The two fields are start and end (exclusive) of the slice from
|
|
||||||
/// the original string, containing errorneous value
|
|
||||||
UnknownUnit(start: usize, end: usize) {
|
|
||||||
display("unknown unit at {}-{}", start, end)
|
|
||||||
description("unknown unit")
|
|
||||||
}
|
|
||||||
/// The numeric value is too large
|
|
||||||
///
|
|
||||||
/// Usually this means value is too large to be useful. If user writes
|
|
||||||
/// data in subsecond units, then the maximum is about 3k years. When
|
|
||||||
/// using seconds, or larger units, the limit is even larger.
|
|
||||||
NumberOverflow {
|
|
||||||
display(self_) -> ("{}", self_.description())
|
|
||||||
description("number is too large")
|
|
||||||
}
|
|
||||||
/// The value was an empty string (or consists only whitespace)
|
|
||||||
Empty {
|
|
||||||
display(self_) -> ("{}", self_.description())
|
|
||||||
description("value was empty")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A wrapper type that allows you to Display a Duration
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct FormattedDuration(Duration);
|
|
||||||
|
|
||||||
trait OverflowOp: Sized {
|
|
||||||
fn mul(self, other: Self) -> Result<Self, Error>;
|
|
||||||
fn add(self, other: Self) -> Result<Self, Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OverflowOp for u64 {
|
|
||||||
fn mul(self, other: Self) -> Result<Self, Error> {
|
|
||||||
self.checked_mul(other).ok_or(Error::NumberOverflow)
|
|
||||||
}
|
|
||||||
fn add(self, other: Self) -> Result<Self, Error> {
|
|
||||||
self.checked_add(other).ok_or(Error::NumberOverflow)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Parser<'a> {
|
|
||||||
iter: Chars<'a>,
|
|
||||||
src: &'a str,
|
|
||||||
current: (u64, u64),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Parser<'a> {
|
|
||||||
fn off(&self) -> usize {
|
|
||||||
self.src.len() - self.iter.as_str().len()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_first_char(&mut self) -> Result<Option<u64>, Error> {
|
|
||||||
let off = self.off();
|
|
||||||
for c in self.iter.by_ref() {
|
|
||||||
match c {
|
|
||||||
'0'...'9' => {
|
|
||||||
return Ok(Some(c as u64 - '0' as u64));
|
|
||||||
}
|
|
||||||
c if c.is_whitespace() => continue,
|
|
||||||
_ => {
|
|
||||||
return Err(Error::NumberExpected(off));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
fn parse_unit(&mut self, n: u64, start: usize, end: usize)
|
|
||||||
-> Result<(), Error>
|
|
||||||
{
|
|
||||||
let (mut sec, nsec) = match &self.src[start..end] {
|
|
||||||
"nanos" | "nsec" | "ns" => (0u64, n),
|
|
||||||
"usec" | "us" => (0u64, try!(n.mul(1000))),
|
|
||||||
"millis" | "msec" | "ms" => (0u64, try!(n.mul(1000_000))),
|
|
||||||
"seconds" | "second" | "secs" | "sec" | "s" => (n, 0),
|
|
||||||
"minutes" | "minute" | "min" | "mins" | "m"
|
|
||||||
=> (try!(n.mul(60)), 0),
|
|
||||||
"hours" | "hour" | "hr" | "hrs" | "h" => (try!(n.mul(3600)), 0),
|
|
||||||
"days" | "day" | "d" => (try!(n.mul(86400)), 0),
|
|
||||||
"weeks" | "week" | "w" => (try!(n.mul(86400*7)), 0),
|
|
||||||
"months" | "month" | "M" => (try!(n.mul(2630016)), 0), // 30.44d
|
|
||||||
"years" | "year" | "y" => (try!(n.mul(31557600)), 0), // 365.25d
|
|
||||||
_ => return Err(Error::UnknownUnit(start, end)),
|
|
||||||
};
|
|
||||||
let mut nsec = try!(self.current.1.add(nsec));
|
|
||||||
if nsec > 1000_000_000 {
|
|
||||||
sec = try!(sec.add(nsec / 1000_000_000));
|
|
||||||
nsec %= 1000_000_000;
|
|
||||||
}
|
|
||||||
sec = try!(self.current.0.add(sec));
|
|
||||||
self.current = (sec, nsec);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse(mut self) -> Result<Duration, Error> {
|
|
||||||
let mut n = try!(try!(self.parse_first_char()).ok_or(Error::Empty));
|
|
||||||
'outer: loop {
|
|
||||||
let mut off = self.off();
|
|
||||||
while let Some(c) = self.iter.next() {
|
|
||||||
match c {
|
|
||||||
'0'...'9' => {
|
|
||||||
n = try!(n.checked_mul(10)
|
|
||||||
.and_then(|x| x.checked_add(c as u64 - '0' as u64))
|
|
||||||
.ok_or(Error::NumberOverflow));
|
|
||||||
}
|
|
||||||
c if c.is_whitespace() => {}
|
|
||||||
'a'...'z' | 'A'...'Z' => {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err(Error::InvalidCharacter(off));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
off = self.off();
|
|
||||||
}
|
|
||||||
let start = off;
|
|
||||||
let mut off = self.off();
|
|
||||||
while let Some(c) = self.iter.next() {
|
|
||||||
match c {
|
|
||||||
'0'...'9' => {
|
|
||||||
try!(self.parse_unit(n, start, off));
|
|
||||||
n = c as u64 - '0' as u64;
|
|
||||||
continue 'outer;
|
|
||||||
}
|
|
||||||
c if c.is_whitespace() => break,
|
|
||||||
'a'...'z' | 'A'...'Z' => {}
|
|
||||||
_ => {
|
|
||||||
return Err(Error::InvalidCharacter(off));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
off = self.off();
|
|
||||||
}
|
|
||||||
try!(self.parse_unit(n, start, off));
|
|
||||||
n = match try!(self.parse_first_char()) {
|
|
||||||
Some(n) => n,
|
|
||||||
None => return Ok(
|
|
||||||
Duration::new(self.current.0, self.current.1 as u32)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse duration object `1hour 12min 5s`
|
|
||||||
///
|
|
||||||
/// The duration object is a concatenation of time spans. Where each time
|
|
||||||
/// span is an integer number and a suffix. Supported suffixes:
|
|
||||||
///
|
|
||||||
/// * `nsec`, `ns` -- microseconds
|
|
||||||
/// * `usec`, `us` -- microseconds
|
|
||||||
/// * `msec`, `ms` -- milliseconds
|
|
||||||
/// * `seconds`, `second`, `sec`, `s`
|
|
||||||
/// * `minutes`, `minute`, `min`, `m`
|
|
||||||
/// * `hours`, `hour`, `hr`, `h`
|
|
||||||
/// * `days`, `day`, `d`
|
|
||||||
/// * `weeks`, `week`, `w`
|
|
||||||
/// * `months`, `month`, `M` -- defined as 30.44 days
|
|
||||||
/// * `years`, `year`, `y` -- defined as 365.25 days
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use std::time::Duration;
|
|
||||||
/// use humantime::parse_duration;
|
|
||||||
///
|
|
||||||
/// assert_eq!(parse_duration("2h 37min"), Ok(Duration::new(9420, 0)));
|
|
||||||
/// assert_eq!(parse_duration("32ms"), Ok(Duration::new(0, 32_000_000)));
|
|
||||||
/// ```
|
|
||||||
pub fn parse_duration(s: &str) -> Result<Duration, Error> {
|
|
||||||
Parser {
|
|
||||||
iter: s.chars(),
|
|
||||||
src: s,
|
|
||||||
current: (0, 0),
|
|
||||||
}.parse()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Formats duration into a human-readable string
|
|
||||||
///
|
|
||||||
/// Note: this format is guaranteed to have same value when using
|
|
||||||
/// parse_duration, but we can change some details of the exact composition
|
|
||||||
/// of the value.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use std::time::Duration;
|
|
||||||
/// use humantime::format_duration;
|
|
||||||
///
|
|
||||||
/// let val1 = Duration::new(9420, 0);
|
|
||||||
/// assert_eq!(format_duration(val1).to_string(), "2h 37m");
|
|
||||||
/// let val2 = Duration::new(0, 32_000_000);
|
|
||||||
/// assert_eq!(format_duration(val2).to_string(), "32ms");
|
|
||||||
/// ```
|
|
||||||
pub fn format_duration(val: Duration) -> FormattedDuration {
|
|
||||||
FormattedDuration(val)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn item_plural(f: &mut fmt::Formatter, started: &mut bool,
|
|
||||||
name: &str, value: u64)
|
|
||||||
-> fmt::Result
|
|
||||||
{
|
|
||||||
if value > 0 {
|
|
||||||
if *started {
|
|
||||||
f.write_str(" ")?;
|
|
||||||
}
|
|
||||||
write!(f, "{}{}", value, name)?;
|
|
||||||
if value > 1 {
|
|
||||||
f.write_str("s")?;
|
|
||||||
}
|
|
||||||
*started = true;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn item(f: &mut fmt::Formatter, started: &mut bool, name: &str, value: u32)
|
|
||||||
-> fmt::Result
|
|
||||||
{
|
|
||||||
if value > 0 {
|
|
||||||
if *started {
|
|
||||||
f.write_str(" ")?;
|
|
||||||
}
|
|
||||||
write!(f, "{}{}", value, name)?;
|
|
||||||
*started = true;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for FormattedDuration {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
let secs = self.0.as_secs();
|
|
||||||
let nanos = self.0.subsec_nanos();
|
|
||||||
|
|
||||||
if secs == 0 && nanos == 0 {
|
|
||||||
f.write_str("0s")?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let years = secs / 31557600; // 365.25d
|
|
||||||
let ydays = secs % 31557600;
|
|
||||||
let months = ydays / 2630016; // 30.44d
|
|
||||||
let mdays = ydays % 2630016;
|
|
||||||
let days = mdays / 86400;
|
|
||||||
let day_secs = mdays % 86400;
|
|
||||||
let hours = day_secs / 3600;
|
|
||||||
let minutes = day_secs % 3600 / 60;
|
|
||||||
let seconds = day_secs % 60;
|
|
||||||
|
|
||||||
let millis = nanos / 1_000_000;
|
|
||||||
let micros = nanos / 1000 % 1000;
|
|
||||||
let nanosec = nanos % 1000;
|
|
||||||
|
|
||||||
let ref mut started = false;
|
|
||||||
item_plural(f, started, "year", years)?;
|
|
||||||
item_plural(f, started, "month", months)?;
|
|
||||||
item_plural(f, started, "day", days)?;
|
|
||||||
item(f, started, "h", hours as u32)?;
|
|
||||||
item(f, started, "m", minutes as u32)?;
|
|
||||||
item(f, started, "s", seconds as u32)?;
|
|
||||||
item(f, started, "ms", millis)?;
|
|
||||||
item(f, started, "us", micros)?;
|
|
||||||
item(f, started, "ns", nanosec)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
extern crate rand;
|
|
||||||
|
|
||||||
use std::time::Duration;
|
|
||||||
use self::rand::Rng;
|
|
||||||
use super::{parse_duration, format_duration};
|
|
||||||
use super::Error;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_units() {
|
|
||||||
assert_eq!(parse_duration("17nsec"), Ok(Duration::new(0, 17)));
|
|
||||||
assert_eq!(parse_duration("17nanos"), Ok(Duration::new(0, 17)));
|
|
||||||
assert_eq!(parse_duration("33ns"), Ok(Duration::new(0, 33)));
|
|
||||||
assert_eq!(parse_duration("3usec"), Ok(Duration::new(0, 3000)));
|
|
||||||
assert_eq!(parse_duration("78us"), Ok(Duration::new(0, 78000)));
|
|
||||||
assert_eq!(parse_duration("31msec"), Ok(Duration::new(0, 31000000)));
|
|
||||||
assert_eq!(parse_duration("31millis"), Ok(Duration::new(0, 31000000)));
|
|
||||||
assert_eq!(parse_duration("6ms"), Ok(Duration::new(0, 6000000)));
|
|
||||||
assert_eq!(parse_duration("3000s"), Ok(Duration::new(3000, 0)));
|
|
||||||
assert_eq!(parse_duration("300sec"), Ok(Duration::new(300, 0)));
|
|
||||||
assert_eq!(parse_duration("300secs"), Ok(Duration::new(300, 0)));
|
|
||||||
assert_eq!(parse_duration("50seconds"), Ok(Duration::new(50, 0)));
|
|
||||||
assert_eq!(parse_duration("1second"), Ok(Duration::new(1, 0)));
|
|
||||||
assert_eq!(parse_duration("100m"), Ok(Duration::new(6000, 0)));
|
|
||||||
assert_eq!(parse_duration("12min"), Ok(Duration::new(720, 0)));
|
|
||||||
assert_eq!(parse_duration("12mins"), Ok(Duration::new(720, 0)));
|
|
||||||
assert_eq!(parse_duration("1minute"), Ok(Duration::new(60, 0)));
|
|
||||||
assert_eq!(parse_duration("7minutes"), Ok(Duration::new(420, 0)));
|
|
||||||
assert_eq!(parse_duration("2h"), Ok(Duration::new(7200, 0)));
|
|
||||||
assert_eq!(parse_duration("7hr"), Ok(Duration::new(25200, 0)));
|
|
||||||
assert_eq!(parse_duration("7hrs"), Ok(Duration::new(25200, 0)));
|
|
||||||
assert_eq!(parse_duration("1hour"), Ok(Duration::new(3600, 0)));
|
|
||||||
assert_eq!(parse_duration("24hours"), Ok(Duration::new(86400, 0)));
|
|
||||||
assert_eq!(parse_duration("1day"), Ok(Duration::new(86400, 0)));
|
|
||||||
assert_eq!(parse_duration("2days"), Ok(Duration::new(172800, 0)));
|
|
||||||
assert_eq!(parse_duration("365d"), Ok(Duration::new(31536000, 0)));
|
|
||||||
assert_eq!(parse_duration("1week"), Ok(Duration::new(604800, 0)));
|
|
||||||
assert_eq!(parse_duration("7weeks"), Ok(Duration::new(4233600, 0)));
|
|
||||||
assert_eq!(parse_duration("52w"), Ok(Duration::new(31449600, 0)));
|
|
||||||
assert_eq!(parse_duration("1month"), Ok(Duration::new(2630016, 0)));
|
|
||||||
assert_eq!(parse_duration("3months"), Ok(Duration::new(3*2630016, 0)));
|
|
||||||
assert_eq!(parse_duration("12M"), Ok(Duration::new(31560192, 0)));
|
|
||||||
assert_eq!(parse_duration("1year"), Ok(Duration::new(31557600, 0)));
|
|
||||||
assert_eq!(parse_duration("7years"), Ok(Duration::new(7*31557600, 0)));
|
|
||||||
assert_eq!(parse_duration("17y"), Ok(Duration::new(536479200, 0)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_combo() {
|
|
||||||
assert_eq!(parse_duration("20 min 17 nsec "), Ok(Duration::new(1200, 17)));
|
|
||||||
assert_eq!(parse_duration("2h 15m"), Ok(Duration::new(8100, 0)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn all_86400_seconds() {
|
|
||||||
for second in 0..86400 { // scan leap year and non-leap year
|
|
||||||
let d = Duration::new(second, 0);
|
|
||||||
assert_eq!(d,
|
|
||||||
parse_duration(&format_duration(d).to_string()).unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn random_second() {
|
|
||||||
for _ in 0..10000 {
|
|
||||||
let sec = rand::thread_rng().gen_range(0, 253370764800);
|
|
||||||
let d = Duration::new(sec, 0);
|
|
||||||
assert_eq!(d,
|
|
||||||
parse_duration(&format_duration(d).to_string()).unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn random_any() {
|
|
||||||
for _ in 0..10000 {
|
|
||||||
let sec = rand::thread_rng().gen_range(0, 253370764800);
|
|
||||||
let nanos = rand::thread_rng().gen_range(0, 1_000_000_000);
|
|
||||||
let d = Duration::new(sec, nanos);
|
|
||||||
assert_eq!(d,
|
|
||||||
parse_duration(&format_duration(d).to_string()).unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_overlow() {
|
|
||||||
// Overflow on subseconds is earlier because of how we do conversion
|
|
||||||
// we could fix it, but I don't see any good reason for this
|
|
||||||
assert_eq!(parse_duration("100000000000000000000ns"),
|
|
||||||
Err(Error::NumberOverflow));
|
|
||||||
assert_eq!(parse_duration("100000000000000000us"),
|
|
||||||
Err(Error::NumberOverflow));
|
|
||||||
assert_eq!(parse_duration("100000000000000ms"),
|
|
||||||
Err(Error::NumberOverflow));
|
|
||||||
|
|
||||||
assert_eq!(parse_duration("100000000000000000000s"),
|
|
||||||
Err(Error::NumberOverflow));
|
|
||||||
assert_eq!(parse_duration("10000000000000000000m"),
|
|
||||||
Err(Error::NumberOverflow));
|
|
||||||
assert_eq!(parse_duration("1000000000000000000h"),
|
|
||||||
Err(Error::NumberOverflow));
|
|
||||||
assert_eq!(parse_duration("100000000000000000d"),
|
|
||||||
Err(Error::NumberOverflow));
|
|
||||||
assert_eq!(parse_duration("10000000000000000w"),
|
|
||||||
Err(Error::NumberOverflow));
|
|
||||||
assert_eq!(parse_duration("1000000000000000M"),
|
|
||||||
Err(Error::NumberOverflow));
|
|
||||||
assert_eq!(parse_duration("10000000000000y"),
|
|
||||||
Err(Error::NumberOverflow));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
//! Human-friendly time parser and formatter
|
|
||||||
//!
|
|
||||||
//! Features:
|
|
||||||
//!
|
|
||||||
//! * Parses durations in free form like `15days 2min 2s`
|
|
||||||
//! * Formats durations in similar form `2years 2min 12us`
|
|
||||||
//! * Parses and formats timestamp in `rfc3339` format: `2018-01-01T12:53:00Z`
|
|
||||||
//! * Parses timestamps in a weaker format: `2018-01-01 12:53:00`
|
|
||||||
//!
|
|
||||||
//! Timestamp parsing/formatting is super-fast because format is basically
|
|
||||||
//! fixed.
|
|
||||||
//!
|
|
||||||
//! See [serde-humantime] for serde integration.
|
|
||||||
//!
|
|
||||||
//! [serde-humantime]: https://docs.rs/serde-humantime/0.1.1/serde_humantime/
|
|
||||||
#![warn(missing_debug_implementations)]
|
|
||||||
#![warn(missing_docs)]
|
|
||||||
|
|
||||||
#[macro_use] extern crate quick_error;
|
|
||||||
|
|
||||||
mod duration;
|
|
||||||
mod wrapper;
|
|
||||||
mod date;
|
|
||||||
|
|
||||||
pub use duration::{parse_duration, Error as DurationError};
|
|
||||||
pub use duration::{format_duration, FormattedDuration};
|
|
||||||
pub use wrapper::{Duration, Timestamp};
|
|
||||||
pub use date::{parse_rfc3339, parse_rfc3339_weak, Error as TimestampError};
|
|
||||||
pub use date::{
|
|
||||||
format_rfc3339, format_rfc3339_micros, format_rfc3339_millis, format_rfc3339_nanos,
|
|
||||||
format_rfc3339_seconds,
|
|
||||||
};
|
|
||||||
pub use date::{Rfc3339Timestamp};
|
|
|
@ -1,107 +0,0 @@
|
||||||
use std::str::FromStr;
|
|
||||||
use std::ops::Deref;
|
|
||||||
use std::fmt;
|
|
||||||
use std::time::{Duration as StdDuration, SystemTime};
|
|
||||||
|
|
||||||
use duration::{self, parse_duration, format_duration};
|
|
||||||
use date::{self, parse_rfc3339_weak, format_rfc3339};
|
|
||||||
|
|
||||||
/// A wrapper for duration that has `FromStr` implementation
|
|
||||||
///
|
|
||||||
/// This is useful if you want to use it somewhere where `FromStr` is
|
|
||||||
/// expected.
|
|
||||||
///
|
|
||||||
/// See `parse_duration` for the description of the format.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use std::time::Duration;
|
|
||||||
/// let x: Duration;
|
|
||||||
/// x = "12h 5min 2ns".parse::<humantime::Duration>().unwrap().into();
|
|
||||||
/// assert_eq!(x, Duration::new(12*3600 + 5*60, 2))
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
|
||||||
pub struct Duration(StdDuration);
|
|
||||||
|
|
||||||
/// A wrapper for SystemTime that has `FromStr` implementation
|
|
||||||
///
|
|
||||||
/// This is useful if you want to use it somewhere where `FromStr` is
|
|
||||||
/// expected.
|
|
||||||
///
|
|
||||||
/// See `parse_rfc3339_weak` for the description of the format. The "weak"
|
|
||||||
/// format is used as it's more pemissive for human input as this is the
|
|
||||||
/// expected use of the type (e.g. command-line parsing).
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use std::time::SystemTime;
|
|
||||||
/// let x: SystemTime;
|
|
||||||
/// x = "2018-02-16T00:31:37Z".parse::<humantime::Timestamp>().unwrap().into();
|
|
||||||
/// assert_eq!(humantime::format_rfc3339(x).to_string(), "2018-02-16T00:31:37Z");
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
|
||||||
pub struct Timestamp(SystemTime);
|
|
||||||
|
|
||||||
impl AsRef<StdDuration> for Duration {
|
|
||||||
fn as_ref(&self) -> &StdDuration { &self.0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for Duration {
|
|
||||||
type Target = StdDuration;
|
|
||||||
fn deref(&self) -> &StdDuration { &self.0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<StdDuration> for Duration {
|
|
||||||
fn into(self) -> StdDuration { self.0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<StdDuration> for Duration {
|
|
||||||
fn from(dur: StdDuration) -> Duration { Duration(dur) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for Duration {
|
|
||||||
type Err = duration::Error;
|
|
||||||
fn from_str(s: &str) -> Result<Duration, Self::Err> {
|
|
||||||
parse_duration(s).map(Duration)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Duration {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
format_duration(self.0).fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRef<SystemTime> for Timestamp {
|
|
||||||
fn as_ref(&self) -> &SystemTime { &self.0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for Timestamp {
|
|
||||||
type Target = SystemTime;
|
|
||||||
fn deref(&self) -> &SystemTime { &self.0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<SystemTime> for Timestamp {
|
|
||||||
fn into(self) -> SystemTime { self.0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<SystemTime> for Timestamp {
|
|
||||||
fn from(dur: SystemTime) -> Timestamp { Timestamp(dur) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for Timestamp {
|
|
||||||
type Err = date::Error;
|
|
||||||
fn from_str(s: &str) -> Result<Timestamp, Self::Err> {
|
|
||||||
parse_rfc3339_weak(s).map(Timestamp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Timestamp {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
format_rfc3339(self.0).fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,92 +0,0 @@
|
||||||
commands:
|
|
||||||
|
|
||||||
cargo: !Command
|
|
||||||
description: Run any cargo command
|
|
||||||
container: ubuntu
|
|
||||||
run: [cargo]
|
|
||||||
|
|
||||||
make: !Command
|
|
||||||
description: Build the library
|
|
||||||
container: ubuntu
|
|
||||||
run: [cargo, build]
|
|
||||||
|
|
||||||
test64: !Command
|
|
||||||
description: Test the 64bit library
|
|
||||||
container: ubuntu
|
|
||||||
environ: { RUST_BACKTRACE: 1 }
|
|
||||||
run: [cargo, test]
|
|
||||||
|
|
||||||
test32: !Command
|
|
||||||
description: Test the 32bit library
|
|
||||||
container: ubuntu32
|
|
||||||
environ: { RUST_BACKTRACE: 1 }
|
|
||||||
run: [cargo, test]
|
|
||||||
|
|
||||||
test: !Command
|
|
||||||
description: Test the 64bit library
|
|
||||||
container: ubuntu
|
|
||||||
environ: { RUST_BACKTRACE: 1 }
|
|
||||||
prerequisites: [test64, test32]
|
|
||||||
run: [echo, okay]
|
|
||||||
|
|
||||||
bench: !Command
|
|
||||||
description: Run benchmarks
|
|
||||||
container: bench
|
|
||||||
environ: { RUST_BACKTRACE: 1 }
|
|
||||||
run: [cargo, bench]
|
|
||||||
|
|
||||||
_bulk: !Command
|
|
||||||
description: Run `bulk` command (for version bookkeeping)
|
|
||||||
container: ubuntu
|
|
||||||
run: [bulk]
|
|
||||||
|
|
||||||
containers:
|
|
||||||
|
|
||||||
ubuntu:
|
|
||||||
setup:
|
|
||||||
- !Ubuntu xenial
|
|
||||||
- !UbuntuUniverse
|
|
||||||
- !Install [ca-certificates, build-essential, vim]
|
|
||||||
|
|
||||||
- !TarInstall
|
|
||||||
url: "https://static.rust-lang.org/dist/rust-1.24.0-x86_64-unknown-linux-gnu.tar.gz"
|
|
||||||
script: "./install.sh --prefix=/usr \
|
|
||||||
--components=rustc,rust-std-x86_64-unknown-linux-gnu,cargo"
|
|
||||||
- &bulk !Tar
|
|
||||||
url: "https://github.com/tailhook/bulk/releases/download/v0.4.10/bulk-v0.4.10.tar.gz"
|
|
||||||
sha256: 481513f8a0306a9857d045497fb5b50b50a51e9ff748909ecf7d2bda1de275ab
|
|
||||||
path: /
|
|
||||||
|
|
||||||
environ:
|
|
||||||
HOME: /work/target
|
|
||||||
USER: pc
|
|
||||||
|
|
||||||
ubuntu32:
|
|
||||||
setup:
|
|
||||||
- !UbuntuRelease
|
|
||||||
codename: xenial
|
|
||||||
arch: i386
|
|
||||||
- !UbuntuUniverse
|
|
||||||
- !Install [ca-certificates, build-essential, vim]
|
|
||||||
|
|
||||||
- !TarInstall
|
|
||||||
url: "https://static.rust-lang.org/dist/rust-1.24.0-i686-unknown-linux-gnu.tar.gz"
|
|
||||||
script: "./install.sh --prefix=/usr \
|
|
||||||
--components=rustc,rust-std-i686-unknown-linux-gnu,cargo"
|
|
||||||
|
|
||||||
environ:
|
|
||||||
HOME: /work/target
|
|
||||||
USER: pc
|
|
||||||
|
|
||||||
bench:
|
|
||||||
setup:
|
|
||||||
- !Ubuntu xenial
|
|
||||||
- !Install [ca-certificates, wget, build-essential]
|
|
||||||
- !TarInstall
|
|
||||||
url: https://static.rust-lang.org/dist/rust-nightly-x86_64-unknown-linux-gnu.tar.gz
|
|
||||||
script: |
|
|
||||||
./install.sh --prefix=/usr \
|
|
||||||
--components=rustc,rust-std-x86_64-unknown-linux-gnu,cargo
|
|
||||||
environ:
|
|
||||||
HOME: /work/target
|
|
||||||
USER: pc
|
|
|
@ -1 +1 @@
|
||||||
{"files":{"Cargo.toml":"105033ac7475000fc0d13cb51973a6fe439a167d05ef79d7db1aa0f82618dedd","benches/avif_benchmark.rs":"e4bdd69c7b434448ef7080bbf488b2f64c2c88121fe1c1f7e9d9c8def3d648ae","src/boxes.rs":"756a3d3638d91fc8e2e58a3e41e663b46d6381539ec12b2e7b6980b205ef4ed2","src/lib.rs":"27ec213bc309ca3701dd0f4d0e5f4af3668c108a4c7f7863285024960c05eae2","src/macros.rs":"76c840f9299797527fe71aa5b378ffb01312767372b45cc62deddb19775400ae","src/tests.rs":"7598fba59c47dc0f713c5a71fa477d046b595ba6362c6e32d775e33197770055","tests/bug-1661347.avif":"a4741189d897401c746492d780bccf4c42dddde8f46d01a791f9656aac2ab164","tests/corrupt/bad-ipma-flags.avif":"ecde7997b97db1910b9dcc7ca8e3c8957da0e83681ea9008c66dc9f12b78ad19","tests/corrupt/bad-ipma-version.avif":"7f9a1a0b4ebbf8d800d22eaae5ff78970cc6b811317db6c1467c6883952b7c9b","tests/corrupt/bug-1655846.avif":"e0a5a06225800fadf05f5352503a4cec11af73eef705c43b4acab5f4a99dea50","tests/corrupt/bug-1661347.avif":"31c26561e1d9eafb60f7c5968b82a0859d203d73f17f26b29276256acee12966","tests/kodim-extents.avif":"e4de6d124535206c3daca797e64cccc6a5b439f93733af52a95b1e82d581a78b","tests/overflow.rs":"16b591d8def1a155b3b997622f6ea255536870d99c3d8f97c51755b77a50de3c","tests/public.rs":"cfc9f440d828d504dfbbb327395c88f92e6685198ef161cd1215188480286d3b"},"package":null}
|
{"files":{"Cargo.toml":"20d60a51a713582d285ab994d13014f101450a359f5ada589b82ea97615ae2e8","benches/avif_benchmark.rs":"e4bdd69c7b434448ef7080bbf488b2f64c2c88121fe1c1f7e9d9c8def3d648ae","src/boxes.rs":"756a3d3638d91fc8e2e58a3e41e663b46d6381539ec12b2e7b6980b205ef4ed2","src/lib.rs":"5cc2731dff15507d7b9a3c2e88ea1682dc40ca75901b69f35c9a6e71898c323b","src/macros.rs":"76c840f9299797527fe71aa5b378ffb01312767372b45cc62deddb19775400ae","src/tests.rs":"7598fba59c47dc0f713c5a71fa477d046b595ba6362c6e32d775e33197770055","tests/bug-1661347.avif":"a4741189d897401c746492d780bccf4c42dddde8f46d01a791f9656aac2ab164","tests/corrupt/bad-ipma-flags.avif":"ecde7997b97db1910b9dcc7ca8e3c8957da0e83681ea9008c66dc9f12b78ad19","tests/corrupt/bad-ipma-version.avif":"7f9a1a0b4ebbf8d800d22eaae5ff78970cc6b811317db6c1467c6883952b7c9b","tests/corrupt/bug-1655846.avif":"e0a5a06225800fadf05f5352503a4cec11af73eef705c43b4acab5f4a99dea50","tests/corrupt/bug-1661347.avif":"31c26561e1d9eafb60f7c5968b82a0859d203d73f17f26b29276256acee12966","tests/kodim-extents.avif":"e4de6d124535206c3daca797e64cccc6a5b439f93733af52a95b1e82d581a78b","tests/overflow.rs":"16b591d8def1a155b3b997622f6ea255536870d99c3d8f97c51755b77a50de3c","tests/public.rs":"bee907c7183040b3a2842312d13e20ac005e880d33d2b2c9067d5a48db3d6514"},"package":null}
|
|
@ -1,11 +1,12 @@
|
||||||
[package]
|
[package]
|
||||||
name = "mp4parse"
|
name = "mp4parse"
|
||||||
version = "0.11.4"
|
version = "0.11.5"
|
||||||
authors = [
|
authors = [
|
||||||
"Ralph Giles <giles@mozilla.com>",
|
"Ralph Giles <giles@mozilla.com>",
|
||||||
"Matthew Gregan <kinetik@flim.org>",
|
"Matthew Gregan <kinetik@flim.org>",
|
||||||
"Alfredo Yang <ayang@mozilla.com>",
|
"Alfredo Yang <ayang@mozilla.com>",
|
||||||
"Jon Bauman <jbauman@mozilla.com>",
|
"Jon Bauman <jbauman@mozilla.com>",
|
||||||
|
"Bryce Seager van Dyk <bvandyk@mozilla.com>",
|
||||||
]
|
]
|
||||||
|
|
||||||
description = "Parser for ISO base media file format (mp4)"
|
description = "Parser for ISO base media file format (mp4)"
|
||||||
|
@ -27,10 +28,10 @@ travis-ci = { repository = "https://github.com/mozilla/mp4parse-rust" }
|
||||||
[dependencies]
|
[dependencies]
|
||||||
byteorder = "1.2.1"
|
byteorder = "1.2.1"
|
||||||
bitreader = { version = "0.3.2" }
|
bitreader = { version = "0.3.2" }
|
||||||
env_logger = "0.7.1"
|
env_logger = "0.8"
|
||||||
fallible_collections = { version = "0.3", features = ["std_io"] }
|
fallible_collections = { version = "0.3", features = ["std_io"] }
|
||||||
hashbrown = "0.9"
|
hashbrown = "0.9"
|
||||||
num-traits = "=0.2.10"
|
num-traits = "0.2.14"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
static_assertions = "1.1.0"
|
static_assertions = "1.1.0"
|
||||||
|
|
||||||
|
|
|
@ -2344,6 +2344,11 @@ fn read_moov<T: Read>(f: &mut BMFFBox<T>, context: Option<MediaContext>) -> Resu
|
||||||
BoxType::UserdataBox => {
|
BoxType::UserdataBox => {
|
||||||
userdata = Some(read_udta(&mut b));
|
userdata = Some(read_udta(&mut b));
|
||||||
debug!("{:?}", userdata);
|
debug!("{:?}", userdata);
|
||||||
|
if let Some(Err(_)) = userdata {
|
||||||
|
// There was an error parsing userdata. Such failures are not fatal to overall
|
||||||
|
// parsing, just skip the rest of the box.
|
||||||
|
skip_box_remain(&mut b)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => skip_box_content(&mut b)?,
|
_ => skip_box_content(&mut b)?,
|
||||||
};
|
};
|
||||||
|
|
|
@ -28,6 +28,9 @@ static VIDEO_EME_CENC_MP4: &str = "tests/bipbop_480wp_1001kbps-cenc-video-key1-i
|
||||||
static AUDIO_EME_CBCS_MP4: &str = "tests/bipbop_cbcs_audio_init.mp4";
|
static AUDIO_EME_CBCS_MP4: &str = "tests/bipbop_cbcs_audio_init.mp4";
|
||||||
static VIDEO_EME_CBCS_MP4: &str = "tests/bipbop_cbcs_video_init.mp4";
|
static VIDEO_EME_CBCS_MP4: &str = "tests/bipbop_cbcs_video_init.mp4";
|
||||||
static VIDEO_AV1_MP4: &str = "tests/tiny_av1.mp4";
|
static VIDEO_AV1_MP4: &str = "tests/tiny_av1.mp4";
|
||||||
|
// This file contains invalid userdata in its copyright userdata. See
|
||||||
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1687357 for more information.
|
||||||
|
static VIDEO_INVALID_USERDATA: &str = "tests/invalid_userdata.mp4";
|
||||||
static IMAGE_AVIF: &str = "av1-avif/testFiles/Microsoft/Monochrome.avif";
|
static IMAGE_AVIF: &str = "av1-avif/testFiles/Microsoft/Monochrome.avif";
|
||||||
static IMAGE_AVIF_EXTENTS: &str = "tests/kodim-extents.avif";
|
static IMAGE_AVIF_EXTENTS: &str = "tests/kodim-extents.avif";
|
||||||
static IMAGE_AVIF_ALPHA: &str = "tests/bug-1661347.avif";
|
static IMAGE_AVIF_ALPHA: &str = "tests/bug-1661347.avif";
|
||||||
|
@ -41,7 +44,6 @@ static AVIF_CORRUPT_IMAGES: &str = "tests/corrupt";
|
||||||
|
|
||||||
// Adapted from https://github.com/GuillaumeGomez/audio-video-metadata/blob/9dff40f565af71d5502e03a2e78ae63df95cfd40/src/metadata.rs#L53
|
// Adapted from https://github.com/GuillaumeGomez/audio-video-metadata/blob/9dff40f565af71d5502e03a2e78ae63df95cfd40/src/metadata.rs#L53
|
||||||
#[test]
|
#[test]
|
||||||
#[allow(clippy::cognitive_complexity)] // TODO: Consider simplifying this
|
|
||||||
fn public_api() {
|
fn public_api() {
|
||||||
let mut fd = File::open(MINI_MP4).expect("Unknown file");
|
let mut fd = File::open(MINI_MP4).expect("Unknown file");
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
|
@ -161,7 +163,6 @@ fn public_api() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[allow(clippy::cognitive_complexity)] // TODO: Consider simplifying this
|
|
||||||
fn public_metadata() {
|
fn public_metadata() {
|
||||||
let mut fd = File::open(MINI_MP4_WITH_METADATA).expect("Unknown file");
|
let mut fd = File::open(MINI_MP4_WITH_METADATA).expect("Unknown file");
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
|
@ -228,7 +229,6 @@ fn public_metadata() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[allow(clippy::cognitive_complexity)] // TODO: Consider simplifying this
|
|
||||||
fn public_metadata_gnre() {
|
fn public_metadata_gnre() {
|
||||||
let mut fd = File::open(MINI_MP4_WITH_METADATA_STD_GENRE).expect("Unknown file");
|
let mut fd = File::open(MINI_MP4_WITH_METADATA_STD_GENRE).expect("Unknown file");
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
|
@ -291,6 +291,46 @@ fn public_metadata_gnre() {
|
||||||
assert_eq!(u32::from_le_bytes(bytes), 0x00ff_d8ff);
|
assert_eq!(u32::from_le_bytes(bytes), 0x00ff_d8ff);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn public_invalid_metadata() {
|
||||||
|
// Test that reading userdata containing invalid metadata is not fatal to parsing and that
|
||||||
|
// expected values are still found.
|
||||||
|
let mut fd = File::open(VIDEO_INVALID_USERDATA).expect("Unknown file");
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
fd.read_to_end(&mut buf).expect("File error");
|
||||||
|
|
||||||
|
let mut c = Cursor::new(&buf);
|
||||||
|
let context = mp4::read_mp4(&mut c).expect("read_mp4 failed");
|
||||||
|
// Should have userdata.
|
||||||
|
assert!(context.userdata.is_some());
|
||||||
|
// But it should contain an error.
|
||||||
|
assert!(context.userdata.unwrap().is_err());
|
||||||
|
// Smoke test that other data has been parsed. Don't check everything, just make sure some
|
||||||
|
// values are as expected.
|
||||||
|
assert_eq!(context.tracks.len(), 2);
|
||||||
|
for track in context.tracks {
|
||||||
|
match track.track_type {
|
||||||
|
mp4::TrackType::Video => {
|
||||||
|
// Check some of the values in the video tkhd.
|
||||||
|
let tkhd = track.tkhd.unwrap();
|
||||||
|
assert_eq!(tkhd.disabled, false);
|
||||||
|
assert_eq!(tkhd.duration, 231232);
|
||||||
|
assert_eq!(tkhd.width, 83_886_080);
|
||||||
|
assert_eq!(tkhd.height, 47_185_920);
|
||||||
|
}
|
||||||
|
mp4::TrackType::Audio => {
|
||||||
|
// Check some of the values in the audio tkhd.
|
||||||
|
let tkhd = track.tkhd.unwrap();
|
||||||
|
assert_eq!(tkhd.disabled, false);
|
||||||
|
assert_eq!(tkhd.duration, 231338);
|
||||||
|
assert_eq!(tkhd.width, 0);
|
||||||
|
assert_eq!(tkhd.height, 0);
|
||||||
|
}
|
||||||
|
_ => panic!("File should not contain other tracks."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn public_audio_tenc() {
|
fn public_audio_tenc() {
|
||||||
let kid = vec![
|
let kid = vec![
|
||||||
|
@ -486,7 +526,6 @@ fn public_audio_cbcs() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[allow(clippy::cognitive_complexity)] // TODO: Consider simplifying this
|
|
||||||
fn public_video_cbcs() {
|
fn public_video_cbcs() {
|
||||||
let system_id = vec![
|
let system_id = vec![
|
||||||
0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb,
|
0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb,
|
||||||
|
@ -567,7 +606,6 @@ fn public_video_cbcs() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[allow(clippy::cognitive_complexity)] // TODO: Consider simplifying this
|
|
||||||
fn public_video_av1() {
|
fn public_video_av1() {
|
||||||
let mut fd = File::open(VIDEO_AV1_MP4).expect("Unknown file");
|
let mut fd = File::open(VIDEO_AV1_MP4).expect("Unknown file");
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
{"files":{"Cargo.toml":"ba144343a4d38fe6918dd61bb6723e46ac297f506a27370c1d10b49bd8123572","cbindgen.toml":"5c9429f271d6e914d81b63e6509c04ffe84cab11ed3a53a2ed4715e5d5ace80e","examples/dump.rs":"83462422315c22e496960bae922edb23105c0aa272d2b106edd7574ff068513a","src/lib.rs":"d25c2e010186ada3524801e8f1ecdbe42cc4f961880ccca11ba9bdddd42363f7","tests/test_chunk_out_of_range.rs":"b5da583218d98027ed973a29c67434a91a1306f2d2fb39ec4d640d4824c308ce","tests/test_encryption.rs":"ca98516ff423c03b5fcc17b05f993f13b32485e4cf3ba86faf1bea72681d75ce","tests/test_fragment.rs":"e90eb5a4418d30002655466c0c4b3125c7fd70a74b6871471eaa172f1def9db8","tests/test_rotation.rs":"fb43c2f2dfa496d151c33bdd46c0fd3252387c23cc71e2cac9ed0234de715a81","tests/test_sample_table.rs":"185755909b2f4e0ea99604bb423a07623d614a180accdaebd1c98aef2c2e3ae6","tests/test_workaround_stsc.rs":"7dd419f3d55b9a3a039cac57e58a9240a9c8166bcd4356c24f69f731c3ced83b"},"package":null}
|
{"files":{"Cargo.toml":"9f188cfbdda3daf4dc36638f136f3640c21cad5eef8df60f66826f06cf2cc0e0","cbindgen.toml":"5c9429f271d6e914d81b63e6509c04ffe84cab11ed3a53a2ed4715e5d5ace80e","examples/dump.rs":"83462422315c22e496960bae922edb23105c0aa272d2b106edd7574ff068513a","src/lib.rs":"d25c2e010186ada3524801e8f1ecdbe42cc4f961880ccca11ba9bdddd42363f7","tests/test_chunk_out_of_range.rs":"b5da583218d98027ed973a29c67434a91a1306f2d2fb39ec4d640d4824c308ce","tests/test_encryption.rs":"f1b4dc5a726f067f6a6049ec43bc93733131e7e25b0d7344251e1fdd02dcbbcd","tests/test_fragment.rs":"e90eb5a4418d30002655466c0c4b3125c7fd70a74b6871471eaa172f1def9db8","tests/test_rotation.rs":"fb43c2f2dfa496d151c33bdd46c0fd3252387c23cc71e2cac9ed0234de715a81","tests/test_sample_table.rs":"185755909b2f4e0ea99604bb423a07623d614a180accdaebd1c98aef2c2e3ae6","tests/test_workaround_stsc.rs":"7dd419f3d55b9a3a039cac57e58a9240a9c8166bcd4356c24f69f731c3ced83b"},"package":null}
|
|
@ -1,11 +1,12 @@
|
||||||
[package]
|
[package]
|
||||||
name = "mp4parse_capi"
|
name = "mp4parse_capi"
|
||||||
version = "0.11.4"
|
version = "0.11.5"
|
||||||
authors = [
|
authors = [
|
||||||
"Ralph Giles <giles@mozilla.com>",
|
"Ralph Giles <giles@mozilla.com>",
|
||||||
"Matthew Gregan <kinetik@flim.org>",
|
"Matthew Gregan <kinetik@flim.org>",
|
||||||
"Alfredo Yang <ayang@mozilla.com>",
|
"Alfredo Yang <ayang@mozilla.com>",
|
||||||
"Jon Bauman <jbauman@mozilla.com>",
|
"Jon Bauman <jbauman@mozilla.com>",
|
||||||
|
"Bryce Seager van Dyk <bvandyk@mozilla.com>",
|
||||||
]
|
]
|
||||||
|
|
||||||
description = "Parser for ISO base media file format (mp4)"
|
description = "Parser for ISO base media file format (mp4)"
|
||||||
|
@ -26,8 +27,8 @@ travis-ci = { repository = "https://github.com/mozilla/mp4parse-rust" }
|
||||||
byteorder = "1.2.1"
|
byteorder = "1.2.1"
|
||||||
fallible_collections = { version = "0.3", features = ["std_io"] }
|
fallible_collections = { version = "0.3", features = ["std_io"] }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
mp4parse = {version = "0.11.2", path = "../mp4parse"}
|
mp4parse = {version = "0.11.5", path = "../mp4parse"}
|
||||||
num-traits = "=0.2.10"
|
num-traits = "0.2.14"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.7.1"
|
env_logger = "0.8"
|
||||||
|
|
|
@ -12,7 +12,6 @@ extern "C" fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[allow(clippy::cognitive_complexity)] // TODO: Consider simplifying this
|
|
||||||
fn parse_cenc() {
|
fn parse_cenc() {
|
||||||
let mut file = std::fs::File::open("tests/short-cenc.mp4").expect("Unknown file");
|
let mut file = std::fs::File::open("tests/short-cenc.mp4").expect("Unknown file");
|
||||||
let io = Mp4parseIo {
|
let io = Mp4parseIo {
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
{"files":{"Cargo.toml":"35c48122af67e382695600a6af69d73c0dd907afb31914aee049eebc88c871a5","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb","README.md":"b442afb73953744a9d2134442845d3e960984ac5be2a058e5064ae851624d67f","RELEASES.md":"683c12fd04a3185fcbfa1915d1fbce8051077c5bdf07c3bfade4606bdd92d0e4","build.rs":"b4b2d0df90ca7570a339ca4d84a72e4ef00d9dced8927350424e666790c752d7","src/bounds.rs":"4fbc6bf3550e4da7ef55c9eb96466c6e0aee7f7542d7c85cfd742f16e3b4729f","src/cast.rs":"2eae0873cbe11fa0d1a012e304f48dea6a0802c0ee738dc25683779cf15b18fb","src/float.rs":"5c1a11e4218d376cd531aff0d6546c2b590d687bda5825f8c27dc6d6f974fb9b","src/identities.rs":"5b6238ebd52e1fadd5b405bc40ad81e45346bcb1c4b46cf1f0496a30be7c9bc4","src/int.rs":"bbc014a02998848170430abf60ab80fa46d7835be3a5e8bf7d28e1c5318068ae","src/lib.rs":"beb207aa3f791811b043d7aa0daa34a2f8f3a52e0555bda8eb32e3359203a129","src/macros.rs":"b589a98c2468ca98131c641a058601964c4e82d75129b1c16fdc17aca8779eca","src/ops/checked.rs":"b6dfae21fe1a5ce41e92074c57d204284975ec56d45e63cac5f0136b8c3643b4","src/ops/inv.rs":"dd80b9bd48d815f17855a25842287942317fa49d1fdcdd655b61bd20ef927cda","src/ops/mod.rs":"036b2a1900dc8e7295a91060e660184b2bd98f33b5db81a62b08cf8d3df726cf","src/ops/mul_add.rs":"368bdebb40b16f3b4b85cf50235954268ff601ec7a458a3a83fe433f47f86f6d","src/ops/saturating.rs":"81c34038ec88fe49f4286e6e1213a1da831c5e66caac651f0a61506a4534c9ce","src/ops/wrapping.rs":"8d334dd2b8128e640bff6c3e93ea301182241987b3ed1fc158268292827bbd0c","src/pow.rs":"9f78cb9c4d5987b59e16f4141723a33ff689781cc352f357b0cc0111d22cde3a","src/real.rs":"b5115bb2cfb752a59426bb3fcbabf9cff15521a00a3f8b5ef4dfc0b0b31bb1f4","src/sign.rs":"81b1116300b5787546852d9d04f0375fa24342f85c34f5bc1ce5360b53fa411a","tests/cast.rs":"2c4b4f2185ec0d687e1bde292731dbc5efec527ab393478b5adf26f6e1352231"},"package":"d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4"}
|
{"files":{"Cargo.toml":"25ae881cd00d94ea9f9cb7844f32e3e58871fc9e45769ead9820d8d40c342ddb","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb","README.md":"dd03422a366071c147d9dec73f01ba8299ccf963a0b038eb85520908d9cd028c","RELEASES.md":"8be8fef421245ea7d9e8a4ab89056307095bfbc9730ca175a75968e6d01b68e4","build.rs":"14cb861026b2493f7b39e0f72ced0ccb95c9a5fd0bdce3158f23a27dac7577e8","src/bounds.rs":"4fbc6bf3550e4da7ef55c9eb96466c6e0aee7f7542d7c85cfd742f16e3b4729f","src/cast.rs":"8c8384dcdfaf926b9331399d528278fc73851f8b5e1105dee343f4f19557e932","src/float.rs":"73cc27228e7578d537c00b9b46e124e365c569a53a7e160fe0d1ad1dacf54fc8","src/identities.rs":"5b6238ebd52e1fadd5b405bc40ad81e45346bcb1c4b46cf1f0496a30be7c9bc4","src/int.rs":"c6e042ac4614529f07b0009271a6b97378451d6d3998e1dc407e76f9a050aa2d","src/lib.rs":"5ff4c0694c7db0538fdca2eb8290ecca4a05e0357607dd4bd078b2b762241094","src/macros.rs":"b589a98c2468ca98131c641a058601964c4e82d75129b1c16fdc17aca8779eca","src/ops/checked.rs":"b6dfae21fe1a5ce41e92074c57d204284975ec56d45e63cac5f0136b8c3643b4","src/ops/inv.rs":"dd80b9bd48d815f17855a25842287942317fa49d1fdcdd655b61bd20ef927cda","src/ops/mod.rs":"fd33030d6e5bd50b9797112b63d14e49ce53a61dd9cc88205c942fa79cb18cf5","src/ops/mul_add.rs":"368bdebb40b16f3b4b85cf50235954268ff601ec7a458a3a83fe433f47f86f6d","src/ops/overflowing.rs":"1b92882a5a738ea4cafdb0971101dc438be9043f540e1246f58f7c9ecf6348dc","src/ops/saturating.rs":"6fb4b2a2c78d9202152a84586d7b068444b78d3caed4b293980832672a234d4b","src/ops/wrapping.rs":"0acf88c0e5fc81a3c6790b31986ab9be5b16c720c9e27461fe5d69b710ffcaba","src/pow.rs":"9f78cb9c4d5987b59e16f4141723a33ff689781cc352f357b0cc0111d22cde3a","src/real.rs":"b5115bb2cfb752a59426bb3fcbabf9cff15521a00a3f8b5ef4dfc0b0b31bb1f4","src/sign.rs":"83562caa3501c6873558eb64c9e3bfe25b4b20d38562a7aa7cc9adafcb3ff037","tests/cast.rs":"0a41785611b3909ecb4a88d6d5264a85564f6de8fbfc761436d3c8baafc8e3d0"},"package":"9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"}
|
|
@ -12,17 +12,17 @@
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.10"
|
version = "0.2.14"
|
||||||
authors = ["The Rust Project Developers"]
|
authors = ["The Rust Project Developers"]
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
exclude = ["/ci/*", "/.travis.yml", "/bors.toml"]
|
exclude = ["/bors.toml", "/ci/*", "/.github/*"]
|
||||||
description = "Numeric traits for generic mathematics"
|
description = "Numeric traits for generic mathematics"
|
||||||
homepage = "https://github.com/rust-num/num-traits"
|
homepage = "https://github.com/rust-num/num-traits"
|
||||||
documentation = "https://docs.rs/num-traits"
|
documentation = "https://docs.rs/num-traits"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
keywords = ["mathematics", "numerics"]
|
keywords = ["mathematics", "numerics"]
|
||||||
categories = ["algorithms", "science", "no-std"]
|
categories = ["algorithms", "science", "no-std"]
|
||||||
license = "MIT/Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
repository = "https://github.com/rust-num/num-traits"
|
repository = "https://github.com/rust-num/num-traits"
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
features = ["std"]
|
features = ["std"]
|
||||||
|
@ -30,7 +30,7 @@ features = ["std"]
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
optional = true
|
optional = true
|
||||||
[build-dependencies.autocfg]
|
[build-dependencies.autocfg]
|
||||||
version = "0.1.3"
|
version = "1"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["std"]
|
default = ["std"]
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
[![crate](https://img.shields.io/crates/v/num-traits.svg)](https://crates.io/crates/num-traits)
|
[![crate](https://img.shields.io/crates/v/num-traits.svg)](https://crates.io/crates/num-traits)
|
||||||
[![documentation](https://docs.rs/num-traits/badge.svg)](https://docs.rs/num-traits)
|
[![documentation](https://docs.rs/num-traits/badge.svg)](https://docs.rs/num-traits)
|
||||||
![minimum rustc 1.8](https://img.shields.io/badge/rustc-1.8+-red.svg)
|
[![minimum rustc 1.8](https://img.shields.io/badge/rustc-1.8+-red.svg)](https://rust-lang.github.io/rfcs/2495-min-rust-version.html)
|
||||||
[![Travis status](https://travis-ci.org/rust-num/num-traits.svg?branch=master)](https://travis-ci.org/rust-num/num-traits)
|
[![build status](https://github.com/rust-num/num-traits/workflows/master/badge.svg)](https://github.com/rust-num/num-traits/actions)
|
||||||
|
|
||||||
Numeric traits for generic mathematics in Rust.
|
Numeric traits for generic mathematics in Rust.
|
||||||
|
|
||||||
|
@ -52,3 +52,18 @@ Release notes are available in [RELEASES.md](RELEASES.md).
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
|
||||||
The `num-traits` crate is tested for rustc 1.8 and greater.
|
The `num-traits` crate is tested for rustc 1.8 and greater.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Licensed under either of
|
||||||
|
|
||||||
|
* [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
* [MIT license](http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
at your option.
|
||||||
|
|
||||||
|
### Contribution
|
||||||
|
|
||||||
|
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||||
|
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
|
||||||
|
dual licensed as above, without any additional terms or conditions.
|
||||||
|
|
|
@ -1,3 +1,56 @@
|
||||||
|
# Release 0.2.14 (2020-10-29)
|
||||||
|
|
||||||
|
- Clarify the license specification as "MIT OR Apache-2.0".
|
||||||
|
|
||||||
|
**Contributors**: @cuviper
|
||||||
|
|
||||||
|
# Release 0.2.13 (2020-10-29)
|
||||||
|
|
||||||
|
- [The new `OverflowingAdd`, `OverflowingSub`, and `OverflowingMul` traits][180]
|
||||||
|
return a tuple with the operation result and a `bool` indicating overflow.
|
||||||
|
- [The "i128" feature now overrides compiler probes for that support][185].
|
||||||
|
This may fix scenarios where `autocfg` probing doesn't work properly.
|
||||||
|
- [Casts from large `f64` values to `f32` now saturate to infinity][186]. They
|
||||||
|
previously returned `None` because that was once thought to be undefined
|
||||||
|
behavior, but [rust#15536] resolved that such casts are fine.
|
||||||
|
- [`Num::from_str_radix` documents requirements for radix support][192], which
|
||||||
|
are now more relaxed than previously implied. It is suggested to accept at
|
||||||
|
least `2..=36` without panicking, but `Err` may be returned otherwise.
|
||||||
|
|
||||||
|
**Contributors**: @cuviper, @Enet4, @KaczuH, @martin-t, @newpavlov
|
||||||
|
|
||||||
|
[180]: https://github.com/rust-num/num-traits/pull/180
|
||||||
|
[185]: https://github.com/rust-num/num-traits/pull/185
|
||||||
|
[186]: https://github.com/rust-num/num-traits/pull/186
|
||||||
|
[192]: https://github.com/rust-num/num-traits/issues/192
|
||||||
|
[rust#15536]: https://github.com/rust-lang/rust/issues/15536
|
||||||
|
|
||||||
|
# Release 0.2.12 (2020-06-11)
|
||||||
|
|
||||||
|
- [The new `WrappingNeg` trait][153] will wrap the result if it exceeds the
|
||||||
|
boundary of the type, e.g. `i32::MIN.wrapping_neg() == i32::MIN`.
|
||||||
|
- [The new `SaturatingAdd`, `SaturatingSub`, and `SaturatingMul` traits][165]
|
||||||
|
will saturate at the numeric bounds if the operation would overflow. These
|
||||||
|
soft-deprecate the existing `Saturating` trait that only has addition and
|
||||||
|
subtraction methods.
|
||||||
|
- [Added new constants for logarithms, `FloatConst::{LOG10_2, LOG2_10}`][171].
|
||||||
|
|
||||||
|
**Contributors**: @cuviper, @ocstl, @trepetti, @vallentin
|
||||||
|
|
||||||
|
[153]: https://github.com/rust-num/num-traits/pull/153
|
||||||
|
[165]: https://github.com/rust-num/num-traits/pull/165
|
||||||
|
[171]: https://github.com/rust-num/num-traits/pull/171
|
||||||
|
|
||||||
|
# Release 0.2.11 (2020-01-09)
|
||||||
|
|
||||||
|
- [Added the full circle constant τ as `FloatConst::TAU`][145].
|
||||||
|
- [Updated the `autocfg` build dependency to 1.0][148].
|
||||||
|
|
||||||
|
**Contributors**: @cuviper, @m-ou-se
|
||||||
|
|
||||||
|
[145]: https://github.com/rust-num/num-traits/pull/145
|
||||||
|
[148]: https://github.com/rust-num/num-traits/pull/148
|
||||||
|
|
||||||
# Release 0.2.10 (2019-11-22)
|
# Release 0.2.10 (2019-11-22)
|
||||||
|
|
||||||
- [Updated the `libm` dependency to 0.2][144].
|
- [Updated the `libm` dependency to 0.2][144].
|
||||||
|
|
|
@ -4,11 +4,17 @@ use std::env;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let ac = autocfg::new();
|
let ac = autocfg::new();
|
||||||
if ac.probe_type("i128") {
|
|
||||||
println!("cargo:rustc-cfg=has_i128");
|
// If the "i128" feature is explicity requested, don't bother probing for it.
|
||||||
} else if env::var_os("CARGO_FEATURE_I128").is_some() {
|
// It will still cause a build error if that was set improperly.
|
||||||
panic!("i128 support was not detected!");
|
if env::var_os("CARGO_FEATURE_I128").is_some() || ac.probe_type("i128") {
|
||||||
|
autocfg::emit("has_i128");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ac.emit_expression_cfg(
|
||||||
|
"unsafe { 1f64.to_int_unchecked::<i32>() }",
|
||||||
|
"has_to_int_unchecked",
|
||||||
|
);
|
||||||
|
|
||||||
autocfg::rerun_path("build.rs");
|
autocfg::rerun_path("build.rs");
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,16 @@ use core::{i128, u128};
|
||||||
use core::{i16, i32, i64, i8, isize};
|
use core::{i16, i32, i64, i8, isize};
|
||||||
use core::{u16, u32, u64, u8, usize};
|
use core::{u16, u32, u64, u8, usize};
|
||||||
|
|
||||||
use float::FloatCore;
|
|
||||||
|
|
||||||
/// A generic trait for converting a value to a number.
|
/// A generic trait for converting a value to a number.
|
||||||
|
///
|
||||||
|
/// A value can be represented by the target type when it lies within
|
||||||
|
/// the range of scalars supported by the target type.
|
||||||
|
/// For example, a negative integer cannot be represented by an unsigned
|
||||||
|
/// integer type, and an `i64` with a very high magnitude might not be
|
||||||
|
/// convertible to an `i32`.
|
||||||
|
/// On the other hand, conversions with possible precision loss or truncation
|
||||||
|
/// are admitted, like an `f32` with a decimal part to an integer type, or
|
||||||
|
/// even a large `f64` saturating to `f32` infinity.
|
||||||
pub trait ToPrimitive {
|
pub trait ToPrimitive {
|
||||||
/// Converts the value of `self` to an `isize`. If the value cannot be
|
/// Converts the value of `self` to an `isize`. If the value cannot be
|
||||||
/// represented by an `isize`, then `None` is returned.
|
/// represented by an `isize`, then `None` is returned.
|
||||||
|
@ -94,7 +101,7 @@ pub trait ToPrimitive {
|
||||||
///
|
///
|
||||||
/// This method is only available with feature `i128` enabled on Rust >= 1.26.
|
/// This method is only available with feature `i128` enabled on Rust >= 1.26.
|
||||||
///
|
///
|
||||||
/// The default implementation converts through `to_u64()`. Types implementing
|
/// The default implementation converts through `to_u64()`. Types implementing
|
||||||
/// this trait should override this method if they can represent a greater range.
|
/// this trait should override this method if they can represent a greater range.
|
||||||
#[inline]
|
#[inline]
|
||||||
#[cfg(has_i128)]
|
#[cfg(has_i128)]
|
||||||
|
@ -102,15 +109,21 @@ pub trait ToPrimitive {
|
||||||
self.to_u64().map(From::from)
|
self.to_u64().map(From::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts the value of `self` to an `f32`. If the value cannot be
|
/// Converts the value of `self` to an `f32`. Overflows may map to positive
|
||||||
/// represented by an `f32`, then `None` is returned.
|
/// or negative inifinity, otherwise `None` is returned if the value cannot
|
||||||
|
/// be represented by an `f32`.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn to_f32(&self) -> Option<f32> {
|
fn to_f32(&self) -> Option<f32> {
|
||||||
self.to_f64().as_ref().and_then(ToPrimitive::to_f32)
|
self.to_f64().as_ref().and_then(ToPrimitive::to_f32)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts the value of `self` to an `f64`. If the value cannot be
|
/// Converts the value of `self` to an `f64`. Overflows may map to positive
|
||||||
/// represented by an `f64`, then `None` is returned.
|
/// or negative inifinity, otherwise `None` is returned if the value cannot
|
||||||
|
/// be represented by an `f64`.
|
||||||
|
///
|
||||||
|
/// The default implementation tries to convert through `to_i64()`, and
|
||||||
|
/// failing that through `to_u64()`. Types implementing this trait should
|
||||||
|
/// override this method if they can represent a greater range.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn to_f64(&self) -> Option<f64> {
|
fn to_f64(&self) -> Option<f64> {
|
||||||
match self.to_i64() {
|
match self.to_i64() {
|
||||||
|
@ -271,19 +284,29 @@ macro_rules! impl_to_primitive_float_to_float {
|
||||||
($SrcT:ident : $( fn $method:ident -> $DstT:ident ; )*) => {$(
|
($SrcT:ident : $( fn $method:ident -> $DstT:ident ; )*) => {$(
|
||||||
#[inline]
|
#[inline]
|
||||||
fn $method(&self) -> Option<$DstT> {
|
fn $method(&self) -> Option<$DstT> {
|
||||||
// Only finite values that are reducing size need to worry about overflow.
|
// We can safely cast all values, whether NaN, +-inf, or finite.
|
||||||
if size_of::<$SrcT>() > size_of::<$DstT>() && FloatCore::is_finite(*self) {
|
// Finite values that are reducing size may saturate to +-inf.
|
||||||
let n = *self as f64;
|
|
||||||
if n < $DstT::MIN as f64 || n > $DstT::MAX as f64 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// We can safely cast NaN, +-inf, and finite values in range.
|
|
||||||
Some(*self as $DstT)
|
Some(*self as $DstT)
|
||||||
}
|
}
|
||||||
)*}
|
)*}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(has_to_int_unchecked)]
|
||||||
|
macro_rules! float_to_int_unchecked {
|
||||||
|
// SAFETY: Must not be NaN or infinite; must be representable as the integer after truncating.
|
||||||
|
// We already checked that the float is in the exclusive range `(MIN-1, MAX+1)`.
|
||||||
|
($float:expr => $int:ty) => {
|
||||||
|
unsafe { $float.to_int_unchecked::<$int>() }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(has_to_int_unchecked))]
|
||||||
|
macro_rules! float_to_int_unchecked {
|
||||||
|
($float:expr => $int:ty) => {
|
||||||
|
$float as $int
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! impl_to_primitive_float_to_signed_int {
|
macro_rules! impl_to_primitive_float_to_signed_int {
|
||||||
($f:ident : $( $(#[$cfg:meta])* fn $method:ident -> $i:ident ; )*) => {$(
|
($f:ident : $( $(#[$cfg:meta])* fn $method:ident -> $i:ident ; )*) => {$(
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -296,7 +319,7 @@ macro_rules! impl_to_primitive_float_to_signed_int {
|
||||||
const MIN_M1: $f = $i::MIN as $f - 1.0;
|
const MIN_M1: $f = $i::MIN as $f - 1.0;
|
||||||
const MAX_P1: $f = $i::MAX as $f + 1.0;
|
const MAX_P1: $f = $i::MAX as $f + 1.0;
|
||||||
if *self > MIN_M1 && *self < MAX_P1 {
|
if *self > MIN_M1 && *self < MAX_P1 {
|
||||||
return Some(*self as $i);
|
return Some(float_to_int_unchecked!(*self => $i));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// We can't represent `MIN-1` exactly, but there's no fractional part
|
// We can't represent `MIN-1` exactly, but there's no fractional part
|
||||||
|
@ -306,7 +329,7 @@ macro_rules! impl_to_primitive_float_to_signed_int {
|
||||||
// `MAX+1` (a power of two) when we cast it.
|
// `MAX+1` (a power of two) when we cast it.
|
||||||
const MAX_P1: $f = $i::MAX as $f;
|
const MAX_P1: $f = $i::MAX as $f;
|
||||||
if *self >= MIN && *self < MAX_P1 {
|
if *self >= MIN && *self < MAX_P1 {
|
||||||
return Some(*self as $i);
|
return Some(float_to_int_unchecked!(*self => $i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
|
@ -325,7 +348,7 @@ macro_rules! impl_to_primitive_float_to_unsigned_int {
|
||||||
// With a larger size, we can represent the range exactly.
|
// With a larger size, we can represent the range exactly.
|
||||||
const MAX_P1: $f = $u::MAX as $f + 1.0;
|
const MAX_P1: $f = $u::MAX as $f + 1.0;
|
||||||
if *self > -1.0 && *self < MAX_P1 {
|
if *self > -1.0 && *self < MAX_P1 {
|
||||||
return Some(*self as $u);
|
return Some(float_to_int_unchecked!(*self => $u));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// We can't represent `MAX` exactly, but it will round up to exactly
|
// We can't represent `MAX` exactly, but it will round up to exactly
|
||||||
|
@ -333,7 +356,7 @@ macro_rules! impl_to_primitive_float_to_unsigned_int {
|
||||||
// (`u128::MAX as f32` is infinity, but this is still ok.)
|
// (`u128::MAX as f32` is infinity, but this is still ok.)
|
||||||
const MAX_P1: $f = $u::MAX as $f;
|
const MAX_P1: $f = $u::MAX as $f;
|
||||||
if *self > -1.0 && *self < MAX_P1 {
|
if *self > -1.0 && *self < MAX_P1 {
|
||||||
return Some(*self as $u);
|
return Some(float_to_int_unchecked!(*self => $u));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
|
@ -376,6 +399,15 @@ impl_to_primitive_float!(f32);
|
||||||
impl_to_primitive_float!(f64);
|
impl_to_primitive_float!(f64);
|
||||||
|
|
||||||
/// A generic trait for converting a number to a value.
|
/// A generic trait for converting a number to a value.
|
||||||
|
///
|
||||||
|
/// A value can be represented by the target type when it lies within
|
||||||
|
/// the range of scalars supported by the target type.
|
||||||
|
/// For example, a negative integer cannot be represented by an unsigned
|
||||||
|
/// integer type, and an `i64` with a very high magnitude might not be
|
||||||
|
/// convertible to an `i32`.
|
||||||
|
/// On the other hand, conversions with possible precision loss or truncation
|
||||||
|
/// are admitted, like an `f32` with a decimal part to an integer type, or
|
||||||
|
/// even a large `f64` saturating to `f32` infinity.
|
||||||
pub trait FromPrimitive: Sized {
|
pub trait FromPrimitive: Sized {
|
||||||
/// Converts an `isize` to return an optional value of this type. If the
|
/// Converts an `isize` to return an optional value of this type. If the
|
||||||
/// value cannot be represented by this type, then `None` is returned.
|
/// value cannot be represented by this type, then `None` is returned.
|
||||||
|
@ -476,6 +508,10 @@ pub trait FromPrimitive: Sized {
|
||||||
|
|
||||||
/// Converts a `f64` to return an optional value of this type. If the
|
/// Converts a `f64` to return an optional value of this type. If the
|
||||||
/// value cannot be represented by this type, then `None` is returned.
|
/// value cannot be represented by this type, then `None` is returned.
|
||||||
|
///
|
||||||
|
/// The default implementation tries to convert through `from_i64()`, and
|
||||||
|
/// failing that through `from_u64()`. Types implementing this trait should
|
||||||
|
/// override this method if they can represent a greater range.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from_f64(n: f64) -> Option<Self> {
|
fn from_f64(n: f64) -> Option<Self> {
|
||||||
match n.to_i64() {
|
match n.to_i64() {
|
||||||
|
@ -656,6 +692,15 @@ pub trait NumCast: Sized + ToPrimitive {
|
||||||
/// Creates a number from another value that can be converted into
|
/// Creates a number from another value that can be converted into
|
||||||
/// a primitive via the `ToPrimitive` trait. If the source value cannot be
|
/// a primitive via the `ToPrimitive` trait. If the source value cannot be
|
||||||
/// represented by the target type, then `None` is returned.
|
/// represented by the target type, then `None` is returned.
|
||||||
|
///
|
||||||
|
/// A value can be represented by the target type when it lies within
|
||||||
|
/// the range of scalars supported by the target type.
|
||||||
|
/// For example, a negative integer cannot be represented by an unsigned
|
||||||
|
/// integer type, and an `i64` with a very high magnitude might not be
|
||||||
|
/// convertible to an `i32`.
|
||||||
|
/// On the other hand, conversions with possible precision loss or truncation
|
||||||
|
/// are admitted, like an `f32` with a decimal part to an integer type, or
|
||||||
|
/// even a large `f64` saturating to `f32` infinity.
|
||||||
fn from<T: ToPrimitive>(n: T) -> Option<Self>;
|
fn from<T: ToPrimitive>(n: T) -> Option<Self>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -712,25 +757,16 @@ impl<T: NumCast> NumCast for Wrapping<T> {
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
///
|
///
|
||||||
/// Currently, some uses of the `as` operator are not entirely safe.
|
/// **In Rust versions before 1.45.0**, some uses of the `as` operator were not entirely safe.
|
||||||
/// In particular, it is undefined behavior if:
|
/// In particular, it was undefined behavior if
|
||||||
///
|
/// a truncated floating point value could not fit in the target integer
|
||||||
/// - A truncated floating point value cannot fit in the target integer
|
/// type ([#10184](https://github.com/rust-lang/rust/issues/10184)).
|
||||||
/// type ([#10184](https://github.com/rust-lang/rust/issues/10184));
|
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// # use num_traits::AsPrimitive;
|
/// # use num_traits::AsPrimitive;
|
||||||
/// let x: u8 = (1.04E+17).as_(); // UB
|
/// let x: u8 = (1.04E+17).as_(); // UB
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// - Or a floating point value does not fit in another floating
|
|
||||||
/// point type ([#15536](https://github.com/rust-lang/rust/issues/15536)).
|
|
||||||
///
|
|
||||||
/// ```ignore
|
|
||||||
/// # use num_traits::AsPrimitive;
|
|
||||||
/// let x: f32 = (1e300f64).as_(); // UB
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
pub trait AsPrimitive<T>: 'static + Copy
|
pub trait AsPrimitive<T>: 'static + Copy
|
||||||
where
|
where
|
||||||
T: 'static + Copy,
|
T: 'static + Copy,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use core::mem;
|
use core::mem;
|
||||||
use core::num::FpCategory;
|
use core::num::FpCategory;
|
||||||
use core::ops::Neg;
|
use core::ops::{Add, Div, Neg};
|
||||||
|
|
||||||
use core::f32;
|
use core::f32;
|
||||||
use core::f64;
|
use core::f64;
|
||||||
|
@ -606,8 +606,8 @@ pub trait FloatCore: Num + NumCast + Neg<Output = Self> + PartialOrd + Copy {
|
||||||
/// use num_traits::float::FloatCore;
|
/// use num_traits::float::FloatCore;
|
||||||
/// use std::{f32, f64};
|
/// use std::{f32, f64};
|
||||||
///
|
///
|
||||||
/// fn check<T: FloatCore>(x: T, y: T, min: T) {
|
/// fn check<T: FloatCore>(x: T, y: T, max: T) {
|
||||||
/// assert!(x.max(y) == min);
|
/// assert!(x.max(y) == max);
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// check(1.0f32, 2.0, 2.0);
|
/// check(1.0f32, 2.0, 2.0);
|
||||||
|
@ -2247,6 +2247,21 @@ macro_rules! float_const_impl {
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub trait FloatConst {
|
pub trait FloatConst {
|
||||||
$(#[$doc] fn $constant() -> Self;)+
|
$(#[$doc] fn $constant() -> Self;)+
|
||||||
|
#[doc = "Return the full circle constant `τ`."]
|
||||||
|
#[inline]
|
||||||
|
fn TAU() -> Self where Self: Sized + Add<Self, Output = Self> {
|
||||||
|
Self::PI() + Self::PI()
|
||||||
|
}
|
||||||
|
#[doc = "Return `log10(2.0)`."]
|
||||||
|
#[inline]
|
||||||
|
fn LOG10_2() -> Self where Self: Sized + Div<Self, Output = Self> {
|
||||||
|
Self::LN_2() / Self::LN_10()
|
||||||
|
}
|
||||||
|
#[doc = "Return `log2(10.0)`."]
|
||||||
|
#[inline]
|
||||||
|
fn LOG2_10() -> Self where Self: Sized + Div<Self, Output = Self> {
|
||||||
|
Self::LN_10() / Self::LN_2()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
float_const_impl! { @float f32, $($constant,)+ }
|
float_const_impl! { @float f32, $($constant,)+ }
|
||||||
float_const_impl! { @float f64, $($constant,)+ }
|
float_const_impl! { @float f64, $($constant,)+ }
|
||||||
|
@ -2255,6 +2270,9 @@ macro_rules! float_const_impl {
|
||||||
impl FloatConst for $T {
|
impl FloatConst for $T {
|
||||||
constant! {
|
constant! {
|
||||||
$( $constant() -> $T::consts::$constant; )+
|
$( $constant() -> $T::consts::$constant; )+
|
||||||
|
TAU() -> 6.28318530717958647692528676655900577;
|
||||||
|
LOG10_2() -> 0.301029995663981195213738894724493027;
|
||||||
|
LOG2_10() -> 3.32192809488736234787031942948939018;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -2289,7 +2307,7 @@ float_const_impl! {
|
||||||
LOG10_E,
|
LOG10_E,
|
||||||
#[doc = "Return `log2(e)`."]
|
#[doc = "Return `log2(e)`."]
|
||||||
LOG2_E,
|
LOG2_E,
|
||||||
#[doc = "Return Archimedes’ constant."]
|
#[doc = "Return Archimedes’ constant `π`."]
|
||||||
PI,
|
PI,
|
||||||
#[doc = "Return `sqrt(2.0)`."]
|
#[doc = "Return `sqrt(2.0)`."]
|
||||||
SQRT_2,
|
SQRT_2,
|
||||||
|
@ -2350,4 +2368,23 @@ mod tests {
|
||||||
57.2957795130823208767981548141051703
|
57.2957795130823208767981548141051703
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(any(feature = "std", feature = "libm"))]
|
||||||
|
fn extra_logs() {
|
||||||
|
use float::{Float, FloatConst};
|
||||||
|
|
||||||
|
fn check<F: Float + FloatConst>(diff: F) {
|
||||||
|
let _2 = F::from(2.0).unwrap();
|
||||||
|
assert!((F::LOG10_2() - F::log10(_2)).abs() < diff);
|
||||||
|
assert!((F::LOG10_2() - F::LN_2() / F::LN_10()).abs() < diff);
|
||||||
|
|
||||||
|
let _10 = F::from(10.0).unwrap();
|
||||||
|
assert!((F::LOG2_10() - F::log2(_10)).abs() < diff);
|
||||||
|
assert!((F::LOG2_10() - F::LN_10() / F::LN_2()).abs() < diff);
|
||||||
|
}
|
||||||
|
|
||||||
|
check::<f32>(1e-6);
|
||||||
|
check::<f64>(1e-12);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,7 +106,7 @@ pub trait PrimInt:
|
||||||
/// ```
|
/// ```
|
||||||
fn trailing_zeros(self) -> u32;
|
fn trailing_zeros(self) -> u32;
|
||||||
|
|
||||||
/// Shifts the bits to the left by a specified amount amount, `n`, wrapping
|
/// Shifts the bits to the left by a specified amount, `n`, wrapping
|
||||||
/// the truncated bits to the end of the resulting integer.
|
/// the truncated bits to the end of the resulting integer.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
|
@ -121,7 +121,7 @@ pub trait PrimInt:
|
||||||
/// ```
|
/// ```
|
||||||
fn rotate_left(self, n: u32) -> Self;
|
fn rotate_left(self, n: u32) -> Self;
|
||||||
|
|
||||||
/// Shifts the bits to the right by a specified amount amount, `n`, wrapping
|
/// Shifts the bits to the right by a specified amount, `n`, wrapping
|
||||||
/// the truncated bits to the beginning of the resulting integer.
|
/// the truncated bits to the beginning of the resulting integer.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
|
@ -136,7 +136,7 @@ pub trait PrimInt:
|
||||||
/// ```
|
/// ```
|
||||||
fn rotate_right(self, n: u32) -> Self;
|
fn rotate_right(self, n: u32) -> Self;
|
||||||
|
|
||||||
/// Shifts the bits to the left by a specified amount amount, `n`, filling
|
/// Shifts the bits to the left by a specified amount, `n`, filling
|
||||||
/// zeros in the least significant bits.
|
/// zeros in the least significant bits.
|
||||||
///
|
///
|
||||||
/// This is bitwise equivalent to signed `Shl`.
|
/// This is bitwise equivalent to signed `Shl`.
|
||||||
|
@ -153,7 +153,7 @@ pub trait PrimInt:
|
||||||
/// ```
|
/// ```
|
||||||
fn signed_shl(self, n: u32) -> Self;
|
fn signed_shl(self, n: u32) -> Self;
|
||||||
|
|
||||||
/// Shifts the bits to the right by a specified amount amount, `n`, copying
|
/// Shifts the bits to the right by a specified amount, `n`, copying
|
||||||
/// the "sign bit" in the most significant bits even for unsigned types.
|
/// the "sign bit" in the most significant bits even for unsigned types.
|
||||||
///
|
///
|
||||||
/// This is bitwise equivalent to signed `Shr`.
|
/// This is bitwise equivalent to signed `Shr`.
|
||||||
|
@ -170,7 +170,7 @@ pub trait PrimInt:
|
||||||
/// ```
|
/// ```
|
||||||
fn signed_shr(self, n: u32) -> Self;
|
fn signed_shr(self, n: u32) -> Self;
|
||||||
|
|
||||||
/// Shifts the bits to the left by a specified amount amount, `n`, filling
|
/// Shifts the bits to the left by a specified amount, `n`, filling
|
||||||
/// zeros in the least significant bits.
|
/// zeros in the least significant bits.
|
||||||
///
|
///
|
||||||
/// This is bitwise equivalent to unsigned `Shl`.
|
/// This is bitwise equivalent to unsigned `Shl`.
|
||||||
|
@ -187,7 +187,7 @@ pub trait PrimInt:
|
||||||
/// ```
|
/// ```
|
||||||
fn unsigned_shl(self, n: u32) -> Self;
|
fn unsigned_shl(self, n: u32) -> Self;
|
||||||
|
|
||||||
/// Shifts the bits to the right by a specified amount amount, `n`, filling
|
/// Shifts the bits to the right by a specified amount, `n`, filling
|
||||||
/// zeros in the most significant bits.
|
/// zeros in the most significant bits.
|
||||||
///
|
///
|
||||||
/// This is bitwise equivalent to unsigned `Shr`.
|
/// This is bitwise equivalent to unsigned `Shr`.
|
||||||
|
|
|
@ -42,8 +42,10 @@ pub use ops::checked::{
|
||||||
};
|
};
|
||||||
pub use ops::inv::Inv;
|
pub use ops::inv::Inv;
|
||||||
pub use ops::mul_add::{MulAdd, MulAddAssign};
|
pub use ops::mul_add::{MulAdd, MulAddAssign};
|
||||||
pub use ops::saturating::Saturating;
|
pub use ops::saturating::{Saturating, SaturatingAdd, SaturatingMul, SaturatingSub};
|
||||||
pub use ops::wrapping::{WrappingAdd, WrappingMul, WrappingShl, WrappingShr, WrappingSub};
|
pub use ops::wrapping::{
|
||||||
|
WrappingAdd, WrappingMul, WrappingNeg, WrappingShl, WrappingShr, WrappingSub,
|
||||||
|
};
|
||||||
pub use pow::{checked_pow, pow, Pow};
|
pub use pow::{checked_pow, pow, Pow};
|
||||||
pub use sign::{abs, abs_sub, signum, Signed, Unsigned};
|
pub use sign::{abs, abs_sub, signum, Signed, Unsigned};
|
||||||
|
|
||||||
|
@ -65,7 +67,7 @@ pub mod sign;
|
||||||
pub trait Num: PartialEq + Zero + One + NumOps {
|
pub trait Num: PartialEq + Zero + One + NumOps {
|
||||||
type FromStrRadixErr;
|
type FromStrRadixErr;
|
||||||
|
|
||||||
/// Convert from a string and radix <= 36.
|
/// Convert from a string and radix (typically `2..=36`).
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
|
@ -78,6 +80,18 @@ pub trait Num: PartialEq + Zero + One + NumOps {
|
||||||
/// let result = <i32 as Num>::from_str_radix("foo", 10);
|
/// let result = <i32 as Num>::from_str_radix("foo", 10);
|
||||||
/// assert!(result.is_err());
|
/// assert!(result.is_err());
|
||||||
/// ```
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Supported radices
|
||||||
|
///
|
||||||
|
/// The exact range of supported radices is at the discretion of each type implementation. For
|
||||||
|
/// primitive integers, this is implemented by the inherent `from_str_radix` methods in the
|
||||||
|
/// standard library, which **panic** if the radix is not in the range from 2 to 36. The
|
||||||
|
/// implementation in this crate for primitive floats is similar.
|
||||||
|
///
|
||||||
|
/// For third-party types, it is suggested that implementations should follow suit and at least
|
||||||
|
/// accept `2..=36` without panicking, but an `Err` may be returned for any unsupported radix.
|
||||||
|
/// It's possible that a type might not even support the common radix 10, nor any, if string
|
||||||
|
/// parsing doesn't make sense for that type.
|
||||||
fn from_str_radix(str: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr>;
|
fn from_str_radix(str: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
pub mod checked;
|
pub mod checked;
|
||||||
pub mod inv;
|
pub mod inv;
|
||||||
pub mod mul_add;
|
pub mod mul_add;
|
||||||
|
pub mod overflowing;
|
||||||
pub mod saturating;
|
pub mod saturating;
|
||||||
pub mod wrapping;
|
pub mod wrapping;
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
use core::ops::{Add, Mul, Sub};
|
||||||
|
#[cfg(has_i128)]
|
||||||
|
use core::{i128, u128};
|
||||||
|
use core::{i16, i32, i64, i8, isize};
|
||||||
|
use core::{u16, u32, u64, u8, usize};
|
||||||
|
|
||||||
|
macro_rules! overflowing_impl {
|
||||||
|
($trait_name:ident, $method:ident, $t:ty) => {
|
||||||
|
impl $trait_name for $t {
|
||||||
|
#[inline]
|
||||||
|
fn $method(&self, v: &Self) -> (Self, bool) {
|
||||||
|
<$t>::$method(*self, *v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs addition with a flag for overflow.
|
||||||
|
pub trait OverflowingAdd: Sized + Add<Self, Output = Self> {
|
||||||
|
/// Returns a tuple of the sum along with a boolean indicating whether an arithmetic overflow would occur.
|
||||||
|
/// If an overflow would have occurred then the wrapped value is returned.
|
||||||
|
fn overflowing_add(&self, v: &Self) -> (Self, bool);
|
||||||
|
}
|
||||||
|
|
||||||
|
overflowing_impl!(OverflowingAdd, overflowing_add, u8);
|
||||||
|
overflowing_impl!(OverflowingAdd, overflowing_add, u16);
|
||||||
|
overflowing_impl!(OverflowingAdd, overflowing_add, u32);
|
||||||
|
overflowing_impl!(OverflowingAdd, overflowing_add, u64);
|
||||||
|
overflowing_impl!(OverflowingAdd, overflowing_add, usize);
|
||||||
|
#[cfg(has_i128)]
|
||||||
|
overflowing_impl!(OverflowingAdd, overflowing_add, u128);
|
||||||
|
|
||||||
|
overflowing_impl!(OverflowingAdd, overflowing_add, i8);
|
||||||
|
overflowing_impl!(OverflowingAdd, overflowing_add, i16);
|
||||||
|
overflowing_impl!(OverflowingAdd, overflowing_add, i32);
|
||||||
|
overflowing_impl!(OverflowingAdd, overflowing_add, i64);
|
||||||
|
overflowing_impl!(OverflowingAdd, overflowing_add, isize);
|
||||||
|
#[cfg(has_i128)]
|
||||||
|
overflowing_impl!(OverflowingAdd, overflowing_add, i128);
|
||||||
|
|
||||||
|
/// Performs substraction with a flag for overflow.
|
||||||
|
pub trait OverflowingSub: Sized + Sub<Self, Output = Self> {
|
||||||
|
/// Returns a tuple of the difference along with a boolean indicating whether an arithmetic overflow would occur.
|
||||||
|
/// If an overflow would have occurred then the wrapped value is returned.
|
||||||
|
fn overflowing_sub(&self, v: &Self) -> (Self, bool);
|
||||||
|
}
|
||||||
|
|
||||||
|
overflowing_impl!(OverflowingSub, overflowing_sub, u8);
|
||||||
|
overflowing_impl!(OverflowingSub, overflowing_sub, u16);
|
||||||
|
overflowing_impl!(OverflowingSub, overflowing_sub, u32);
|
||||||
|
overflowing_impl!(OverflowingSub, overflowing_sub, u64);
|
||||||
|
overflowing_impl!(OverflowingSub, overflowing_sub, usize);
|
||||||
|
#[cfg(has_i128)]
|
||||||
|
overflowing_impl!(OverflowingSub, overflowing_sub, u128);
|
||||||
|
|
||||||
|
overflowing_impl!(OverflowingSub, overflowing_sub, i8);
|
||||||
|
overflowing_impl!(OverflowingSub, overflowing_sub, i16);
|
||||||
|
overflowing_impl!(OverflowingSub, overflowing_sub, i32);
|
||||||
|
overflowing_impl!(OverflowingSub, overflowing_sub, i64);
|
||||||
|
overflowing_impl!(OverflowingSub, overflowing_sub, isize);
|
||||||
|
#[cfg(has_i128)]
|
||||||
|
overflowing_impl!(OverflowingSub, overflowing_sub, i128);
|
||||||
|
|
||||||
|
/// Performs multiplication with a flag for overflow.
|
||||||
|
pub trait OverflowingMul: Sized + Mul<Self, Output = Self> {
|
||||||
|
/// Returns a tuple of the product along with a boolean indicating whether an arithmetic overflow would occur.
|
||||||
|
/// If an overflow would have occurred then the wrapped value is returned.
|
||||||
|
fn overflowing_mul(&self, v: &Self) -> (Self, bool);
|
||||||
|
}
|
||||||
|
|
||||||
|
overflowing_impl!(OverflowingMul, overflowing_mul, u8);
|
||||||
|
overflowing_impl!(OverflowingMul, overflowing_mul, u16);
|
||||||
|
overflowing_impl!(OverflowingMul, overflowing_mul, u32);
|
||||||
|
overflowing_impl!(OverflowingMul, overflowing_mul, u64);
|
||||||
|
overflowing_impl!(OverflowingMul, overflowing_mul, usize);
|
||||||
|
#[cfg(has_i128)]
|
||||||
|
overflowing_impl!(OverflowingMul, overflowing_mul, u128);
|
||||||
|
|
||||||
|
overflowing_impl!(OverflowingMul, overflowing_mul, i8);
|
||||||
|
overflowing_impl!(OverflowingMul, overflowing_mul, i16);
|
||||||
|
overflowing_impl!(OverflowingMul, overflowing_mul, i32);
|
||||||
|
overflowing_impl!(OverflowingMul, overflowing_mul, i64);
|
||||||
|
overflowing_impl!(OverflowingMul, overflowing_mul, isize);
|
||||||
|
#[cfg(has_i128)]
|
||||||
|
overflowing_impl!(OverflowingMul, overflowing_mul, i128);
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_overflowing_traits() {
|
||||||
|
fn overflowing_add<T: OverflowingAdd>(a: T, b: T) -> (T, bool) {
|
||||||
|
a.overflowing_add(&b)
|
||||||
|
}
|
||||||
|
fn overflowing_sub<T: OverflowingSub>(a: T, b: T) -> (T, bool) {
|
||||||
|
a.overflowing_sub(&b)
|
||||||
|
}
|
||||||
|
fn overflowing_mul<T: OverflowingMul>(a: T, b: T) -> (T, bool) {
|
||||||
|
a.overflowing_mul(&b)
|
||||||
|
}
|
||||||
|
assert_eq!(overflowing_add(5i16, 2), (7, false));
|
||||||
|
assert_eq!(overflowing_add(i16::MAX, 1), (i16::MIN, true));
|
||||||
|
assert_eq!(overflowing_sub(5i16, 2), (3, false));
|
||||||
|
assert_eq!(overflowing_sub(i16::MIN, 1), (i16::MAX, true));
|
||||||
|
assert_eq!(overflowing_mul(5i16, 2), (10, false));
|
||||||
|
assert_eq!(overflowing_mul(1_000_000_000i32, 10), (1410065408, true));
|
||||||
|
}
|
|
@ -1,4 +1,7 @@
|
||||||
/// Saturating math operations
|
use core::ops::{Add, Mul, Sub};
|
||||||
|
|
||||||
|
/// Saturating math operations. Deprecated, use `SaturatingAdd`, `SaturatingSub` and
|
||||||
|
/// `SaturatingMul` instead.
|
||||||
pub trait Saturating {
|
pub trait Saturating {
|
||||||
/// Saturating addition operator.
|
/// Saturating addition operator.
|
||||||
/// Returns a+b, saturating at the numeric bounds instead of overflowing.
|
/// Returns a+b, saturating at the numeric bounds instead of overflowing.
|
||||||
|
@ -9,7 +12,7 @@ pub trait Saturating {
|
||||||
fn saturating_sub(self, v: Self) -> Self;
|
fn saturating_sub(self, v: Self) -> Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! saturating_impl {
|
macro_rules! deprecated_saturating_impl {
|
||||||
($trait_name:ident for $($t:ty)*) => {$(
|
($trait_name:ident for $($t:ty)*) => {$(
|
||||||
impl $trait_name for $t {
|
impl $trait_name for $t {
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -25,6 +28,110 @@ macro_rules! saturating_impl {
|
||||||
)*}
|
)*}
|
||||||
}
|
}
|
||||||
|
|
||||||
saturating_impl!(Saturating for isize usize i8 u8 i16 u16 i32 u32 i64 u64);
|
deprecated_saturating_impl!(Saturating for isize usize i8 u8 i16 u16 i32 u32 i64 u64);
|
||||||
#[cfg(has_i128)]
|
#[cfg(has_i128)]
|
||||||
saturating_impl!(Saturating for i128 u128);
|
deprecated_saturating_impl!(Saturating for i128 u128);
|
||||||
|
|
||||||
|
macro_rules! saturating_impl {
|
||||||
|
($trait_name:ident, $method:ident, $t:ty) => {
|
||||||
|
impl $trait_name for $t {
|
||||||
|
#[inline]
|
||||||
|
fn $method(&self, v: &Self) -> Self {
|
||||||
|
<$t>::$method(*self, *v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs addition that saturates at the numeric bounds instead of overflowing.
|
||||||
|
pub trait SaturatingAdd: Sized + Add<Self, Output = Self> {
|
||||||
|
/// Saturating addition. Computes `self + other`, saturating at the relevant high or low boundary of
|
||||||
|
/// the type.
|
||||||
|
fn saturating_add(&self, v: &Self) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
saturating_impl!(SaturatingAdd, saturating_add, u8);
|
||||||
|
saturating_impl!(SaturatingAdd, saturating_add, u16);
|
||||||
|
saturating_impl!(SaturatingAdd, saturating_add, u32);
|
||||||
|
saturating_impl!(SaturatingAdd, saturating_add, u64);
|
||||||
|
saturating_impl!(SaturatingAdd, saturating_add, usize);
|
||||||
|
#[cfg(has_i128)]
|
||||||
|
saturating_impl!(SaturatingAdd, saturating_add, u128);
|
||||||
|
|
||||||
|
saturating_impl!(SaturatingAdd, saturating_add, i8);
|
||||||
|
saturating_impl!(SaturatingAdd, saturating_add, i16);
|
||||||
|
saturating_impl!(SaturatingAdd, saturating_add, i32);
|
||||||
|
saturating_impl!(SaturatingAdd, saturating_add, i64);
|
||||||
|
saturating_impl!(SaturatingAdd, saturating_add, isize);
|
||||||
|
#[cfg(has_i128)]
|
||||||
|
saturating_impl!(SaturatingAdd, saturating_add, i128);
|
||||||
|
|
||||||
|
/// Performs subtraction that saturates at the numeric bounds instead of overflowing.
|
||||||
|
pub trait SaturatingSub: Sized + Sub<Self, Output = Self> {
|
||||||
|
/// Saturating subtraction. Computes `self - other`, saturating at the relevant high or low boundary of
|
||||||
|
/// the type.
|
||||||
|
fn saturating_sub(&self, v: &Self) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
saturating_impl!(SaturatingSub, saturating_sub, u8);
|
||||||
|
saturating_impl!(SaturatingSub, saturating_sub, u16);
|
||||||
|
saturating_impl!(SaturatingSub, saturating_sub, u32);
|
||||||
|
saturating_impl!(SaturatingSub, saturating_sub, u64);
|
||||||
|
saturating_impl!(SaturatingSub, saturating_sub, usize);
|
||||||
|
#[cfg(has_i128)]
|
||||||
|
saturating_impl!(SaturatingSub, saturating_sub, u128);
|
||||||
|
|
||||||
|
saturating_impl!(SaturatingSub, saturating_sub, i8);
|
||||||
|
saturating_impl!(SaturatingSub, saturating_sub, i16);
|
||||||
|
saturating_impl!(SaturatingSub, saturating_sub, i32);
|
||||||
|
saturating_impl!(SaturatingSub, saturating_sub, i64);
|
||||||
|
saturating_impl!(SaturatingSub, saturating_sub, isize);
|
||||||
|
#[cfg(has_i128)]
|
||||||
|
saturating_impl!(SaturatingSub, saturating_sub, i128);
|
||||||
|
|
||||||
|
/// Performs multiplication that saturates at the numeric bounds instead of overflowing.
|
||||||
|
pub trait SaturatingMul: Sized + Mul<Self, Output = Self> {
|
||||||
|
/// Saturating multiplication. Computes `self * other`, saturating at the relevant high or low boundary of
|
||||||
|
/// the type.
|
||||||
|
fn saturating_mul(&self, v: &Self) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
saturating_impl!(SaturatingMul, saturating_mul, u8);
|
||||||
|
saturating_impl!(SaturatingMul, saturating_mul, u16);
|
||||||
|
saturating_impl!(SaturatingMul, saturating_mul, u32);
|
||||||
|
saturating_impl!(SaturatingMul, saturating_mul, u64);
|
||||||
|
saturating_impl!(SaturatingMul, saturating_mul, usize);
|
||||||
|
#[cfg(has_i128)]
|
||||||
|
saturating_impl!(SaturatingMul, saturating_mul, u128);
|
||||||
|
|
||||||
|
saturating_impl!(SaturatingMul, saturating_mul, i8);
|
||||||
|
saturating_impl!(SaturatingMul, saturating_mul, i16);
|
||||||
|
saturating_impl!(SaturatingMul, saturating_mul, i32);
|
||||||
|
saturating_impl!(SaturatingMul, saturating_mul, i64);
|
||||||
|
saturating_impl!(SaturatingMul, saturating_mul, isize);
|
||||||
|
#[cfg(has_i128)]
|
||||||
|
saturating_impl!(SaturatingMul, saturating_mul, i128);
|
||||||
|
|
||||||
|
// TODO: add SaturatingNeg for signed integer primitives once the saturating_neg() API is stable.
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_saturating_traits() {
|
||||||
|
fn saturating_add<T: SaturatingAdd>(a: T, b: T) -> T {
|
||||||
|
a.saturating_add(&b)
|
||||||
|
}
|
||||||
|
fn saturating_sub<T: SaturatingSub>(a: T, b: T) -> T {
|
||||||
|
a.saturating_sub(&b)
|
||||||
|
}
|
||||||
|
fn saturating_mul<T: SaturatingMul>(a: T, b: T) -> T {
|
||||||
|
a.saturating_mul(&b)
|
||||||
|
}
|
||||||
|
assert_eq!(saturating_add(255, 1), 255u8);
|
||||||
|
assert_eq!(saturating_add(127, 1), 127i8);
|
||||||
|
assert_eq!(saturating_add(-128, -1), -128i8);
|
||||||
|
assert_eq!(saturating_sub(0, 1), 0u8);
|
||||||
|
assert_eq!(saturating_sub(-128, 1), -128i8);
|
||||||
|
assert_eq!(saturating_sub(127, -1), 127i8);
|
||||||
|
assert_eq!(saturating_mul(255, 2), 255u8);
|
||||||
|
assert_eq!(saturating_mul(127, 2), 127i8);
|
||||||
|
assert_eq!(saturating_mul(-128, 2), -128i8);
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use core::num::Wrapping;
|
use core::num::Wrapping;
|
||||||
use core::ops::{Add, Mul, Shl, Shr, Sub};
|
use core::ops::{Add, Mul, Neg, Shl, Shr, Sub};
|
||||||
|
|
||||||
macro_rules! wrapping_impl {
|
macro_rules! wrapping_impl {
|
||||||
($trait_name:ident, $method:ident, $t:ty) => {
|
($trait_name:ident, $method:ident, $t:ty) => {
|
||||||
|
@ -89,6 +89,54 @@ wrapping_impl!(WrappingMul, wrapping_mul, isize);
|
||||||
#[cfg(has_i128)]
|
#[cfg(has_i128)]
|
||||||
wrapping_impl!(WrappingMul, wrapping_mul, i128);
|
wrapping_impl!(WrappingMul, wrapping_mul, i128);
|
||||||
|
|
||||||
|
macro_rules! wrapping_unary_impl {
|
||||||
|
($trait_name:ident, $method:ident, $t:ty) => {
|
||||||
|
impl $trait_name for $t {
|
||||||
|
#[inline]
|
||||||
|
fn $method(&self) -> $t {
|
||||||
|
<$t>::$method(*self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs a negation that does not panic.
|
||||||
|
pub trait WrappingNeg: Sized {
|
||||||
|
/// Wrapping (modular) negation. Computes `-self`,
|
||||||
|
/// wrapping around at the boundary of the type.
|
||||||
|
///
|
||||||
|
/// Since unsigned types do not have negative equivalents
|
||||||
|
/// all applications of this function will wrap (except for `-0`).
|
||||||
|
/// For values smaller than the corresponding signed type's maximum
|
||||||
|
/// the result is the same as casting the corresponding signed value.
|
||||||
|
/// Any larger values are equivalent to `MAX + 1 - (val - MAX - 1)` where
|
||||||
|
/// `MAX` is the corresponding signed type's maximum.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use num_traits::WrappingNeg;
|
||||||
|
///
|
||||||
|
/// assert_eq!(100i8.wrapping_neg(), -100);
|
||||||
|
/// assert_eq!((-100i8).wrapping_neg(), 100);
|
||||||
|
/// assert_eq!((-128i8).wrapping_neg(), -128); // wrapped!
|
||||||
|
/// ```
|
||||||
|
fn wrapping_neg(&self) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapping_unary_impl!(WrappingNeg, wrapping_neg, u8);
|
||||||
|
wrapping_unary_impl!(WrappingNeg, wrapping_neg, u16);
|
||||||
|
wrapping_unary_impl!(WrappingNeg, wrapping_neg, u32);
|
||||||
|
wrapping_unary_impl!(WrappingNeg, wrapping_neg, u64);
|
||||||
|
wrapping_unary_impl!(WrappingNeg, wrapping_neg, usize);
|
||||||
|
#[cfg(has_i128)]
|
||||||
|
wrapping_unary_impl!(WrappingNeg, wrapping_neg, u128);
|
||||||
|
wrapping_unary_impl!(WrappingNeg, wrapping_neg, i8);
|
||||||
|
wrapping_unary_impl!(WrappingNeg, wrapping_neg, i16);
|
||||||
|
wrapping_unary_impl!(WrappingNeg, wrapping_neg, i32);
|
||||||
|
wrapping_unary_impl!(WrappingNeg, wrapping_neg, i64);
|
||||||
|
wrapping_unary_impl!(WrappingNeg, wrapping_neg, isize);
|
||||||
|
#[cfg(has_i128)]
|
||||||
|
wrapping_unary_impl!(WrappingNeg, wrapping_neg, i128);
|
||||||
|
|
||||||
macro_rules! wrapping_shift_impl {
|
macro_rules! wrapping_shift_impl {
|
||||||
($trait_name:ident, $method:ident, $t:ty) => {
|
($trait_name:ident, $method:ident, $t:ty) => {
|
||||||
impl $trait_name for $t {
|
impl $trait_name for $t {
|
||||||
|
@ -195,6 +243,14 @@ where
|
||||||
Wrapping(self.0.wrapping_mul(&v.0))
|
Wrapping(self.0.wrapping_mul(&v.0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl<T: WrappingNeg> WrappingNeg for Wrapping<T>
|
||||||
|
where
|
||||||
|
Wrapping<T>: Neg<Output = Wrapping<T>>,
|
||||||
|
{
|
||||||
|
fn wrapping_neg(&self) -> Self {
|
||||||
|
Wrapping(self.0.wrapping_neg())
|
||||||
|
}
|
||||||
|
}
|
||||||
impl<T: WrappingShl> WrappingShl for Wrapping<T>
|
impl<T: WrappingShl> WrappingShl for Wrapping<T>
|
||||||
where
|
where
|
||||||
Wrapping<T>: Shl<usize, Output = Wrapping<T>>,
|
Wrapping<T>: Shl<usize, Output = Wrapping<T>>,
|
||||||
|
@ -223,6 +279,9 @@ fn test_wrapping_traits() {
|
||||||
fn wrapping_mul<T: WrappingMul>(a: T, b: T) -> T {
|
fn wrapping_mul<T: WrappingMul>(a: T, b: T) -> T {
|
||||||
a.wrapping_mul(&b)
|
a.wrapping_mul(&b)
|
||||||
}
|
}
|
||||||
|
fn wrapping_neg<T: WrappingNeg>(a: T) -> T {
|
||||||
|
a.wrapping_neg()
|
||||||
|
}
|
||||||
fn wrapping_shl<T: WrappingShl>(a: T, b: u32) -> T {
|
fn wrapping_shl<T: WrappingShl>(a: T, b: u32) -> T {
|
||||||
a.wrapping_shl(b)
|
a.wrapping_shl(b)
|
||||||
}
|
}
|
||||||
|
@ -232,11 +291,14 @@ fn test_wrapping_traits() {
|
||||||
assert_eq!(wrapping_add(255, 1), 0u8);
|
assert_eq!(wrapping_add(255, 1), 0u8);
|
||||||
assert_eq!(wrapping_sub(0, 1), 255u8);
|
assert_eq!(wrapping_sub(0, 1), 255u8);
|
||||||
assert_eq!(wrapping_mul(255, 2), 254u8);
|
assert_eq!(wrapping_mul(255, 2), 254u8);
|
||||||
|
assert_eq!(wrapping_neg(255), 1u8);
|
||||||
assert_eq!(wrapping_shl(255, 8), 255u8);
|
assert_eq!(wrapping_shl(255, 8), 255u8);
|
||||||
assert_eq!(wrapping_shr(255, 8), 255u8);
|
assert_eq!(wrapping_shr(255, 8), 255u8);
|
||||||
assert_eq!(wrapping_add(255, 1), (Wrapping(255u8) + Wrapping(1u8)).0);
|
assert_eq!(wrapping_add(255, 1), (Wrapping(255u8) + Wrapping(1u8)).0);
|
||||||
assert_eq!(wrapping_sub(0, 1), (Wrapping(0u8) - Wrapping(1u8)).0);
|
assert_eq!(wrapping_sub(0, 1), (Wrapping(0u8) - Wrapping(1u8)).0);
|
||||||
assert_eq!(wrapping_mul(255, 2), (Wrapping(255u8) * Wrapping(2u8)).0);
|
assert_eq!(wrapping_mul(255, 2), (Wrapping(255u8) * Wrapping(2u8)).0);
|
||||||
|
// TODO: Test for Wrapping::Neg. Not possible yet since core::ops::Neg was
|
||||||
|
// only added to core::num::Wrapping<_> in Rust 1.10.
|
||||||
assert_eq!(wrapping_shl(255, 8), (Wrapping(255u8) << 8).0);
|
assert_eq!(wrapping_shl(255, 8), (Wrapping(255u8) << 8).0);
|
||||||
assert_eq!(wrapping_shr(255, 8), (Wrapping(255u8) >> 8).0);
|
assert_eq!(wrapping_shr(255, 8), (Wrapping(255u8) >> 8).0);
|
||||||
}
|
}
|
||||||
|
@ -259,6 +321,9 @@ fn wrapping_is_wrappingmul() {
|
||||||
require_wrappingmul(&Wrapping(42));
|
require_wrappingmul(&Wrapping(42));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Test for Wrapping::Neg. Not possible yet since core::ops::Neg was
|
||||||
|
// only added to core::num::Wrapping<_> in Rust 1.10.
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn wrapping_is_wrappingshl() {
|
fn wrapping_is_wrappingshl() {
|
||||||
fn require_wrappingshl<T: WrappingShl>(_: &T) {}
|
fn require_wrappingshl<T: WrappingShl>(_: &T) {}
|
||||||
|
|
|
@ -213,13 +213,12 @@ fn unsigned_wrapping_is_unsigned() {
|
||||||
fn require_unsigned<T: Unsigned>(_: &T) {}
|
fn require_unsigned<T: Unsigned>(_: &T) {}
|
||||||
require_unsigned(&Wrapping(42_u32));
|
require_unsigned(&Wrapping(42_u32));
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
// Commenting this out since it doesn't compile on Rust 1.8,
|
// Commenting this out since it doesn't compile on Rust 1.8,
|
||||||
// because on this version Wrapping doesn't implement Neg and therefore can't
|
// because on this version Wrapping doesn't implement Neg and therefore can't
|
||||||
// implement Signed.
|
// implement Signed.
|
||||||
#[test]
|
// #[test]
|
||||||
fn signed_wrapping_is_signed() {
|
// fn signed_wrapping_is_signed() {
|
||||||
fn require_signed<T: Signed>(_: &T) {}
|
// fn require_signed<T: Signed>(_: &T) {}
|
||||||
require_signed(&Wrapping(-42));
|
// require_signed(&Wrapping(-42));
|
||||||
}
|
// }
|
||||||
*/
|
|
||||||
|
|
|
@ -24,7 +24,8 @@ use core::num::Wrapping;
|
||||||
#[test]
|
#[test]
|
||||||
fn to_primitive_float() {
|
fn to_primitive_float() {
|
||||||
let f32_toolarge = 1e39f64;
|
let f32_toolarge = 1e39f64;
|
||||||
assert_eq!(f32_toolarge.to_f32(), None);
|
assert_eq!(f32_toolarge.to_f32(), Some(f32::INFINITY));
|
||||||
|
assert_eq!((-f32_toolarge).to_f32(), Some(f32::NEG_INFINITY));
|
||||||
assert_eq!((f32::MAX as f64).to_f32(), Some(f32::MAX));
|
assert_eq!((f32::MAX as f64).to_f32(), Some(f32::MAX));
|
||||||
assert_eq!((-f32::MAX as f64).to_f32(), Some(-f32::MAX));
|
assert_eq!((-f32::MAX as f64).to_f32(), Some(-f32::MAX));
|
||||||
assert_eq!(f64::INFINITY.to_f32(), Some(f32::INFINITY));
|
assert_eq!(f64::INFINITY.to_f32(), Some(f32::INFINITY));
|
||||||
|
|
|
@ -9,7 +9,7 @@ description = "Shared Rust code for libxul"
|
||||||
geckoservo = { path = "../../../../servo/ports/geckolib" }
|
geckoservo = { path = "../../../../servo/ports/geckolib" }
|
||||||
kvstore = { path = "../../../components/kvstore" }
|
kvstore = { path = "../../../components/kvstore" }
|
||||||
lmdb-rkv-sys = { version = "0.11", features = ["mdb_idl_logn_9"] }
|
lmdb-rkv-sys = { version = "0.11", features = ["mdb_idl_logn_9"] }
|
||||||
mp4parse_capi = { git = "https://github.com/mozilla/mp4parse-rust", rev = "3d9efdc868ce8c5767cea28708fa6512c0ab6d17" }
|
mp4parse_capi = { git = "https://github.com/mozilla/mp4parse-rust", rev = "3011a2b923c8b0f1b392bcdd008cd8b95ffd846b" }
|
||||||
nserror = { path = "../../../../xpcom/rust/nserror" }
|
nserror = { path = "../../../../xpcom/rust/nserror" }
|
||||||
nsstring = { path = "../../../../xpcom/rust/nsstring" }
|
nsstring = { path = "../../../../xpcom/rust/nsstring" }
|
||||||
netwerk_helper = { path = "../../../../netwerk/base/rust-helper" }
|
netwerk_helper = { path = "../../../../netwerk/base/rust-helper" }
|
||||||
|
|
Загрузка…
Ссылка в новой задаче