Skip to content

Commit 0c54469

Browse files
committed
release: 0.7.0
2 parents 8c41e8a + ea75674 commit 0c54469

9 files changed

+135
-106
lines changed

CHANGELOG.md

+16
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,22 @@
22

33

44

5+
## [0.7.0](https://github.com/Blobfolio/cdtoc/releases/tag/v0.7.0) - 2025-02-20
6+
7+
### New
8+
9+
* Bump MSRV `1.85`
10+
* Bump edition `2024`
11+
* `Toc::ctdb_url`
12+
* `Toc::musicbrainz_url`
13+
14+
### Removed
15+
16+
* `AccurateRip::pretty_print`
17+
* `ShaB64::pretty_print`
18+
19+
20+
521
## [0.6.1](https://github.com/Blobfolio/cdtoc/releases/tag/v0.6.1) - 2025-01-09
622

723
### Changed

CREDITS.md

+10-10
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Project Dependencies
22
Package: cdtoc
3-
Version: 0.6.1
4-
Generated: 2025-01-09 18:06:36 UTC
3+
Version: 0.7.0
4+
Generated: 2025-02-20 19:42:00 UTC
55

66
| Package | Version | Author(s) | License |
77
| ---- | ---- | ---- | ---- |
@@ -10,27 +10,27 @@
1010
| [**trimothy**](https://github.com/Blobfolio/trimothy) | 0.6.1 | [Josh Stoik](mailto:josh@blobfolio.com) | WTFPL |
1111
| [_block-buffer_](https://github.com/RustCrypto/utils) | 0.10.4 | RustCrypto Developers | MIT OR Apache-2.0 |
1212
| [_byteorder_](https://github.com/BurntSushi/byteorder) | 1.5.0 | [Andrew Gallant](mailto:jamslam@gmail.com) | Unlicense OR MIT |
13-
| [_cc_](https://github.com/rust-lang/cc-rs) ⚒️ | 1.2.7 | [Alex Crichton](mailto:alex@alexcrichton.com) | MIT OR Apache-2.0 |
13+
| [_cc_](https://github.com/rust-lang/cc-rs) ⚒️ | 1.2.14 | [Alex Crichton](mailto:alex@alexcrichton.com) | MIT OR Apache-2.0 |
1414
| [_cfg-if_](https://github.com/alexcrichton/cfg-if) | 1.0.0 | [Alex Crichton](mailto:alex@alexcrichton.com) | MIT OR Apache-2.0 |
15-
| [_cpufeatures_](https://github.com/RustCrypto/utils) | 0.2.16 | RustCrypto Developers | MIT OR Apache-2.0 |
15+
| [_cpufeatures_](https://github.com/RustCrypto/utils) | 0.2.17 | RustCrypto Developers | MIT OR Apache-2.0 |
1616
| [_crypto-common_](https://github.com/RustCrypto/traits) | 0.1.6 | RustCrypto Developers | MIT OR Apache-2.0 |
1717
| [_digest_](https://github.com/RustCrypto/traits) | 0.10.7 | RustCrypto Developers | MIT OR Apache-2.0 |
1818
| [_generic-array_](https://github.com/fizyk20/generic-array.git) | 0.14.7 | [Bartłomiej Kamiński](mailto:fizyk20@gmail.com) and [Aaron Trent](mailto:novacrazy@gmail.com) | MIT |
1919
| [_hash32_](https://github.com/japaric/hash32) | 0.3.1 | [Jorge Aparicio](mailto:jorge@japaric.io) | MIT OR Apache-2.0 |
2020
| [_heapless_](https://github.com/rust-embedded/heapless) | 0.8.0 | [Jorge Aparicio](mailto:jorge@japaric.io), [Per Lindgren](mailto:per.lindgren@ltu.se), and [Emil Fresk](mailto:emil.fresk@gmail.com) | MIT OR Apache-2.0 |
2121
| [**_itoa_**](https://github.com/dtolnay/itoa) | 1.0.14 | [David Tolnay](mailto:dtolnay@gmail.com) | MIT OR Apache-2.0 |
2222
| [_libc_](https://github.com/rust-lang/libc) | 0.2.169 | The Rust Project Developers | MIT OR Apache-2.0 |
23-
| [_proc-macro2_](https://github.com/dtolnay/proc-macro2) ⚒️ | 1.0.92 | [David Tolnay](mailto:dtolnay@gmail.com) and [Alex Crichton](mailto:alex@alexcrichton.com) | MIT OR Apache-2.0 |
23+
| [_proc-macro2_](https://github.com/dtolnay/proc-macro2) ⚒️ | 1.0.93 | [David Tolnay](mailto:dtolnay@gmail.com) and [Alex Crichton](mailto:alex@alexcrichton.com) | MIT OR Apache-2.0 |
2424
| [_quote_](https://github.com/dtolnay/quote) ⚒️ | 1.0.38 | [David Tolnay](mailto:dtolnay@gmail.com) | MIT OR Apache-2.0 |
25-
| [**_serde_**](https://github.com/serde-rs/serde) | 1.0.217 | [Erick Tryzelaar](mailto:erick.tryzelaar@gmail.com) and [David Tolnay](mailto:dtolnay@gmail.com) | MIT OR Apache-2.0 |
26-
| [_serde_derive_](https://github.com/serde-rs/serde) | 1.0.217 | [Erick Tryzelaar](mailto:erick.tryzelaar@gmail.com) and [David Tolnay](mailto:dtolnay@gmail.com) | MIT OR Apache-2.0 |
25+
| [**_serde_**](https://github.com/serde-rs/serde) | 1.0.218 | [Erick Tryzelaar](mailto:erick.tryzelaar@gmail.com) and [David Tolnay](mailto:dtolnay@gmail.com) | MIT OR Apache-2.0 |
26+
| [_serde_derive_](https://github.com/serde-rs/serde) | 1.0.218 | [Erick Tryzelaar](mailto:erick.tryzelaar@gmail.com) and [David Tolnay](mailto:dtolnay@gmail.com) | MIT OR Apache-2.0 |
2727
| [**_sha1_**](https://github.com/RustCrypto/hashes) | 0.10.6 | RustCrypto Developers | MIT OR Apache-2.0 |
2828
| [_sha1-asm_](https://github.com/RustCrypto/asm-hashes) | 0.5.3 | RustCrypto Developers | MIT |
2929
| [_shlex_](https://github.com/comex/rust-shlex) ⚒️ | 1.3.0 | [comex](mailto:comexk@gmail.com), [Fenhl](mailto:fenhl@fenhl.net), [Adrian Taylor](mailto:adetaylor@chromium.org), [Alex Touchet](mailto:alextouchet@outlook.com), [Daniel Parks](mailto:dp+git@oxidized.org), and [Garrett Berg](mailto:googberg@gmail.com) | MIT OR Apache-2.0 |
3030
| [_stable_deref_trait_](https://github.com/storyyeller/stable_deref_trait) | 1.2.0 | [Robert Grosse](mailto:n210241048576@gmail.com) | MIT OR Apache-2.0 |
31-
| [_syn_](https://github.com/dtolnay/syn) ⚒️ | 2.0.95 | [David Tolnay](mailto:dtolnay@gmail.com) | MIT OR Apache-2.0 |
32-
| [_typenum_](https://github.com/paholg/typenum) | 1.17.0 | [Paho Lurie-Gregg](mailto:paho@paholg.com) and [Andre Bogus](mailto:bogusandre@gmail.com) | MIT OR Apache-2.0 |
33-
| [_unicode-ident_](https://github.com/dtolnay/unicode-ident) ⚒️ | 1.0.14 | [David Tolnay](mailto:dtolnay@gmail.com) | (MIT OR Apache-2.0) AND Unicode-3.0 |
31+
| [_syn_](https://github.com/dtolnay/syn) ⚒️ | 2.0.98 | [David Tolnay](mailto:dtolnay@gmail.com) | MIT OR Apache-2.0 |
32+
| [_typenum_](https://github.com/paholg/typenum) | 1.18.0 | [Paho Lurie-Gregg](mailto:paho@paholg.com) and [Andre Bogus](mailto:bogusandre@gmail.com) | MIT OR Apache-2.0 |
33+
| [_unicode-ident_](https://github.com/dtolnay/unicode-ident) ⚒️ | 1.0.17 | [David Tolnay](mailto:dtolnay@gmail.com) | (MIT OR Apache-2.0) AND Unicode-3.0 |
3434
| [_version_check_](https://github.com/SergioBenitez/version_check) ⚒️ | 0.9.5 | [Sergio Benitez](mailto:sb@sergio.bz) | MIT OR Apache-2.0 |
3535

3636
### Legend

Cargo.toml

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
[package]
22
name = "cdtoc"
3-
version = "0.6.1"
3+
version = "0.7.0"
44
authors = ["Josh Stoik <josh@blobfolio.com>"]
5-
edition = "2021"
6-
rust-version = "1.83"
5+
edition = "2024"
6+
rust-version = "1.85"
77
description = "Parser and tools for CDTOC metadata tags."
88
license = "WTFPL"
99
repository = "https://github.com/Blobfolio/cdtoc"

benches/ctdb.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,6 @@ fn main() {
1919
benches!(
2020
inline:
2121
Bench::new("Toc::ctdb_id").run(|| toc.ctdb_id()),
22-
Bench::new("ShaB64::pretty_print").run(|| ctdb_id.pretty_print()),
22+
Bench::new("ShaB64::to_string").run(|| ctdb_id.to_string()),
2323
);
2424
}

src/accuraterip.rs

-45
Original file line numberDiff line numberDiff line change
@@ -404,50 +404,6 @@ impl AccurateRip {
404404
if out.is_empty() { Err(TocError::NoDriveOffsets) }
405405
else { Ok(out) }
406406
}
407-
408-
#[expect(unsafe_code, reason = "For performance.")]
409-
#[must_use]
410-
/// # Pretty Print.
411-
///
412-
/// Return a String representation of the disc ID, same as `AccurateRip::to_string`,
413-
/// but a little faster.
414-
///
415-
/// ## Examples
416-
///
417-
/// ```
418-
/// use cdtoc::Toc;
419-
///
420-
/// let toc = Toc::from_cdtoc("D+96+4FFB+7F76+BB0A+EF38+12FB3+16134+1BCC4+1EC21+24A6A+272F9+299FA+2CCA6+30EE6").unwrap();
421-
/// assert_eq!(
422-
/// toc.accuraterip_id().pretty_print(),
423-
/// "013-0015deca-00d9b921-9a0a6e0d",
424-
/// );
425-
/// assert_eq!(
426-
/// toc.accuraterip_id().to_string(),
427-
/// "013-0015deca-00d9b921-9a0a6e0d",
428-
/// );
429-
/// ```
430-
pub fn pretty_print(&self) -> String {
431-
let mut out: Vec<u8> = vec![
432-
b'0', b'0', b'0',
433-
b'-', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0',
434-
b'-', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0',
435-
b'-', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0',
436-
];
437-
438-
// Length.
439-
out[..3].copy_from_slice(dactyl::NiceU8::from(self.0[0]).as_bytes3());
440-
441-
// ID Parts.
442-
faster_hex::hex_encode_fallback(&[self.0[4], self.0[3], self.0[2], self.0[1]], &mut out[4..12]);
443-
faster_hex::hex_encode_fallback(&[self.0[8], self.0[7], self.0[6], self.0[5]], &mut out[13..21]);
444-
faster_hex::hex_encode_fallback(&[self.0[12], self.0[11], self.0[10], self.0[9]], &mut out[22..]);
445-
446-
debug_assert!(out.is_ascii(), "Bug: AccurateRip ID is not ASCII?!");
447-
448-
// Safety: all bytes are ASCII.
449-
unsafe { String::from_utf8_unchecked(out) }
450-
}
451407
}
452408

453409
impl AccurateRip {
@@ -585,7 +541,6 @@ mod tests {
585541
let toc = Toc::from_cdtoc(t).expect("Invalid TOC");
586542
let ar_id = toc.accuraterip_id();
587543
assert_eq!(ar_id.to_string(), id);
588-
assert_eq!(ar_id.pretty_print(), id);
589544

590545
// Test decoding three ways.
591546
assert_eq!(AccurateRip::decode(id), Ok(ar_id));

src/ctdb.rs

+35-4
Original file line numberDiff line numberDiff line change
@@ -98,20 +98,52 @@ impl Toc {
9898
ShaB64::from(sha)
9999
}
100100

101+
#[cfg_attr(docsrs, doc(cfg(feature = "ctdb")))]
102+
#[must_use]
103+
/// # CUETools Database URL.
104+
///
105+
/// This URL can be visited in a web browser to view the details for the
106+
/// disc (if it is present in the database).
107+
///
108+
/// See also: [`Toc::ctdb_checksum_url`]
109+
///
110+
/// ## Examples
111+
///
112+
/// ```
113+
/// use cdtoc::Toc;
114+
///
115+
/// let toc = Toc::from_cdtoc("4+96+2D2B+6256+B327+D84A").unwrap();
116+
///
117+
/// // Note: the CUETools website lacks SSL/TLS support.
118+
/// assert_eq!(
119+
/// toc.ctdb_url(),
120+
/// "http://db.cuetools.net/?tocid=VukMWWItblELRM.CEFpXxw0FlME-",
121+
/// );
122+
/// ```
123+
pub fn ctdb_url(&self) -> String {
124+
let mut out = String::with_capacity(58);
125+
out.push_str("http://db.cuetools.net/?tocid=");
126+
self.ctdb_id().push_to_string(&mut out);
127+
out
128+
}
129+
101130
#[cfg_attr(docsrs, doc(cfg(feature = "ctdb")))]
102131
#[must_use]
103132
/// # CUETools Database Checksum URL.
104133
///
105-
/// This returns the URL where you can download the checksums for the disc,
106-
/// provided it is actually _in_ the CTDB. (If it isn't, their server will
107-
/// return a `404` or empty XML document.)
134+
/// This URL can be used to fetch XML-formatted checksums and metadata for
135+
/// the disc (if it is present in the database).
136+
///
137+
/// See also: [`Toc::ctdb_url`]
108138
///
109139
/// ## Examples
110140
///
111141
/// ```
112142
/// use cdtoc::Toc;
113143
///
114144
/// let toc = Toc::from_cdtoc("4+96+2D2B+6256+B327+D84A").unwrap();
145+
///
146+
/// // Note: the CUETools website lacks SSL/TLS support.
115147
/// assert_eq!(
116148
/// toc.ctdb_checksum_url(),
117149
/// "http://db.cuetools.net/lookup2.php?version=3&ctdb=1&fuzzy=1&toc=0:11413:25024:45713:55220",
@@ -260,7 +292,6 @@ mod tests {
260292
let toc = Toc::from_cdtoc(t).expect("Invalid TOC");
261293
let ctdb_id = toc.ctdb_id();
262294
assert_eq!(ctdb_id.to_string(), id);
263-
assert_eq!(ctdb_id.pretty_print(), id);
264295
assert_eq!(toc.ctdb_checksum_url(), lookup);
265296

266297
// Test decoding three ways.

src/musicbrainz.rs

+25-5
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,6 @@ impl Toc {
3737
/// toc.musicbrainz_id().to_string(),
3838
/// "nljDXdC8B_pDwbdY1vZJvdrAZI4-",
3939
/// );
40-
/// assert_eq!(
41-
/// toc.musicbrainz_id().pretty_print(),
42-
/// "nljDXdC8B_pDwbdY1vZJvdrAZI4-",
43-
/// );
4440
/// ```
4541
pub fn musicbrainz_id(&self) -> ShaB64 {
4642
use sha1::Digest;
@@ -98,6 +94,31 @@ impl Toc {
9894
// Run it through base64 and we're done!
9995
ShaB64::from(sha)
10096
}
97+
98+
#[cfg_attr(docsrs, doc(cfg(feature = "musicbrainz")))]
99+
#[must_use]
100+
/// # MusicBrainz URL.
101+
///
102+
/// This URL can be visited in a web browser to view the details for the
103+
/// disc (if it is present in the database).
104+
///
105+
/// ## Examples
106+
///
107+
/// ```
108+
/// use cdtoc::Toc;
109+
///
110+
/// let toc = Toc::from_cdtoc("4+96+2D2B+6256+B327+D84A").unwrap();
111+
/// assert_eq!(
112+
/// toc.musicbrainz_url(),
113+
/// "https://musicbrainz.org/cdtoc/nljDXdC8B_pDwbdY1vZJvdrAZI4-",
114+
/// );
115+
/// ```
116+
pub fn musicbrainz_url(&self) -> String {
117+
let mut out = String::with_capacity(58);
118+
out.push_str("https://musicbrainz.org/cdtoc/");
119+
self.musicbrainz_id().push_to_string(&mut out);
120+
out
121+
}
101122
}
102123

103124

@@ -137,7 +158,6 @@ mod tests {
137158
let toc = Toc::from_cdtoc(t).expect("Invalid TOC");
138159
let mb_id = toc.musicbrainz_id();
139160
assert_eq!(mb_id.to_string(), id);
140-
assert_eq!(mb_id.pretty_print(), id);
141161

142162
// Test decoding three ways.
143163
assert_eq!(ShaB64::decode(id), Ok(mb_id));

src/serde.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,13 @@ macro_rules! serialize_with {
7474

7575

7676
#[cfg(feature = "accuraterip")] deserialize_str_with!(AccurateRip, decode);
77-
#[cfg(feature = "accuraterip")] serialize_with!(AccurateRip, pretty_print);
77+
#[cfg(feature = "accuraterip")] serialize_with!(AccurateRip, to_string);
7878

7979
#[cfg(feature = "cddb")] deserialize_str_with!(Cddb, decode);
8080
#[cfg(feature = "cddb")] serialize_with!(Cddb, to_string);
8181

8282
#[cfg(feature = "sha1")] deserialize_str_with!(ShaB64, decode);
83-
#[cfg(feature = "sha1")] serialize_with!(ShaB64, pretty_print);
83+
#[cfg(feature = "sha1")] serialize_with!(ShaB64, to_string);
8484

8585
deserialize_str_with!(Toc, from_cdtoc);
8686
serialize_with!(Toc, to_string);

src/shab64.rs

+43-36
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,35 @@ use std::{
2121
/// This struct holds ID data for MusicBrainz and CTDB consisting of a binary
2222
/// sha1 hash encoded with an almost-but-not-quite standard base64 alphabet.
2323
///
24-
/// String formatting is deferred until `ShaB64::to_string` or
25-
/// [`ShaB64::pretty_print`] are called, allowing for a slightly smaller and
26-
/// `copy`-friendly footprint.
24+
/// String formatting is deferred until [`fmt::Display`], allowing for a
25+
/// slightly smaller and `Copy`-friendly footprint.
2726
///
2827
/// If you already have a stringified copy and want to get back to a `ShaB64`,
2928
/// you can use [`ShaB64::decode`] or its `FromStr` or `TryFrom<&str>` impls.
3029
pub struct ShaB64([u8; 20]);
3130

3231
impl fmt::Display for ShaB64 {
33-
#[inline]
3432
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35-
f.pad(&self.pretty_print())
33+
// The output will always be 28-bytes, ending with a dash.
34+
let mut buf = [b'-'; 28];
35+
36+
// For all but the last chunk, it's a simple 3:4 ratio.
37+
for (raw, dst) in self.0.chunks_exact(3).zip(buf.chunks_exact_mut(4)) {
38+
dst[0] = base64_encode(raw[0] >> 2);
39+
dst[1] = base64_encode(((raw[0] & 0b0000_0011) << 4) | (raw[1] >> 4));
40+
dst[2] = base64_encode(((raw[1] & 0b0000_1111) << 2) | (raw[2] >> 6));
41+
dst[3] = base64_encode(raw[2] & 0b0011_1111);
42+
}
43+
44+
// The last byte (27) is always padding, but the three before it still
45+
// need figuring.
46+
buf[24] = base64_encode(self.0[18] >> 2);
47+
buf[25] = base64_encode(((self.0[18] & 0b0000_0011) << 4) | (self.0[19] >> 4));
48+
buf[26] = base64_encode((self.0[19] & 0b0000_1111) << 2);
49+
50+
std::str::from_utf8(buf.as_slice())
51+
.map_err(|_| fmt::Error)
52+
.and_then(|s| f.pad(s))
3653
}
3754
}
3855

@@ -74,57 +91,47 @@ impl ShaB64 {
7491
let c = base64_decode(chunk[2])?;
7592
let d = base64_decode(chunk[3])?;
7693
i.copy_from_slice(&[
77-
(a & 0b0011_1111) << 2 | b >> 4,
78-
(b & 0b0000_1111) << 4 | c >> 2,
79-
(c & 0b0000_0011) << 6 | d & 0b0011_1111,
94+
((a & 0b0011_1111) << 2) | (b >> 4),
95+
((b & 0b0000_1111) << 4) | (c >> 2),
96+
((c & 0b0000_0011) << 6) | d & 0b0011_1111,
8097
]);
8198
}
8299

83100
// Handle the remainder manually.
84101
let a = base64_decode(src[24])?;
85102
let b = base64_decode(src[25])?;
86103
let c = base64_decode(src[26])?;
87-
out[18] = (a & 0b0011_1111) << 2 | b >> 4;
88-
out[19] = (b & 0b0000_1111) << 4 | c >> 2;
104+
out[18] = ((a & 0b0011_1111) << 2) | (b >> 4);
105+
out[19] = ((b & 0b0000_1111) << 4) | (c >> 2);
89106

90107
// Done!
91108
Ok(Self(out))
92109
}
93110
else { Err(TocError::ShaB64Decode) }
94111
}
95112

96-
#[expect(unsafe_code, reason = "For performance.")]
97-
#[must_use]
98-
/// # Pretty Print.
113+
#[inline]
114+
/// # Push to String.
99115
///
100-
/// Return the value has a human-readable string, exactly like `ShaB64::to_string`,
101-
/// but slightly faster. The result will always be 28-characters in length.
102-
pub fn pretty_print(&self) -> String {
103-
let mut out = Vec::with_capacity(28);
104-
105-
// Handle all the nice 3-byte chunks en masse.
116+
/// Unpack and write `self` onto the end of a string without the use of
117+
/// any intermediary buffers.
118+
pub(crate) fn push_to_string(&self, out: &mut String) {
119+
// For all but the last chunk, it's a simple 3:4 ratio.
106120
for chunk in self.0.chunks_exact(3) {
107-
out.push(base64_encode(chunk[0] >> 2));
108-
out.push(base64_encode((chunk[0] & 0b0000_0011) << 4 | chunk[1] >> 4));
109-
out.push(base64_encode((chunk[1] & 0b0000_1111) << 2 | chunk[2] >> 6));
110-
out.push(base64_encode(chunk[2] & 0b0011_1111));
121+
out.push(base64_encode(chunk[0] >> 2) as char);
122+
out.push(base64_encode(((chunk[0] & 0b0000_0011) << 4) | (chunk[1] >> 4)) as char);
123+
out.push(base64_encode(((chunk[1] & 0b0000_1111) << 2) | (chunk[2] >> 6)) as char);
124+
out.push(base64_encode(chunk[2] & 0b0011_1111) as char);
111125
}
112126

113-
// Handle the remainder manually.
114-
out.push(base64_encode(self.0[18] >> 2));
115-
out.push(base64_encode((self.0[18] & 0b0000_0011) << 4 | self.0[19] >> 4));
116-
out.push(base64_encode((self.0[19] & 0b0000_1111) << 2));
127+
// The last byte (27) is always padding, but the three before it still
128+
// need figuring.
129+
out.push(base64_encode(self.0[18] >> 2) as char);
130+
out.push(base64_encode(((self.0[18] & 0b0000_0011) << 4) | (self.0[19] >> 4)) as char);
131+
out.push(base64_encode((self.0[19] & 0b0000_1111) << 2) as char);
117132

118133
// And add one byte for padding.
119-
out.push(b'-');
120-
121-
debug_assert!(
122-
out.len() == 28 && out.is_ascii(),
123-
"Bug: Sha/base64 ID is malformed."
124-
);
125-
126-
// Safety: our alphabet is ASCII.
127-
unsafe { String::from_utf8_unchecked(out) }
134+
out.push('-');
128135
}
129136
}
130137

0 commit comments

Comments
 (0)